From 9b0a1d53dc449fedebd5eb6255a312aa334ffad9 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 14 Jan 2024 23:40:29 +0100 Subject: [PATCH 001/299] Latte: Fix syntax error in generated GLSL --- .../LatteDecompilerEmitGLSL.cpp | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp index aa7b7162..f3d2c7a8 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp @@ -246,6 +246,22 @@ static void _appendPVPS(LatteDecompilerShaderContext* shaderContext, StringBuf* _appendChannel(src, aluUnit); } +std::string _FormatFloatAsGLSLConstant(float f) +{ + char floatAsStr[64]; + size_t floatAsStrLen = fmt::format_to_n(floatAsStr, 64, "{:#}", f).size; + size_t floatAsStrLenOrg = floatAsStrLen; + if(floatAsStrLen > 0 && floatAsStr[floatAsStrLen-1] == '.') + { + floatAsStr[floatAsStrLen] = '0'; + floatAsStrLen++; + } + cemu_assert(floatAsStrLen < 50); // constant suspiciously long? + floatAsStr[floatAsStrLen] = '\0'; + cemu_assert_debug(floatAsStrLen >= 3); // shortest possible form is "0.0" + return floatAsStr; +} + // tracks PV/PS and register backups struct ALUClauseTemporariesState { @@ -926,15 +942,7 @@ void _emitOperandInputCode(LatteDecompilerShaderContext* shaderContext, LatteDec exponent -= 127; if ((constVal & 0xFF) == 0 && exponent >= -10 && exponent <= 10) { - char floatAsStr[32]; - size_t floatAsStrLen = fmt::format_to_n(floatAsStr, 32, "{:#}", *(float*)&constVal).size; - if(floatAsStrLen > 0 && floatAsStr[floatAsStrLen-1] == '.') - { - floatAsStr[floatAsStrLen] = '0'; - floatAsStrLen++; - } - cemu_assert_debug(floatAsStrLen >= 3); // shortest possible form is "0.0" - src->add(std::string_view(floatAsStr, floatAsStrLen)); + src->add(_FormatFloatAsGLSLConstant(*(float*)&constVal)); } else src->addFmt("intBitsToFloat(0x{:08x})", constVal); @@ -2561,13 +2569,11 @@ void _emitTEXSampleTextureCode(LatteDecompilerShaderContext* shaderContext, Latt // lod or lod bias parameter if( texOpcode == GPU7_TEX_INST_SAMPLE_L || texOpcode == GPU7_TEX_INST_SAMPLE_LB || texOpcode == GPU7_TEX_INST_SAMPLE_C_L) { + src->add(","); if(texOpcode == GPU7_TEX_INST_SAMPLE_LB) - src->addFmt("{}", (float)texInstruction->textureFetch.lodBias / 16.0f); + src->add(_FormatFloatAsGLSLConstant((float)texInstruction->textureFetch.lodBias / 16.0f)); else - { - src->add(","); _emitTEXSampleCoordInputComponent(shaderContext, texInstruction, 3, LATTE_DECOMPILER_DTYPE_FLOAT); - } } else if( texOpcode == GPU7_TEX_INST_SAMPLE_LZ || texOpcode == GPU7_TEX_INST_SAMPLE_C_LZ ) { From f39a5e757b1d82c509c12ba67e88916cf7341573 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Mon, 15 Jan 2024 15:14:42 +0100 Subject: [PATCH 002/299] Add "Open MLC folder" option Also updated Patron supporter list --- src/gui/MainWindow.cpp | 14 ++++++++++---- src/gui/MainWindow.h | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 92594d00..dc9ff0a8 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -75,6 +75,7 @@ enum MAINFRAME_MENU_ID_FILE_LOAD = 20100, MAINFRAME_MENU_ID_FILE_INSTALL_UPDATE, MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER, + MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER, MAINFRAME_MENU_ID_FILE_EXIT, MAINFRAME_MENU_ID_FILE_END_EMULATION, MAINFRAME_MENU_ID_FILE_RECENT_0, @@ -166,7 +167,8 @@ EVT_MOVE(MainWindow::OnMove) // file menu EVT_MENU(MAINFRAME_MENU_ID_FILE_LOAD, MainWindow::OnFileMenu) EVT_MENU(MAINFRAME_MENU_ID_FILE_INSTALL_UPDATE, MainWindow::OnInstallUpdate) -EVT_MENU(MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER, MainWindow::OnOpenCemuFolder) +EVT_MENU(MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER, MainWindow::OnOpenFolder) +EVT_MENU(MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER, MainWindow::OnOpenFolder) EVT_MENU(MAINFRAME_MENU_ID_FILE_EXIT, MainWindow::OnFileExit) EVT_MENU(MAINFRAME_MENU_ID_FILE_END_EMULATION, MainWindow::OnFileMenu) EVT_MENU_RANGE(MAINFRAME_MENU_ID_FILE_RECENT_0 + 0, MAINFRAME_MENU_ID_FILE_RECENT_LAST, MainWindow::OnFileMenu) @@ -684,9 +686,12 @@ void MainWindow::OnFileMenu(wxCommandEvent& event) } } -void MainWindow::OnOpenCemuFolder(wxCommandEvent& event) +void MainWindow::OnOpenFolder(wxCommandEvent& event) { - wxLaunchDefaultApplication(wxHelper::FromPath(ActiveSettings::GetUserDataPath())); + if(event.GetId() == MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER) + wxLaunchDefaultApplication(wxHelper::FromPath(ActiveSettings::GetUserDataPath())); + else if(event.GetId() == MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER) + wxLaunchDefaultApplication(wxHelper::FromPath(ActiveSettings::GetMlcPath())); } void MainWindow::OnInstallUpdate(wxCommandEvent& event) @@ -2015,7 +2020,7 @@ public: , "Faris Leonhart", "MahvZero", "PlaguedGuardian", "Stuffie", "CaptainLester", "Qtech", "Zaurexus", "Leonidas", "Artifesto" , "Alca259", "SirWestofAsh", "Loli Co.", "The Technical Revolutionary", "MegaYama", "mitori", "Seymordius", "Adrian Josh Cruz", "Manuel Hoenings", "Just A Jabb" , "pgantonio", "CannonXIII", "Lonewolf00708", "AlexsDesign.com", "NoskLo", "MrSirHaku", "xElite_V AKA William H. Johnson", "Zalnor", "Pig", "James \"SE4LS\"", "DairyOrange", "Horoko Lawrence", "bloodmc", "Officer Jenny", "Quasar", "Postposterous", "Jake Jackson", "Kaydax", "CthePredatorG" - , "Hengi", "Pyrochaser"}; + , "Hengi", "Pyrochaser", "luma.x3"}; wxString nameListLeft, nameListRight; for (size_t i = 0; i < patreonSupporterNames.size(); i++) @@ -2107,6 +2112,7 @@ void MainWindow::RecreateMenu() } m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER, _("&Open Cemu folder")); + m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER, _("&Open MLC folder")); m_fileMenu->AppendSeparator(); m_exitMenuItem = m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_EXIT, _("&Exit")); diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index 07189b52..25100b72 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -92,7 +92,7 @@ public: void OnMouseWheel(wxMouseEvent& event); void OnClose(wxCloseEvent& event); void OnFileMenu(wxCommandEvent& event); - void OnOpenCemuFolder(wxCommandEvent& event); + void OnOpenFolder(wxCommandEvent& event); void OnLaunchFromFile(wxLaunchGameEvent& event); void OnInstallUpdate(wxCommandEvent& event); void OnFileExit(wxCommandEvent& event); From f58b260cbd566f1f76506246b3b9cd247b1ca511 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Mon, 15 Jan 2024 16:31:59 +0100 Subject: [PATCH 003/299] Fix macos missing dylib file --- src/CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index de9a6600..b7711018 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -78,7 +78,7 @@ if (MACOS_BUNDLE) set(MACOSX_BUNDLE_BUNDLE_NAME "Cemu") set(MACOSX_BUNDLE_SHORT_VERSION_STRING ${CMAKE_PROJECT_VERSION}) set(MACOSX_BUNDLE_BUNDLE_VERSION ${CMAKE_PROJECT_VERSION}) - set(MACOSX_BUNDLE_COPYRIGHT "Copyright © 2023 Cemu Project") + set(MACOSX_BUNDLE_COPYRIGHT "Copyright © 2024 Cemu Project") set(MACOSX_BUNDLE_CATEGORY "public.app-category.games") set(MACOSX_MINIMUM_SYSTEM_VERSION "12.0") @@ -100,6 +100,9 @@ if (MACOS_BUNDLE) add_custom_command (TARGET CemuBin POST_BUILD COMMAND bash -c "install_name_tool -add_rpath @executable_path/../Frameworks ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}") + + add_custom_command (TARGET CemuBin POST_BUILD + COMMAND bash -c "install_name_tool -change /usr/local/opt/libusb/lib/libusb-1.0.0.dylib @executable_path/../Frameworks/libusb-1.0.0.dylib ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}") endif() set_target_properties(CemuBin PROPERTIES From 7e778042ee0aa053f6daa6a0c23f7ff0c74b5f6e Mon Sep 17 00:00:00 2001 From: Live session user Date: Mon, 15 Jan 2024 17:46:56 -0800 Subject: [PATCH 004/299] Fix macos missing dylib file --- CMakeLists.txt | 2 + dependencies/vcpkg_overlay_ports_mac/.gitkeep | 0 .../libusb/portfile.cmake | 71 +++++++++++++++++++ .../vcpkg_overlay_ports_mac/libusb/usage | 5 ++ .../vcpkg_overlay_ports_mac/libusb/vcpkg.json | 8 +++ src/CMakeLists.txt | 11 ++- 6 files changed, 90 insertions(+), 7 deletions(-) create mode 100644 dependencies/vcpkg_overlay_ports_mac/.gitkeep create mode 100644 dependencies/vcpkg_overlay_ports_mac/libusb/portfile.cmake create mode 100644 dependencies/vcpkg_overlay_ports_mac/libusb/usage create mode 100644 dependencies/vcpkg_overlay_ports_mac/libusb/vcpkg.json diff --git a/CMakeLists.txt b/CMakeLists.txt index c988508c..ec6abedc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,8 @@ endif() if (ENABLE_VCPKG) if(UNIX AND NOT APPLE) set(VCPKG_OVERLAY_PORTS "${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports_linux") + elseif(APPLE) + set(VCPKG_OVERLAY_PORTS "${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports_mac") else() set(VCPKG_OVERLAY_PORTS "${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports") endif() diff --git a/dependencies/vcpkg_overlay_ports_mac/.gitkeep b/dependencies/vcpkg_overlay_ports_mac/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/dependencies/vcpkg_overlay_ports_mac/libusb/portfile.cmake b/dependencies/vcpkg_overlay_ports_mac/libusb/portfile.cmake new file mode 100644 index 00000000..7b76bba0 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_mac/libusb/portfile.cmake @@ -0,0 +1,71 @@ +set(VCPKG_LIBRARY_LINKAGE dynamic) + +if(VCPKG_TARGET_IS_LINUX) + message("${PORT} currently requires the following tools and libraries from the system package manager:\n autoreconf\n libudev\n\nThese can be installed on Ubuntu systems via apt-get install autoconf libudev-dev") +endif() + +set(VERSION 1.0.26) +vcpkg_from_github( + OUT_SOURCE_PATH SOURCE_PATH + REPO libusb/libusb + REF fcf0c710ef5911ae37fbbf1b39d48a89f6f14e8a # v1.0.26.11791 2023-03-12 + SHA512 0aa6439f7988487adf2a3bff473fec80b5c722a47f117a60696d2aa25c87cc3f20fb6aaca7c66e49be25db6a35eb0bb5f71ed7b211d1b8ee064c5d7f1b985c73 + HEAD_REF master +) + +if(VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_MINGW) + + if(VCPKG_LIBRARY_LINKAGE STREQUAL "dynamic") + set(LIBUSB_PROJECT_TYPE dll) + else() + set(LIBUSB_PROJECT_TYPE static) + endif() + + # The README.md file in the archive is a symlink to README + # which causes issues with the windows MSBUILD process + file(REMOVE "${SOURCE_PATH}/README.md") + + vcpkg_msbuild_install( + SOURCE_PATH "${SOURCE_PATH}" + PROJECT_SUBPATH msvc/libusb_${LIBUSB_PROJECT_TYPE}.vcxproj + ) + + file(INSTALL "${SOURCE_PATH}/libusb/libusb.h" DESTINATION "${CURRENT_PACKAGES_DIR}/include/libusb-1.0") + set(prefix "") + set(exec_prefix [[${prefix}]]) + set(libdir [[${prefix}/lib]]) + set(includedir [[${prefix}/include]]) + configure_file("${SOURCE_PATH}/libusb-1.0.pc.in" "${CURRENT_PACKAGES_DIR}/lib/pkgconfig/libusb-1.0.pc" @ONLY) + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/libusb-1.0.pc" " -lusb-1.0" " -llibusb-1.0") + if(NOT VCPKG_BUILD_TYPE) + set(includedir [[${prefix}/../include]]) + configure_file("${SOURCE_PATH}/libusb-1.0.pc.in" "${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/libusb-1.0.pc" @ONLY) + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/libusb-1.0.pc" " -lusb-1.0" " -llibusb-1.0") + endif() +else() + vcpkg_list(SET MAKE_OPTIONS) + vcpkg_list(SET LIBUSB_LINK_LIBRARIES) + if(VCPKG_TARGET_IS_EMSCRIPTEN) + vcpkg_list(APPEND MAKE_OPTIONS BUILD_TRIPLET --host=wasm32) + endif() + if("udev" IN_LIST FEATURES) + vcpkg_list(APPEND MAKE_OPTIONS "--enable-udev") + vcpkg_list(APPEND LIBUSB_LINK_LIBRARIES udev) + else() + vcpkg_list(APPEND MAKE_OPTIONS "--disable-udev") + endif() + vcpkg_configure_make( + SOURCE_PATH "${SOURCE_PATH}" + AUTOCONFIG + OPTIONS + ${MAKE_OPTIONS} + "--enable-examples-build=no" + "--enable-tests-build=no" + ) + vcpkg_install_make() +endif() + +vcpkg_fixup_pkgconfig() + +file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") +vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/COPYING") diff --git a/dependencies/vcpkg_overlay_ports_mac/libusb/usage b/dependencies/vcpkg_overlay_ports_mac/libusb/usage new file mode 100644 index 00000000..87e6e860 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_mac/libusb/usage @@ -0,0 +1,5 @@ +libusb can be imported via CMake FindPkgConfig module: + find_package(PkgConfig REQUIRED) + pkg_check_modules(libusb REQUIRED IMPORTED_TARGET libusb-1.0) + + target_link_libraries(main PRIVATE PkgConfig::libusb) diff --git a/dependencies/vcpkg_overlay_ports_mac/libusb/vcpkg.json b/dependencies/vcpkg_overlay_ports_mac/libusb/vcpkg.json new file mode 100644 index 00000000..efc70f3d --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_mac/libusb/vcpkg.json @@ -0,0 +1,8 @@ +{ + "name": "libusb", + "version": "1.0.26.11791", + "port-version": 7, + "description": "a cross-platform library to access USB devices", + "homepage": "https://github.com/libusb/libusb", + "license": "LGPL-2.1-or-later" +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b7711018..7442e37c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -96,13 +96,10 @@ if (MACOS_BUNDLE) endforeach(folder) add_custom_command (TARGET CemuBin POST_BUILD - COMMAND ${CMAKE_COMMAND} ARGS -E copy "/usr/local/lib/libMoltenVK.dylib" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libMoltenVK.dylib") - - add_custom_command (TARGET CemuBin POST_BUILD - COMMAND bash -c "install_name_tool -add_rpath @executable_path/../Frameworks ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}") - - add_custom_command (TARGET CemuBin POST_BUILD - COMMAND bash -c "install_name_tool -change /usr/local/opt/libusb/lib/libusb-1.0.0.dylib @executable_path/../Frameworks/libusb-1.0.0.dylib ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}") + COMMAND ${CMAKE_COMMAND} ARGS -E copy "/usr/local/lib/libMoltenVK.dylib" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libMoltenVK.dylib" + COMMAND ${CMAKE_COMMAND} ARGS -E copy "${CMAKE_BINARY_DIR}/vcpkg_installed/x64-osx/lib/libusb-1.0.0.dylib" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libusb-1.0.0.dylib" + COMMAND bash -c "install_name_tool -add_rpath @executable_path/../Frameworks ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}" + COMMAND bash -c "install_name_tool -change /usr/local/opt/libusb/lib/libusb-1.0.0.dylib @executable_path/../Frameworks/libusb-1.0.0.dylib ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}") endif() set_target_properties(CemuBin PROPERTIES From f899ab7c34035bc3746112b6459bd15a4a181dd4 Mon Sep 17 00:00:00 2001 From: Colin Kinloch Date: Wed, 17 Jan 2024 01:09:56 +0000 Subject: [PATCH 005/299] Vulkan: Check for 0 size before wayland resize Fixes "Launching games directly with the --title-id argument doesn't work in Wayland" (#999) --- src/gui/canvas/VulkanCanvas.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/gui/canvas/VulkanCanvas.cpp b/src/gui/canvas/VulkanCanvas.cpp index eb56b3c4..8b0c8506 100644 --- a/src/gui/canvas/VulkanCanvas.cpp +++ b/src/gui/canvas/VulkanCanvas.cpp @@ -66,6 +66,10 @@ void VulkanCanvas::OnPaint(wxPaintEvent& event) void VulkanCanvas::OnResize(wxSizeEvent& event) { + const wxSize size = GetSize(); + if (size.GetWidth() == 0 || size.GetHeight() == 0) + return; + #if BOOST_OS_LINUX && HAS_WAYLAND if(m_subsurface) { @@ -73,9 +77,6 @@ void VulkanCanvas::OnResize(wxSizeEvent& event) m_subsurface->setSize(sRect.GetX(), sRect.GetY(), sRect.GetWidth(), sRect.GetHeight()); } #endif - const wxSize size = GetSize(); - if (size.GetWidth() == 0 || size.GetHeight() == 0) - return; const wxRect refreshRect(size); RefreshRect(refreshRect, false); From e53c63b828e856cb4bf11729cdf90fe12544ac9f Mon Sep 17 00:00:00 2001 From: Colin Kinloch Date: Wed, 17 Jan 2024 01:18:07 +0000 Subject: [PATCH 006/299] Flatpak: Create shortcuts that launch flatpak --- src/gui/components/wxGameList.cpp | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/gui/components/wxGameList.cpp b/src/gui/components/wxGameList.cpp index 2c78ea3c..5ceaf71f 100644 --- a/src/gui/components/wxGameList.cpp +++ b/src/gui/components/wxGameList.cpp @@ -1235,6 +1235,7 @@ void wxGameList::CreateShortcut(GameInfo2& gameInfo) { const auto title_id = gameInfo.GetBaseTitleId(); const auto title_name = gameInfo.GetTitleName(); auto exe_path = ActiveSettings::GetExecutablePath(); + const char *flatpak_id = getenv("FLATPAK_ID"); // GetExecutablePath returns the AppImage's temporary mount location, instead of its actual path wxString appimage_path; @@ -1292,22 +1293,31 @@ void wxGameList::CreateShortcut(GameInfo2& gameInfo) { } } } + + std::string desktop_exec_entry; + if (flatpak_id) + desktop_exec_entry = fmt::format("/usr/bin/flatpak run {0} --title-id {1:016x}", flatpak_id, title_id); + else + desktop_exec_entry = fmt::format("{0:?} --title-id {1:016x}", _pathToUtf8(exe_path), title_id); + // 'Icon' accepts spaces in file name, does not accept quoted file paths // 'Exec' does not accept non-escaped spaces, and can accept quoted file paths - const auto desktop_entry_string = + auto desktop_entry_string = fmt::format("[Desktop Entry]\n" "Name={0}\n" "Comment=Play {0} on Cemu\n" - "Exec={1:?} --title-id {2:016x}\n" - "Icon={3}\n" + "Exec={1}\n" + "Icon={2}\n" "Terminal=false\n" "Type=Application\n" - "Categories=Game;", + "Categories=Game;\n", title_name, - _pathToUtf8(exe_path), - title_id, + desktop_exec_entry, _pathToUtf8(icon_path.value_or(""))); + if (flatpak_id) + desktop_entry_string += fmt::format("X-Flatpak={}\n", flatpak_id); + std::ofstream output_stream(output_path); if (!output_stream.good()) { From 72aacbdcecc064ea7c3b158c433e4803496ac296 Mon Sep 17 00:00:00 2001 From: Mike Lothian Date: Fri, 19 Jan 2024 01:03:57 +0000 Subject: [PATCH 007/299] Vulkan: Don't use glslang internal headers Signed-off-by: Mike Lothian --- src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp | 3 +-- src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp index e9936c43..72a1be4c 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp @@ -3,7 +3,6 @@ #include "Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h" #include "Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.h" -#include #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" #include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" @@ -91,4 +90,4 @@ PipelineInfo::~PipelineInfo() // remove from cache VulkanRenderer::GetInstance()->unregisterGraphicsPipeline(this); -} \ No newline at end of file +} diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 44214606..616f57e2 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -26,7 +26,7 @@ #include "Cafe/HW/Latte/Core/LatteTiming.h" // vsync control -#include +#include #include From 18679af4ec641a4c59753d54751dcab257777eef Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Fri, 19 Jan 2024 14:07:17 +0000 Subject: [PATCH 008/299] Ignore Wii U pro controller --- src/input/api/Wiimote/hidapi/HidapiWiimote.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/input/api/Wiimote/hidapi/HidapiWiimote.cpp b/src/input/api/Wiimote/hidapi/HidapiWiimote.cpp index a5701f56..db185675 100644 --- a/src/input/api/Wiimote/hidapi/HidapiWiimote.cpp +++ b/src/input/api/Wiimote/hidapi/HidapiWiimote.cpp @@ -1,9 +1,11 @@ #include "HidapiWiimote.h" +#include static constexpr uint16 WIIMOTE_VENDOR_ID = 0x057e; static constexpr uint16 WIIMOTE_PRODUCT_ID = 0x0306; static constexpr uint16 WIIMOTE_MP_PRODUCT_ID = 0x0330; static constexpr uint16 WIIMOTE_MAX_INPUT_REPORT_LENGTH = 22; +static constexpr auto PRO_CONTROLLER_NAME = L"Nintendo RVL-CNT-01-UC"; HidapiWiimote::HidapiWiimote(hid_device* dev, std::string_view path) : m_handle(dev), m_path(path) { @@ -30,6 +32,8 @@ std::vector HidapiWiimote::get_devices() { for (auto it = device_enumeration; it != nullptr; it = it->next){ if (it->product_id != WIIMOTE_PRODUCT_ID && it->product_id != WIIMOTE_MP_PRODUCT_ID) continue; + if (std::wcscmp(it->product_string, PRO_CONTROLLER_NAME) == 0) + continue; auto dev = hid_open_path(it->path); if (!dev){ cemuLog_logDebug(LogType::Force, "Unable to open Wiimote device at {}: {}", it->path, boost::nowide::narrow(hid_error(nullptr))); From 4e4ac0de51b82c455aec71060c109f9e5a1888d9 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Fri, 19 Jan 2024 23:32:24 +0100 Subject: [PATCH 009/299] CI: For the Windows build use as many cores as available --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d23faa31..3c01ba7a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -183,7 +183,7 @@ jobs: - name: "Build Cemu" run: | cd build - cmake --build . --config ${{ env.BUILD_MODE }} -j 2 + cmake --build . --config ${{ env.BUILD_MODE }} - name: Prepare artifact if: ${{ inputs.deploymode == 'release' }} From ca01e923bf03573d5023feb0f4464ce015910ea6 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 20 Jan 2024 00:33:36 +0100 Subject: [PATCH 010/299] Update issue templates --- .../bug-report-feature-request.md | 34 --------- .github/ISSUE_TEMPLATE/config.yml | 2 +- .../ISSUE_TEMPLATE/emulation_bug_report.yaml | 69 +++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_report.yaml | 28 ++++++++ 4 files changed, 98 insertions(+), 35 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug-report-feature-request.md create mode 100644 .github/ISSUE_TEMPLATE/emulation_bug_report.yaml create mode 100644 .github/ISSUE_TEMPLATE/feature_report.yaml diff --git a/.github/ISSUE_TEMPLATE/bug-report-feature-request.md b/.github/ISSUE_TEMPLATE/bug-report-feature-request.md deleted file mode 100644 index 61bd5d06..00000000 --- a/.github/ISSUE_TEMPLATE/bug-report-feature-request.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -name: Bug Report / Feature Request -about: Tech support does not belong here. You should only file an issue here if you think you have experienced an actual bug with Cemu or you are requesting a feature you believe would make Cemu better. -title: '' -labels: '' -assignees: '' - ---- - - diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index d33d87ed..d71e22d0 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -2,4 +2,4 @@ blank_issues_enabled: false contact_links: - name: Cemu Discord url: https://discord.com/invite/5psYsup - about: If you are experiencing an issue with Cemu, and you need tech support, or if you have a general question, try asking in the official Cemu Discord linked here. Piracy is not allowed. + about: If you need technical support with Cemu or have other questions the best place to ask is on the official Cemu Discord linked here diff --git a/.github/ISSUE_TEMPLATE/emulation_bug_report.yaml b/.github/ISSUE_TEMPLATE/emulation_bug_report.yaml new file mode 100644 index 00000000..75928607 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/emulation_bug_report.yaml @@ -0,0 +1,69 @@ +# Docs - https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema +name: Bug Report +description: Report an issue with Cemu emulator +title: "Enter a title for the bug report here" +labels: bug +body: + - type: markdown + id: md_readme + attributes: + value: | + ## Important: Read First + + If you discovered a bug you can report it here. Please make sure of the following first: + - That you are using the latest version of Cemu + - Only report something if you are sure it's a bug and not any technical issue on your end. For troubleshooting help see the [links page](https://github.com/cemu-project/Cemu#links) + - Problems specific to a single game should be reported on the [compatibility wiki](https://wiki.cemu.info/wiki/Main_Page) instead + - Verify that your problem isn't already mentioned on the [issue tracker](https://github.com/cemu-project/Cemu/issues) + + Additionally, be aware that graphic packs can also causes issues. There is a separate issue tracker for graphic pack bugs over at the [graphic pack repository](https://github.com/cemu-project/cemu_graphic_packs) + - type: textarea + id: current_behavior + attributes: + label: Current Behavior + description: "What the bug is, in a brief description" + validations: + required: true + - type: textarea + id: expected_behavior + attributes: + label: Expected Behavior + description: "What did you expect to happen?" + validations: + required: true + + - type: textarea + id: steps_to_reproduce + attributes: + label: Steps to Reproduce + description: "How to reproduce the issue" + validations: + required: true + - type: textarea + id: sys_info + attributes: + label: System Info (Optional) + description: "Your PC specifications. Usually only the operating system and graphics card is important. But feel free to add more info." + placeholder: | + Info + OS: Windows 10 + GPU: NVIDIA GeForce RTX 4090 + value: | + OS: + GPU: + - type: textarea + id: emulation_settings + attributes: + label: Emulation Settings (Optional) + description: | + Any non-default settings. You can leave this empty if you didn't change anything other than input settings. + validations: + required: false + - type: textarea + id: logs_files + attributes: + label: "Logs (Optional)" + description: | + "Attach `log.txt` from your Cemu folder (*File > Open Cemu folder*)". + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/feature_report.yaml b/.github/ISSUE_TEMPLATE/feature_report.yaml new file mode 100644 index 00000000..a5d8705c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_report.yaml @@ -0,0 +1,28 @@ +# Docs - https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema +name: Feature suggestion +description: Suggest a new feature +title: "Enter a title for the suggestion here" +labels: feature request +body: + - type: markdown + id: md_readme + attributes: + value: | + ## Important: Read First + + While we appreciate suggestions, it is important to note that we are a very small team and there are already many more ideas than we could ever implement in the near future. Therefore, please only suggest something if you believe it is a great addition and the idea is reasonably unique. + + *Avoid* to create suggestions for: + - Overly obvious features ("Game xyz does not work and should be fixed", "Wiimote support should be improved", "You should add an Android port", "Copy feature xyz from another emulator", "A button to pause/stop emulation") + - Niche features which are only interesting to a tiny percentage of users + - Large scale features ("Add a Metal backend for MacOS", "Add ARM support", "Add savestates") + + Note that this doesn't mean we aren't interested in these ideas, but rather we likely have them planned anyway and it's mostly up to finding the time to implement them. + If you believe your idea is worthwhile even if it doesn't meet all the criteria above, you can still try suggesting it but we might close it. + - type: textarea + id: idea_suggestion + attributes: + label: Your suggestion + description: "Describe what your suggestion is in as much detail as possible" + validations: + required: true From 81acd80a97faa3905d27ad170f16244e17581d2d Mon Sep 17 00:00:00 2001 From: Squall Leonhart Date: Sun, 18 Feb 2024 15:51:00 +1100 Subject: [PATCH 011/299] Cubeb: Add a default device to the selection (#1017) --- src/audio/CubebAPI.cpp | 13 +++++++++---- src/audio/CubebInputAPI.cpp | 13 +++++++++---- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/audio/CubebAPI.cpp b/src/audio/CubebAPI.cpp index 09e45011..2b4aec41 100644 --- a/src/audio/CubebAPI.cpp +++ b/src/audio/CubebAPI.cpp @@ -188,15 +188,20 @@ std::vector CubebAPI::GetDevices() return {}; std::vector result; - result.reserve(devices.count); + result.reserve(devices.count + 1); // Reserve space for the default device + + // Add the default device to the list + auto defaultDevice = std::make_shared(nullptr, "default", L"Default Device"); + result.emplace_back(defaultDevice); + for (size_t i = 0; i < devices.count; ++i) { - //const auto& device = devices.device[i]; + // const auto& device = devices.device[i]; if (devices.device[i].state == CUBEB_DEVICE_STATE_ENABLED) { auto device = std::make_shared(devices.device[i].devid, devices.device[i].device_id, - boost::nowide::widen( - devices.device[i].friendly_name)); + boost::nowide::widen( + devices.device[i].friendly_name)); result.emplace_back(device); } } diff --git a/src/audio/CubebInputAPI.cpp b/src/audio/CubebInputAPI.cpp index de030fdc..c0fa73f4 100644 --- a/src/audio/CubebInputAPI.cpp +++ b/src/audio/CubebInputAPI.cpp @@ -180,15 +180,20 @@ std::vector CubebInputAPI::GetDevices() return {}; std::vector result; - result.reserve(devices.count); + result.reserve(devices.count + 1); // Reserve space for the default device + + // Add the default device to the list + auto defaultDevice = std::make_shared(nullptr, "default", L"Default Device"); + result.emplace_back(defaultDevice); + for (size_t i = 0; i < devices.count; ++i) { - //const auto& device = devices.device[i]; + // const auto& device = devices.device[i]; if (devices.device[i].state == CUBEB_DEVICE_STATE_ENABLED) { auto device = std::make_shared(devices.device[i].devid, devices.device[i].device_id, - boost::nowide::widen( - devices.device[i].friendly_name)); + boost::nowide::widen( + devices.device[i].friendly_name)); result.emplace_back(device); } } From 6a08d04af9c22d2b6ec432b9ac48a299abcfb9f8 Mon Sep 17 00:00:00 2001 From: Squall Leonhart Date: Sun, 18 Feb 2024 15:52:11 +1100 Subject: [PATCH 012/299] UI: Make Alt+F4/Ctrl+Q more reliable (#1035) --- src/gui/MainWindow.cpp | 14 ++++++++++++++ src/gui/MainWindow.h | 1 + 2 files changed, 15 insertions(+) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index dc9ff0a8..d271ca3a 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -1485,6 +1485,19 @@ void MainWindow::OnKeyUp(wxKeyEvent& event) g_window_info.has_screenshot_request = true; // async screenshot request } +void MainWindow::OnKeyDown(wxKeyEvent& event) +{ + if ((event.AltDown() && event.GetKeyCode() == WXK_F4) || + (event.CmdDown() && event.GetKeyCode() == 'Q')) + { + Close(true); + } + else + { + event.Skip(); + } +} + void MainWindow::OnChar(wxKeyEvent& event) { if (swkbd_hasKeyboardInputHook()) @@ -1590,6 +1603,7 @@ void MainWindow::CreateCanvas() // key events m_render_canvas->Bind(wxEVT_KEY_UP, &MainWindow::OnKeyUp, this); + m_render_canvas->Bind(wxEVT_KEY_DOWN, &MainWindow::OnKeyDown, this); m_render_canvas->Bind(wxEVT_CHAR, &MainWindow::OnChar, this); m_render_canvas->SetDropTarget(new wxAmiiboDropTarget(this)); diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index 25100b72..88d2a1d3 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -124,6 +124,7 @@ public: void OnSetWindowTitle(wxCommandEvent& event); void OnKeyUp(wxKeyEvent& event); + void OnKeyDown(wxKeyEvent& event); void OnChar(wxKeyEvent& event); void OnToolsInput(wxCommandEvent& event); From 9bbb7c8b97ff6e5a080984c818b1e66b0f2ce609 Mon Sep 17 00:00:00 2001 From: Steveice10 <1269164+Steveice10@users.noreply.github.com> Date: Sat, 17 Feb 2024 20:54:41 -0800 Subject: [PATCH 013/299] Add support for portable directory without build flag (#1071) --- .github/workflows/build.yml | 3 +- CMakeLists.txt | 5 ---- src/gui/CemuApp.cpp | 59 ++++++++++++++++++++++--------------- 3 files changed, 36 insertions(+), 31 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3c01ba7a..00aac0fe 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -75,7 +75,7 @@ jobs: - name: "cmake" run: | - cmake -S . -B build ${{ env.BUILD_FLAGS }} -DCMAKE_BUILD_TYPE=${{ env.BUILD_MODE }} -DPORTABLE=OFF -DCMAKE_C_COMPILER=/usr/bin/clang-15 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-15 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja + cmake -S . -B build ${{ env.BUILD_FLAGS }} -DCMAKE_BUILD_TYPE=${{ env.BUILD_MODE }} -DCMAKE_C_COMPILER=/usr/bin/clang-15 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-15 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja - name: "Build Cemu" run: | @@ -258,7 +258,6 @@ jobs: cd build cmake .. ${{ env.BUILD_FLAGS }} \ -DCMAKE_BUILD_TYPE=${{ env.BUILD_MODE }} \ - -DPORTABLE=OFF \ -DMACOS_BUNDLE=ON \ -DCMAKE_C_COMPILER=/usr/local/opt/llvm@15/bin/clang \ -DCMAKE_CXX_COMPILER=/usr/local/opt/llvm@15/bin/clang++ \ diff --git a/CMakeLists.txt b/CMakeLists.txt index ec6abedc..6b5f3881 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,6 @@ cmake_minimum_required(VERSION 3.21.1) option(ENABLE_VCPKG "Enable the vcpkg package manager" ON) -option(PORTABLE "All data created and maintained by Cemu will be in the directory where the executable file is located" ON) option(MACOS_BUNDLE "The executable when built on macOS will be created as an application bundle" OFF) set(EXPERIMENTAL_VERSION "" CACHE STRING "") # used by CI script to set experimental version @@ -45,10 +44,6 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) add_compile_definitions($<$:CEMU_DEBUG_ASSERT>) # if build type is debug, set CEMU_DEBUG_ASSERT -if(PORTABLE) - add_compile_definitions(PORTABLE) -endif() - set_property(GLOBAL PROPERTY USE_FOLDERS ON) # enable link time optimization for release builds diff --git a/src/gui/CemuApp.cpp b/src/gui/CemuApp.cpp index 4acc1cf5..fde4bcc0 100644 --- a/src/gui/CemuApp.cpp +++ b/src/gui/CemuApp.cpp @@ -59,33 +59,44 @@ bool CemuApp::OnInit() fs::path user_data_path, config_path, cache_path, data_path; auto standardPaths = wxStandardPaths::Get(); fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath())); -#ifdef PORTABLE -#if MACOS_BUNDLE - exePath = exePath.parent_path().parent_path().parent_path(); + + // Try a portable path first, if it exists. + user_data_path = config_path = cache_path = data_path = exePath.parent_path() / "portable"; +#if BOOST_OS_MACOS + // If run from an app bundle, use its parent directory. + fs::path appPath = exePath.parent_path().parent_path().parent_path(); + if (appPath.extension() == ".app") + user_data_path = config_path = cache_path = data_path = appPath.parent_path() / "portable"; #endif - user_data_path = config_path = cache_path = data_path = exePath.parent_path(); -#else - SetAppName("Cemu"); - wxString appName=GetAppName(); - #if BOOST_OS_LINUX - standardPaths.SetFileLayout(wxStandardPaths::FileLayout::FileLayout_XDG); - auto getEnvDir = [&](const wxString& varName, const wxString& defaultValue) + + if (!fs::exists(user_data_path)) { - wxString dir; - if (!wxGetEnv(varName, &dir) || dir.empty()) - return defaultValue; - return dir; - }; - wxString homeDir=wxFileName::GetHomeDir(); - user_data_path = (getEnvDir(wxS("XDG_DATA_HOME"), homeDir + wxS("/.local/share")) + "/" + appName).ToStdString(); - config_path = (getEnvDir(wxS("XDG_CONFIG_HOME"), homeDir + wxS("/.config")) + "/" + appName).ToStdString(); - #else - user_data_path = config_path = standardPaths.GetUserDataDir().ToStdString(); - #endif - data_path = standardPaths.GetDataDir().ToStdString(); - cache_path = standardPaths.GetUserDir(wxStandardPaths::Dir::Dir_Cache).ToStdString(); - cache_path /= appName.ToStdString(); +#if BOOST_OS_WINDOWS + user_data_path = config_path = cache_path = data_path = exePath.parent_path(); +#else + SetAppName("Cemu"); + wxString appName=GetAppName(); +#if BOOST_OS_LINUX + standardPaths.SetFileLayout(wxStandardPaths::FileLayout::FileLayout_XDG); + auto getEnvDir = [&](const wxString& varName, const wxString& defaultValue) + { + wxString dir; + if (!wxGetEnv(varName, &dir) || dir.empty()) + return defaultValue; + return dir; + }; + wxString homeDir=wxFileName::GetHomeDir(); + user_data_path = (getEnvDir(wxS("XDG_DATA_HOME"), homeDir + wxS("/.local/share")) + "/" + appName).ToStdString(); + config_path = (getEnvDir(wxS("XDG_CONFIG_HOME"), homeDir + wxS("/.config")) + "/" + appName).ToStdString(); +#else + user_data_path = config_path = standardPaths.GetUserDataDir().ToStdString(); #endif + data_path = standardPaths.GetDataDir().ToStdString(); + cache_path = standardPaths.GetUserDir(wxStandardPaths::Dir::Dir_Cache).ToStdString(); + cache_path /= appName.ToStdString(); +#endif + } + auto failed_write_access = ActiveSettings::LoadOnce(exePath, user_data_path, config_path, cache_path, data_path); for (auto&& path : failed_write_access) wxMessageBox(formatWxString(_("Cemu can't write to {}!"), wxString::FromUTF8(_pathToUtf8(path))), From ed01eaf5f949847d8be16d271abdeafe901e3971 Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Sun, 18 Feb 2024 04:56:36 +0000 Subject: [PATCH 014/299] Gamelist: Add right-click actions for copying title ID, name, and icon (#1089) --- src/gui/components/wxGameList.cpp | 51 +++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/src/gui/components/wxGameList.cpp b/src/gui/components/wxGameList.cpp index 5ceaf71f..88934cd8 100644 --- a/src/gui/components/wxGameList.cpp +++ b/src/gui/components/wxGameList.cpp @@ -17,6 +17,8 @@ #include #include #include +#include + #include #include @@ -546,7 +548,12 @@ enum ContextMenuEntries kContextMenuStyleList, kContextMenuStyleIcon, kContextMenuStyleIconSmall, - kContextMenuCreateShortcut + + kContextMenuCreateShortcut, + + kContextMenuCopyTitleName, + kContextMenuCopyTitleId, + kContextMenuCopyTitleImage }; void wxGameList::OnContextMenu(wxContextMenuEvent& event) { @@ -591,6 +598,10 @@ void wxGameList::OnContextMenu(wxContextMenuEvent& event) #if BOOST_OS_LINUX || BOOST_OS_WINDOWS menu.Append(kContextMenuCreateShortcut, _("&Create shortcut")); #endif + menu.AppendSeparator(); + menu.Append(kContextMenuCopyTitleName, _("&Copy Title Name")); + menu.Append(kContextMenuCopyTitleId, _("&Copy Title ID")); + menu.Append(kContextMenuCopyTitleImage, _("&Copy Title Image")); menu.AppendSeparator(); } } @@ -711,10 +722,44 @@ void wxGameList::OnContextMenuSelected(wxCommandEvent& event) break; } case kContextMenuCreateShortcut: + { #if BOOST_OS_LINUX || BOOST_OS_WINDOWS - CreateShortcut(gameInfo); + CreateShortcut(gameInfo); #endif break; + } + case kContextMenuCopyTitleName: + { + if (wxTheClipboard->Open()) + { + wxTheClipboard->SetData(new wxTextDataObject(gameInfo.GetTitleName())); + wxTheClipboard->Close(); + } + break; + } + case kContextMenuCopyTitleId: + { + if (wxTheClipboard->Open()) + { + wxTheClipboard->SetData(new wxTextDataObject(fmt::format("{:016x}", gameInfo.GetBaseTitleId()))); + wxTheClipboard->Close(); + } + break; + } + case kContextMenuCopyTitleImage: + { + if (wxTheClipboard->Open()) + { + int icon_large; + int icon_small; + if (!QueryIconForTitle(title_id, icon_large, icon_small)) + break; + auto icon = m_image_list->GetBitmap(icon_large); + wxTheClipboard->SetData(new wxBitmapDataObject(icon)); + wxTheClipboard->Close(); + } + break; + } } } } @@ -1042,7 +1087,7 @@ void wxGameList::OnGameEntryUpdatedByTitleId(wxTitleIdEvent& event) const auto region_text = fmt::format("{}", gameInfo.GetRegion()); SetItem(index, ColumnRegion, wxGetTranslation(region_text)); - SetItem(index, ColumnTitleID, fmt::format("{:016x}", titleId)); + SetItem(index, ColumnTitleID, fmt::format("{:016x}", baseTitleId)); } else if (m_style == Style::kIcons) { From 8d7fc98275f3eae1f2e105df678956b7540e65eb Mon Sep 17 00:00:00 2001 From: rawdatafeel <108900299+rawdatafeel@users.noreply.github.com> Date: Sat, 17 Feb 2024 23:59:00 -0500 Subject: [PATCH 015/299] Improve BUILD.md (#1093) --- BUILD.md | 153 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 114 insertions(+), 39 deletions(-) diff --git a/BUILD.md b/BUILD.md index e4993ca1..034000ac 100644 --- a/BUILD.md +++ b/BUILD.md @@ -1,4 +1,26 @@ -# Build instructions +# Build Instructions + +## Table of Contents + +- [Windows](#windows) +- [Linux](#linux) + - [Dependencies](#dependencies) + - [For Arch and derivatives:](#for-arch-and-derivatives) + - [For Fedora and derivatives:](#for-fedora-and-derivatives) + - [For Ubuntu and derivatives](#for-ubuntu-and-derivatives) + - [Build Cemu](#build-cemu) + - [CMake and Clang](#cmake-and-clang) + - [GCC](#gcc) + - [Debug Build](#debug-build) + - [Troubleshooting Steps](#troubleshooting-steps) + - [Compiling Errors](#compiling-errors) + - [Building Errors](#building-errors) +- [macOS](#macos) + - [On Apple Silicon Macs, Rosetta 2 and the x86_64 version of Homebrew must be used](#on-apple-silicon-macs-rosetta-2-and-the-x86_64-version-of-homebrew-must-be-used) + - [Installing brew](#installing-brew) + - [Installing Dependencies](#installing-dependencies) + - [Build Cemu using CMake and Clang](#build-cemu-using-cmake-and-clang) + - [Updating Cemu and source code](#updating-cemu-and-source-code) ## Windows @@ -19,17 +41,9 @@ Any other IDE should also work as long as it has CMake and MSVC support. CLion a ## Linux -To compile Cemu, a recent enough compiler and STL with C++20 support is required! clang-15 or higher is what we recommend. +To compile Cemu, a recent enough compiler and STL with C++20 support is required! Clang-15 or higher is what we recommend. -### Installing dependencies - -#### For Ubuntu and derivatives: -`sudo apt install -y cmake curl clang-15 freeglut3-dev git libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev nasm ninja-build` - -You may also need to install `libusb-1.0-0-dev` as a workaround for an issue with the vcpkg hidapi package. - -At step 3 while building, use: - `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang-15 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-15 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja` +### Dependencies #### For Arch and derivatives: `sudo pacman -S --needed base-devel clang cmake freeglut git glm gtk3 libgcrypt libpulse libsecret linux-headers llvm nasm ninja systemd unzip zip` @@ -37,39 +51,99 @@ At step 3 while building, use: #### For Fedora and derivatives: `sudo dnf install clang cmake cubeb-devel freeglut-devel git glm-devel gtk3-devel kernel-headers libgcrypt-devel libsecret-devel libtool libusb1-devel nasm ninja-build perl-core systemd-devel zlib-devel` -### Build Cemu using cmake and clang -1. `git clone --recursive https://github.com/cemu-project/Cemu` -2. `cd Cemu` -3. `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++ -G Ninja` -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`. +#### For Ubuntu and derivatives: +`sudo apt install -y cmake curl clang-15 freeglut3-dev git libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libtool nasm ninja-build` -#### Using GCC -While we build and test Cemu using clang, using GCC might work better with your distro (they should be fairly similar performance/issues wise and should only be considered if compilation is the issue). +You may also need to install `libusb-1.0-0-dev` as a workaround for an issue with the vcpkg hidapi package. -You can use GCC by doing the following: -- make sure you have g++ installed in your system - - installation for Ubuntu and derivatives: `sudo apt install g++` - - installation for Fedora and derivatives: `sudo dnf install gcc-c++` -- replace the step 3 with the following: -`cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/gcc -DCMAKE_CXX_COMPILER=/usr/bin/g++ -G Ninja` +At Step 3 in [Build Cemu using cmake and clang](#build-cemu-using-cmake-and-clang), use the following command instead: + `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang-15 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-15 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja` -#### Troubleshooting steps - - If step 3 gives you an error about not being able to find ninja, try appending `-DCMAKE_MAKE_PROGRAM=/usr/bin/ninja` to the command and running it again. - - If step 3 fails while compiling the boost-build dependency, it means you don't have a working/good standard library installation. Check the integrity of your system headers and making sure that C++ related packages are installed and intact. - - If step 3 gives a random error, read the `[package-name-and-platform]-out.log` and `[package-name-and-platform]-err.log` for the actual reason to see if you might be lacking the headers from a dependency. - - If step 3 is still failing or if you're not able to find the cause, please make an issue on our Github about it! - - If step 3 fails during rebuild after `git pull` with an error that mentions RPATH, add this to the end of step 3: `-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON` - - If step 4 gives you an error that contains something like `main.cpp.o: in function 'std::__cxx11::basic_string...`, you likely are experiencing a clang-14 issue. This can only be fixed by either lowering the clang version or using GCC, see below. - - If step 4 gives you a different error, you could report it to this repo or try using GCC. Just make sure your standard library and compilers are updated since Cemu uses a lot of modern features! - - If step 4 gives you undefined libdecor_xx, you are likely experiencing an issue with sdl2 package that comes with vcpkg. Delete sdl2 from vcpkg.json in source file and recompile. - - If step 4 gives you `fatal error: 'span' file not found`, then you're either missing `libstdc++` or are using a version that's too old. Install at least v10 with your package manager, eg `sudo apt install libstdc++-10-dev`. See #644. +### Build Cemu + +#### CMake and Clang + +``` +git clone --recursive https://github.com/cemu-project/Cemu +cd Cemu +cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++ -G Ninja +cmake --build build +``` + +#### GCC + +If you are building using GCC, make sure you have g++ installed: +- Installation for Arch and derivatives: `sudo pacman -S gcc` +- Installation for Fedora and derivatives: `sudo dnf install gcc-c++` +- Installation for Ubuntu and derivatives: `sudo apt install g++` + +``` +git clone --recursive https://github.com/cemu-project/Cemu +cd Cemu +cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/gcc -DCMAKE_CXX_COMPILER=/usr/bin/g++ -G Ninja +cmake --build build +``` + +#### Debug Build + +``` +git clone --recursive https://github.com/cemu-project/Cemu +cd Cemu +cmake -S . -B build -DCMAKE_BUILD_TYPE=debug -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++ -G Ninja +cmake --build build +``` + +If you are using GCC, replace `cmake -S . -B build -DCMAKE_BUILD_TYPE=debug -DCMAKE_C_COMPILER=/usr/bin/clang -DCMAKE_CXX_COMPILER=/usr/bin/clang++ -G Ninja` with `cmake -S . -B build -DCMAKE_BUILD_TYPE=debug -DCMAKE_C_COMPILER=/usr/bin/gcc -DCMAKE_CXX_COMPILER=/usr/bin/g++ -G Ninja` + +#### Troubleshooting Steps + +##### Compiling Errors + +This section refers to running `cmake -S...` (truncated). + +* `vcpkg install failed` + * Run the following in the root directory and try running the command again (don't forget to change directories afterwards): + * `cd dependencies/vcpkg && git fetch --unshallow` +* `Please ensure you're using the latest port files with git pull and vcpkg update.` + * Either: + * Update vcpkg by running by the following command: + * `git submodule update --remote dependencies/vcpkg` + * If you are sure vcpkg is up to date, check the following logs: + * `Cemu/dependencies/vcpkg/buildtrees/wxwidgets/config-x64-linux-out.log` + * `Cemu/dependencies/vcpkg/buildtrees/libsystemd/config-x64-linux-dbg-meson-log.txt.log` + * `Cemu/dependencies/vcpkg/buildtrees/libsystemd/config-x64-linux-dbg-out.log` +* Not able to find Ninja. + * Add the following and try running the command again: + * `-DCMAKE_MAKE_PROGRAM=/usr/bin/ninja` +* Compiling failed during the boost-build dependency. + * It means you don't have a working/good standard library installation. Check the integrity of your system headers and making sure that C++ related packages are installed and intact. +* Compiling failed during rebuild after `git pull` with an error that mentions RPATH + * Add the following and try running the command again: + * `-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON` +* If you are getting a random error, read the [package-name-and-platform]-out.log and [package-name-and-platform]-err.log for the actual reason to see if you might be lacking the headers from a dependency. + + +If you are getting a different error than any of the errors listed above, you may either open an issue in this repo or try using [GCC](#gcc). Make sure your standard library and compilers are updated since Cemu uses a lot of modern features! + + +##### Building Errors + +This section refers to running `cmake --build build`. + +* `main.cpp.o: in function 'std::__cxx11::basic_string...` + * You likely are experiencing a clang-14 issue. This can only be fixed by either lowering the clang version or using GCC, see [GCC](#gcc). +* `fatal error: 'span' file not found` + * You're either missing `libstdc++` or are using a version that's too old. Install at least v10 with your package manager, eg `sudo apt install libstdc++-10-dev`. See [#644](https://github.com/cemu-project/Cemu/issues/644). +* `undefined libdecor_xx` + * You are likely experiencing an issue with sdl2 package that comes with vcpkg. Delete sdl2 from vcpkg.json in source file and recompile. + +If you are getting a different error than any of the errors listed above, you may either open an issue in this repo or try using [GCC](#gcc). Make sure your standard library and compilers are updated since Cemu uses a lot of modern features! ## macOS -To compile Cemu, a recent enough compiler and STL with C++20 support is required! LLVM 13 and +To compile Cemu, a recent enough compiler and STL with C++20 support is required! LLVM 13 and below, built in LLVM, and Xcode LLVM don't support the C++20 feature set required. The OpenGL graphics -API isn't support on macOS, Vulkan must be used. Additionally Vulkan must be used through the +API isn't support on macOS, Vulkan must be used. Additionally Vulkan must be used through the Molten-VK compatibility layer ### On Apple Silicon Macs, Rosetta 2 and the x86_64 version of Homebrew must be used @@ -84,11 +158,11 @@ You can skip this section if you have an Intel Mac. Every time you compile, you 1. `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"` 2. `eval "$(/usr/local/Homebrew/bin/brew shellenv)"` # set x86_64 brew env -### Installing dependencies +### Installing Dependencies `brew install boost git cmake llvm ninja nasm molten-vk automake libtool` -### Build Cemu using cmake and clang +### Build Cemu using CMake and Clang 1. `git clone --recursive https://github.com/cemu-project/Cemu` 2. `cd Cemu` 3. `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/local/opt/llvm/bin/clang -DCMAKE_CXX_COMPILER=/usr/local/opt/llvm/bin/clang++ -G Ninja` @@ -104,3 +178,4 @@ You can skip this section if you have an Intel Mac. Every time you compile, you 2. Then, you can rebuild Cemu using the steps listed above, according to whether you use Linux or Windows. If CMake complains about Cemu already being compiled or another similar error, try deleting the `CMakeCache.txt` file inside the `build` folder and retry building. + From 3a02490a1f37c7f437b99a2ef459ab886d08d79f Mon Sep 17 00:00:00 2001 From: MoonlightWave-12 <123384363+MoonlightWave-12@users.noreply.github.com> Date: Sun, 18 Feb 2024 17:12:09 +0100 Subject: [PATCH 016/299] BUILD.md: Mention Debian in the build-instructions for Ubuntu (#1096) --- BUILD.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/BUILD.md b/BUILD.md index 034000ac..9f3a35be 100644 --- a/BUILD.md +++ b/BUILD.md @@ -6,8 +6,8 @@ - [Linux](#linux) - [Dependencies](#dependencies) - [For Arch and derivatives:](#for-arch-and-derivatives) + - [For Debian, Ubuntu and derivatives](#for-debian-ubuntu-and-derivatives) - [For Fedora and derivatives:](#for-fedora-and-derivatives) - - [For Ubuntu and derivatives](#for-ubuntu-and-derivatives) - [Build Cemu](#build-cemu) - [CMake and Clang](#cmake-and-clang) - [GCC](#gcc) @@ -48,10 +48,7 @@ To compile Cemu, a recent enough compiler and STL with C++20 support is required #### For Arch and derivatives: `sudo pacman -S --needed base-devel clang cmake freeglut git glm gtk3 libgcrypt libpulse libsecret linux-headers llvm nasm ninja systemd unzip zip` -#### For Fedora and derivatives: -`sudo dnf install clang cmake cubeb-devel freeglut-devel git glm-devel gtk3-devel kernel-headers libgcrypt-devel libsecret-devel libtool libusb1-devel nasm ninja-build perl-core systemd-devel zlib-devel` - -#### For Ubuntu and derivatives: +#### For Debian, Ubuntu and derivatives: `sudo apt install -y cmake curl clang-15 freeglut3-dev git libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libtool nasm ninja-build` You may also need to install `libusb-1.0-0-dev` as a workaround for an issue with the vcpkg hidapi package. @@ -59,6 +56,9 @@ You may also need to install `libusb-1.0-0-dev` as a workaround for an issue wit At Step 3 in [Build Cemu using cmake and clang](#build-cemu-using-cmake-and-clang), use the following command instead: `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang-15 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-15 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja` +#### For Fedora and derivatives: +`sudo dnf install clang cmake cubeb-devel freeglut-devel git glm-devel gtk3-devel kernel-headers libgcrypt-devel libsecret-devel libtool libusb1-devel nasm ninja-build perl-core systemd-devel zlib-devel` + ### Build Cemu #### CMake and Clang @@ -74,8 +74,8 @@ cmake --build build If you are building using GCC, make sure you have g++ installed: - Installation for Arch and derivatives: `sudo pacman -S gcc` +- Installation for Debian, Ubuntu and derivatives: `sudo apt install g++` - Installation for Fedora and derivatives: `sudo dnf install gcc-c++` -- Installation for Ubuntu and derivatives: `sudo apt install g++` ``` git clone --recursive https://github.com/cemu-project/Cemu From 96bbd3bd259eccb767be3a8a3dd406cdbff4b905 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Mon, 19 Feb 2024 12:03:16 +0100 Subject: [PATCH 017/299] Latte: Avoid assert in texture view check --- src/Cafe/HW/Latte/Core/LatteTexture.cpp | 101 ++++++------------------ 1 file changed, 25 insertions(+), 76 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/LatteTexture.cpp b/src/Cafe/HW/Latte/Core/LatteTexture.cpp index d38af8ec..707428af 100644 --- a/src/Cafe/HW/Latte/Core/LatteTexture.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTexture.cpp @@ -790,81 +790,30 @@ enum VIEWCOMPATIBILITY VIEW_NOT_COMPATIBLE, }; -bool IsDimensionCompatibleForView(Latte::E_DIM baseDim, Latte::E_DIM viewDim) +bool IsDimensionCompatibleForGX2View(Latte::E_DIM baseDim, Latte::E_DIM viewDim) { - bool incompatibleDim = false; - if (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_2D) - ; - else if (baseDim == Latte::E_DIM::DIM_1D && viewDim == Latte::E_DIM::DIM_1D) - ; - else if (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_2D_ARRAY) - ; - else if (baseDim == Latte::E_DIM::DIM_CUBEMAP && viewDim == Latte::E_DIM::DIM_CUBEMAP) - ; - else if (baseDim == Latte::E_DIM::DIM_CUBEMAP && viewDim == Latte::E_DIM::DIM_2D_ARRAY) - ; - else if (baseDim == Latte::E_DIM::DIM_2D_ARRAY && viewDim == Latte::E_DIM::DIM_2D_ARRAY) - ; - else if (baseDim == Latte::E_DIM::DIM_2D_ARRAY && viewDim == Latte::E_DIM::DIM_2D) - ; - else if (baseDim == Latte::E_DIM::DIM_2D_ARRAY && viewDim == Latte::E_DIM::DIM_CUBEMAP) - ; - else if (baseDim == Latte::E_DIM::DIM_3D && viewDim == Latte::E_DIM::DIM_2D_ARRAY) - ; - else if (baseDim == Latte::E_DIM::DIM_2D_MSAA && viewDim == Latte::E_DIM::DIM_2D_MSAA) - ; - else if (baseDim == Latte::E_DIM::DIM_2D_ARRAY && viewDim == Latte::E_DIM::DIM_3D) - { - // not compatible on OpenGL - incompatibleDim = true; - } - else if (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_2D_MSAA) - { - // not compatible - incompatibleDim = true; - } - else if (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_1D) - { - // not compatible - incompatibleDim = true; - } - else if (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_3D) - { - // not compatible - incompatibleDim = true; - } - else if (baseDim == Latte::E_DIM::DIM_3D && viewDim == Latte::E_DIM::DIM_2D) - { - // not compatible - incompatibleDim = true; - } - else if (baseDim == Latte::E_DIM::DIM_3D && viewDim == Latte::E_DIM::DIM_3D) - { - // incompatible by default, but may be compatible if the view matches the depth of the base texture and starts at mip/slice 0 - incompatibleDim = true; - } - else if ((baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_CUBEMAP) || - (baseDim == Latte::E_DIM::DIM_CUBEMAP && viewDim == Latte::E_DIM::DIM_2D)) - { - // not compatible - incompatibleDim = true; - } - else if (baseDim == Latte::E_DIM::DIM_2D_MSAA && viewDim == Latte::E_DIM::DIM_2D) - { - // not compatible - incompatibleDim = true; - } - else if (baseDim == Latte::E_DIM::DIM_1D && viewDim == Latte::E_DIM::DIM_2D) - { - // not compatible (probably?) - incompatibleDim = true; - } - else - { - cemu_assert_debug(false); - incompatibleDim = true; - } - return !incompatibleDim; + // Note that some combinations depend on the exact view/slice index and count which we currently ignore (like a 3D view of a 3D texture) + bool isCompatible = + (baseDim == viewDim) || + (baseDim == Latte::E_DIM::DIM_CUBEMAP && viewDim == Latte::E_DIM::DIM_2D) || + (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_2D_ARRAY) || + (baseDim == Latte::E_DIM::DIM_2D_ARRAY && viewDim == Latte::E_DIM::DIM_2D) || + (baseDim == Latte::E_DIM::DIM_CUBEMAP && viewDim == Latte::E_DIM::DIM_2D_ARRAY) || + (baseDim == Latte::E_DIM::DIM_2D_ARRAY && viewDim == Latte::E_DIM::DIM_CUBEMAP) || + (baseDim == Latte::E_DIM::DIM_3D && viewDim == Latte::E_DIM::DIM_2D_ARRAY); + if(isCompatible) + return true; + // these combinations have been seen in use by games and are considered incompatible: + // (baseDim == Latte::E_DIM::DIM_2D_ARRAY && viewDim == Latte::E_DIM::DIM_3D) -> Not allowed on OpenGL + // (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_2D_MSAA) + // (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_1D) + // (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_3D) + // (baseDim == Latte::E_DIM::DIM_3D && viewDim == Latte::E_DIM::DIM_2D) + // (baseDim == Latte::E_DIM::DIM_3D && viewDim == Latte::E_DIM::DIM_3D) -> Only compatible if the same depth and shared at mip/slice 0 + // (baseDim == Latte::E_DIM::DIM_2D && viewDim == Latte::E_DIM::DIM_CUBEMAP) + // (baseDim == Latte::E_DIM::DIM_2D_MSAA && viewDim == Latte::E_DIM::DIM_2D) + // (baseDim == Latte::E_DIM::DIM_1D && viewDim == Latte::E_DIM::DIM_2D) + return false; } VIEWCOMPATIBILITY LatteTexture_CanTextureBeRepresentedAsView(LatteTexture* baseTexture, uint32 physAddr, sint32 width, sint32 height, sint32 pitch, Latte::E_DIM dimView, Latte::E_GX2SURFFMT format, bool isDepth, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, sint32& relativeMipIndex, sint32& relativeSliceIndex) @@ -881,7 +830,7 @@ VIEWCOMPATIBILITY LatteTexture_CanTextureBeRepresentedAsView(LatteTexture* baseT return VIEW_NOT_COMPATIBLE; // 3D views are only compatible on Vulkan if they match the base texture in regards to mip and slice count bool isCompatible3DView = dimView == Latte::E_DIM::DIM_3D && baseTexture->dim == dimView && firstSlice == 0 && firstMip == 0 && baseTexture->mipLevels == numMip && baseTexture->depth == numSlice; - if (!isCompatible3DView && !IsDimensionCompatibleForView(baseTexture->dim, dimView)) + if (!isCompatible3DView && !IsDimensionCompatibleForGX2View(baseTexture->dim, dimView)) return VIEW_NOT_COMPATIBLE; if (baseTexture->isDepth && baseTexture->format != format) { @@ -933,7 +882,7 @@ VIEWCOMPATIBILITY LatteTexture_CanTextureBeRepresentedAsView(LatteTexture* baseT if (!LatteTexture_IsTexelSizeCompatibleFormat(baseTexture->format, format) ) return VIEW_NOT_COMPATIBLE; - if (!IsDimensionCompatibleForView(baseTexture->dim, dimView)) + if (!IsDimensionCompatibleForGX2View(baseTexture->dim, dimView)) return VIEW_NOT_COMPATIBLE; if (baseTexture->isDepth && baseTexture->format != format) { From 72ce4838ea79252f9ec0df3f3eeb5959ca6616e6 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Mon, 19 Feb 2024 12:07:03 +0100 Subject: [PATCH 018/299] Latte: Optimize uniform register array size for known shaders --- src/Cafe/HW/Latte/Core/LatteShader.cpp | 2 +- .../LatteDecompilerAnalyzer.cpp | 2 +- .../LatteDecompilerEmitGLSLHeader.hpp | 4 ++-- .../LatteDecompilerInternal.h | 18 +++++++++++------- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/LatteShader.cpp b/src/Cafe/HW/Latte/Core/LatteShader.cpp index 503fb664..b59702cd 100644 --- a/src/Cafe/HW/Latte/Core/LatteShader.cpp +++ b/src/Cafe/HW/Latte/Core/LatteShader.cpp @@ -652,7 +652,7 @@ LatteDecompilerShader* LatteShader_CreateShaderFromDecompilerOutput(LatteDecompi } else { - shader->uniform.count_uniformRegister = decompilerOutput.uniformOffsetsVK.count_uniformRegister; + shader->uniform.count_uniformRegister = decompilerOutput.uniformOffsetsGL.count_uniformRegister; } // calculate aux hash if (calculateAuxHash) diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp index 2e837198..cf22f05d 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp @@ -787,7 +787,7 @@ void LatteDecompiler_analyze(LatteDecompilerShaderContext* shaderContext, LatteD continue; LatteDecompilerShader::QuickBufferEntry entry; entry.index = i; - entry.size = shaderContext->analyzer.uniformBufferAccessTracker[i].DetermineSize(LATTE_GLSL_DYNAMIC_UNIFORM_BLOCK_SIZE) * 16; + entry.size = shaderContext->analyzer.uniformBufferAccessTracker[i].DetermineSize(shaderContext->shaderBaseHash, LATTE_GLSL_DYNAMIC_UNIFORM_BLOCK_SIZE) * 16; shader->list_quickBufferList.push_back(entry); } // get dimension of each used texture diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLHeader.hpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLHeader.hpp index 21cae093..428f8647 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLHeader.hpp +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLHeader.hpp @@ -37,7 +37,7 @@ namespace LatteDecompiler } else if (decompilerContext->shader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CFILE) { - uint32 cfileSize = decompilerContext->analyzer.uniformRegisterAccessTracker.DetermineSize(256); + uint32 cfileSize = decompilerContext->analyzer.uniformRegisterAccessTracker.DetermineSize(decompilerContext->shaderBaseHash, 256); // full or partial uniform register file has to be present if (shaderType == LatteConst::ShaderType::Vertex) shaderSrc->addFmt("uniform ivec4 uf_uniformRegisterVS[{}];" _CRLF, cfileSize); @@ -156,7 +156,7 @@ namespace LatteDecompiler shaderSrc->addFmt("uniform {}{}" _CRLF, _getShaderUniformBlockInterfaceName(decompilerContext->shaderType), i); shaderSrc->add("{" _CRLF); - shaderSrc->addFmt("vec4 {}{}[{}];" _CRLF, _getShaderUniformBlockVariableName(decompilerContext->shaderType), i, decompilerContext->analyzer.uniformBufferAccessTracker[i].DetermineSize(LATTE_GLSL_DYNAMIC_UNIFORM_BLOCK_SIZE)); + shaderSrc->addFmt("vec4 {}{}[{}];" _CRLF, _getShaderUniformBlockVariableName(decompilerContext->shaderType), i, decompilerContext->analyzer.uniformBufferAccessTracker[i].DetermineSize(decompilerContext->shaderBaseHash, LATTE_GLSL_DYNAMIC_UNIFORM_BLOCK_SIZE)); shaderSrc->add("};" _CRLF _CRLF); shaderSrc->add(_CRLF); } diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h index ac2a1fe1..ed1858ba 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInternal.h @@ -157,19 +157,23 @@ struct LatteDecompilerBufferAccessTracker } } - sint32 DetermineSize(sint32 maximumSize) const + sint32 DetermineSize(uint64 shaderBaseHash, sint32 maximumSize) const { - // here we try to predict the accessed range so we dont have to upload the whole buffer - // potential risky optimization: assume that if there is a fixed-index access on an index higher than any other non-zero relative accesses, it bounds the prior relative access + // here we try to predict the accessed byte range so we dont have to upload the whole buffer + // if no bound can be determined then return maximumSize + // for some known shaders we use hand-tuned values instead of the maximumSize fallback value that those shaders would normally use + if(shaderBaseHash == 0x8ff56afdf1a2f837) // XCX text rendering + return 24; + if(shaderBaseHash == 0x37b9100c1310d3bb) // BotW UI backdrops 1 + return 24; + if(shaderBaseHash == 0xf7ba548c1fefe24a) // BotW UI backdrops 2 + return 30; + sint32 highestAccessIndex = -1; if(hasStaticIndexAccess) - { highestAccessIndex = highestAccessStaticIndex; - } if(hasDynamicIndexAccess) - { return maximumSize; // dynamic index exists and no bound can be determined - } if (highestAccessIndex < 0) return 1; // no access at all? But avoid zero as a size return highestAccessIndex + 1; From a63678c1f40c21151c6daa6f20cbb8fc600ae92a Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Tue, 20 Feb 2024 11:10:35 +0100 Subject: [PATCH 019/299] Update SDL2 vcpkg port to 2.30.0 --- .../sdl2/alsa-dep-fix.patch | 13 ++ .../vcpkg_overlay_ports/sdl2/deps.patch | 13 ++ .../vcpkg_overlay_ports/sdl2/portfile.cmake | 137 ++++++++++++++++++ dependencies/vcpkg_overlay_ports/sdl2/usage | 8 + .../vcpkg_overlay_ports/sdl2/vcpkg.json | 68 +++++++++ .../sdl2/alsa-dep-fix.patch | 13 ++ .../vcpkg_overlay_ports_linux/sdl2/deps.patch | 13 ++ .../sdl2/portfile.cmake | 137 ++++++++++++++++++ .../vcpkg_overlay_ports_linux/sdl2/usage | 8 + .../vcpkg_overlay_ports_linux/sdl2/vcpkg.json | 68 +++++++++ .../sdl2/alsa-dep-fix.patch | 13 ++ .../vcpkg_overlay_ports_mac/sdl2/deps.patch | 13 ++ .../sdl2/portfile.cmake | 137 ++++++++++++++++++ .../vcpkg_overlay_ports_mac/sdl2/usage | 8 + .../vcpkg_overlay_ports_mac/sdl2/vcpkg.json | 68 +++++++++ 15 files changed, 717 insertions(+) create mode 100644 dependencies/vcpkg_overlay_ports/sdl2/alsa-dep-fix.patch create mode 100644 dependencies/vcpkg_overlay_ports/sdl2/deps.patch create mode 100644 dependencies/vcpkg_overlay_ports/sdl2/portfile.cmake create mode 100644 dependencies/vcpkg_overlay_ports/sdl2/usage create mode 100644 dependencies/vcpkg_overlay_ports/sdl2/vcpkg.json create mode 100644 dependencies/vcpkg_overlay_ports_linux/sdl2/alsa-dep-fix.patch create mode 100644 dependencies/vcpkg_overlay_ports_linux/sdl2/deps.patch create mode 100644 dependencies/vcpkg_overlay_ports_linux/sdl2/portfile.cmake create mode 100644 dependencies/vcpkg_overlay_ports_linux/sdl2/usage create mode 100644 dependencies/vcpkg_overlay_ports_linux/sdl2/vcpkg.json create mode 100644 dependencies/vcpkg_overlay_ports_mac/sdl2/alsa-dep-fix.patch create mode 100644 dependencies/vcpkg_overlay_ports_mac/sdl2/deps.patch create mode 100644 dependencies/vcpkg_overlay_ports_mac/sdl2/portfile.cmake create mode 100644 dependencies/vcpkg_overlay_ports_mac/sdl2/usage create mode 100644 dependencies/vcpkg_overlay_ports_mac/sdl2/vcpkg.json diff --git a/dependencies/vcpkg_overlay_ports/sdl2/alsa-dep-fix.patch b/dependencies/vcpkg_overlay_ports/sdl2/alsa-dep-fix.patch new file mode 100644 index 00000000..5b2c77b9 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/sdl2/alsa-dep-fix.patch @@ -0,0 +1,13 @@ +diff --git a/SDL2Config.cmake.in b/SDL2Config.cmake.in +index cc8bcf26d..ead829767 100644 +--- a/SDL2Config.cmake.in ++++ b/SDL2Config.cmake.in +@@ -35,7 +35,7 @@ include("${CMAKE_CURRENT_LIST_DIR}/sdlfind.cmake") + + set(SDL_ALSA @SDL_ALSA@) + set(SDL_ALSA_SHARED @SDL_ALSA_SHARED@) +-if(SDL_ALSA AND NOT SDL_ALSA_SHARED AND TARGET SDL2::SDL2-static) ++if(SDL_ALSA) + sdlFindALSA() + endif() + unset(SDL_ALSA) diff --git a/dependencies/vcpkg_overlay_ports/sdl2/deps.patch b/dependencies/vcpkg_overlay_ports/sdl2/deps.patch new file mode 100644 index 00000000..a8637d8c --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/sdl2/deps.patch @@ -0,0 +1,13 @@ +diff --git a/cmake/sdlchecks.cmake b/cmake/sdlchecks.cmake +index 65a98efbe..2f99f28f1 100644 +--- a/cmake/sdlchecks.cmake ++++ b/cmake/sdlchecks.cmake +@@ -352,7 +352,7 @@ endmacro() + # - HAVE_SDL_LOADSO opt + macro(CheckLibSampleRate) + if(SDL_LIBSAMPLERATE) +- find_package(SampleRate QUIET) ++ find_package(SampleRate CONFIG REQUIRED) + if(SampleRate_FOUND AND TARGET SampleRate::samplerate) + set(HAVE_LIBSAMPLERATE TRUE) + set(HAVE_LIBSAMPLERATE_H TRUE) diff --git a/dependencies/vcpkg_overlay_ports/sdl2/portfile.cmake b/dependencies/vcpkg_overlay_ports/sdl2/portfile.cmake new file mode 100644 index 00000000..22685e6a --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/sdl2/portfile.cmake @@ -0,0 +1,137 @@ +vcpkg_from_github( + OUT_SOURCE_PATH SOURCE_PATH + REPO libsdl-org/SDL + REF "release-${VERSION}" + SHA512 c7635a83a52f3970a372b804a8631f0a7e6b8d89aed1117bcc54a2040ad0928122175004cf2b42cf84a4fd0f86236f779229eaa63dfa6ca9c89517f999c5ff1c + HEAD_REF main + PATCHES + deps.patch + alsa-dep-fix.patch +) + +string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "static" SDL_STATIC) +string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "dynamic" SDL_SHARED) +string(COMPARE EQUAL "${VCPKG_CRT_LINKAGE}" "static" FORCE_STATIC_VCRT) + +vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS + FEATURES + alsa SDL_ALSA + alsa CMAKE_REQUIRE_FIND_PACKAGE_ALSA + ibus SDL_IBUS + samplerate SDL_LIBSAMPLERATE + vulkan SDL_VULKAN + wayland SDL_WAYLAND + x11 SDL_X11 + INVERTED_FEATURES + alsa CMAKE_DISABLE_FIND_PACKAGE_ALSA +) + +if ("x11" IN_LIST FEATURES) + message(WARNING "You will need to install Xorg dependencies to use feature x11:\nsudo apt install libx11-dev libxft-dev libxext-dev\n") +endif() +if ("wayland" IN_LIST FEATURES) + message(WARNING "You will need to install Wayland dependencies to use feature wayland:\nsudo apt install libwayland-dev libxkbcommon-dev libegl1-mesa-dev\n") +endif() +if ("ibus" IN_LIST FEATURES) + message(WARNING "You will need to install ibus dependencies to use feature ibus:\nsudo apt install libibus-1.0-dev\n") +endif() + +if(VCPKG_TARGET_IS_UWP) + set(configure_opts WINDOWS_USE_MSBUILD) +endif() + +vcpkg_cmake_configure( + SOURCE_PATH "${SOURCE_PATH}" + ${configure_opts} + OPTIONS ${FEATURE_OPTIONS} + -DSDL_STATIC=${SDL_STATIC} + -DSDL_SHARED=${SDL_SHARED} + -DSDL_FORCE_STATIC_VCRT=${FORCE_STATIC_VCRT} + -DSDL_LIBC=ON + -DSDL_TEST=OFF + -DSDL_INSTALL_CMAKEDIR="cmake" + -DCMAKE_DISABLE_FIND_PACKAGE_Git=ON + -DPKG_CONFIG_USE_CMAKE_PREFIX_PATH=ON + -DSDL_LIBSAMPLERATE_SHARED=OFF + MAYBE_UNUSED_VARIABLES + SDL_FORCE_STATIC_VCRT + PKG_CONFIG_USE_CMAKE_PREFIX_PATH +) + +vcpkg_cmake_install() +vcpkg_cmake_config_fixup(CONFIG_PATH cmake) + +file(REMOVE_RECURSE + "${CURRENT_PACKAGES_DIR}/debug/include" + "${CURRENT_PACKAGES_DIR}/debug/share" + "${CURRENT_PACKAGES_DIR}/bin/sdl2-config" + "${CURRENT_PACKAGES_DIR}/debug/bin/sdl2-config" + "${CURRENT_PACKAGES_DIR}/SDL2.framework" + "${CURRENT_PACKAGES_DIR}/debug/SDL2.framework" + "${CURRENT_PACKAGES_DIR}/share/licenses" + "${CURRENT_PACKAGES_DIR}/share/aclocal" +) + +file(GLOB BINS "${CURRENT_PACKAGES_DIR}/debug/bin/*" "${CURRENT_PACKAGES_DIR}/bin/*") +if(NOT BINS) + file(REMOVE_RECURSE + "${CURRENT_PACKAGES_DIR}/bin" + "${CURRENT_PACKAGES_DIR}/debug/bin" + ) +endif() + +if(VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_UWP AND NOT VCPKG_TARGET_IS_MINGW) + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") + file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/lib/manual-link") + file(RENAME "${CURRENT_PACKAGES_DIR}/lib/SDL2main.lib" "${CURRENT_PACKAGES_DIR}/lib/manual-link/SDL2main.lib") + endif() + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") + file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link") + file(RENAME "${CURRENT_PACKAGES_DIR}/debug/lib/SDL2maind.lib" "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link/SDL2maind.lib") + endif() + + file(GLOB SHARE_FILES "${CURRENT_PACKAGES_DIR}/share/sdl2/*.cmake") + foreach(SHARE_FILE ${SHARE_FILES}) + vcpkg_replace_string("${SHARE_FILE}" "lib/SDL2main" "lib/manual-link/SDL2main") + endforeach() +endif() + +vcpkg_copy_pdbs() + +set(DYLIB_COMPATIBILITY_VERSION_REGEX "set\\(DYLIB_COMPATIBILITY_VERSION (.+)\\)") +set(DYLIB_CURRENT_VERSION_REGEX "set\\(DYLIB_CURRENT_VERSION (.+)\\)") +file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_COMPATIBILITY_VERSION REGEX ${DYLIB_COMPATIBILITY_VERSION_REGEX}) +file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_CURRENT_VERSION REGEX ${DYLIB_CURRENT_VERSION_REGEX}) +string(REGEX REPLACE ${DYLIB_COMPATIBILITY_VERSION_REGEX} "\\1" DYLIB_COMPATIBILITY_VERSION "${DYLIB_COMPATIBILITY_VERSION}") +string(REGEX REPLACE ${DYLIB_CURRENT_VERSION_REGEX} "\\1" DYLIB_CURRENT_VERSION "${DYLIB_CURRENT_VERSION}") + +if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2main" "-lSDL2maind") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2 " "-lSDL2d ") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-static " "-lSDL2-staticd ") +endif() + +if(VCPKG_LIBRARY_LINKAGE STREQUAL "dynamic" AND VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_MINGW) + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-lSDL2-static " " ") + endif() + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-staticd " " ") + endif() +endif() + +if(VCPKG_TARGET_IS_UWP) + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "$<$:d>.lib" "") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:") + endif() + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "$<$:d>.lib" "d") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:") + endif() +endif() + +vcpkg_fixup_pkgconfig() + +file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") +vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.txt") diff --git a/dependencies/vcpkg_overlay_ports/sdl2/usage b/dependencies/vcpkg_overlay_ports/sdl2/usage new file mode 100644 index 00000000..1cddcd46 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/sdl2/usage @@ -0,0 +1,8 @@ +sdl2 provides CMake targets: + + find_package(SDL2 CONFIG REQUIRED) + target_link_libraries(main + PRIVATE + $ + $,SDL2::SDL2,SDL2::SDL2-static> + ) diff --git a/dependencies/vcpkg_overlay_ports/sdl2/vcpkg.json b/dependencies/vcpkg_overlay_ports/sdl2/vcpkg.json new file mode 100644 index 00000000..1f460375 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/sdl2/vcpkg.json @@ -0,0 +1,68 @@ +{ + "name": "sdl2", + "version": "2.30.0", + "description": "Simple DirectMedia Layer is a cross-platform development library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL and Direct3D.", + "homepage": "https://www.libsdl.org/download-2.0.php", + "license": "Zlib", + "dependencies": [ + { + "name": "dbus", + "default-features": false, + "platform": "linux" + }, + { + "name": "vcpkg-cmake", + "host": true + }, + { + "name": "vcpkg-cmake-config", + "host": true + } + ], + "default-features": [ + { + "name": "ibus", + "platform": "linux" + }, + { + "name": "wayland", + "platform": "linux" + }, + { + "name": "x11", + "platform": "linux" + } + ], + "features": { + "alsa": { + "description": "Support for alsa audio", + "dependencies": [ + { + "name": "alsa", + "platform": "linux" + } + ] + }, + "ibus": { + "description": "Build with ibus IME support", + "supports": "linux" + }, + "samplerate": { + "description": "Use libsamplerate for audio rate conversion", + "dependencies": [ + "libsamplerate" + ] + }, + "vulkan": { + "description": "Vulkan functionality for SDL" + }, + "wayland": { + "description": "Build with Wayland support", + "supports": "linux" + }, + "x11": { + "description": "Build with X11 support", + "supports": "!windows" + } + } +} diff --git a/dependencies/vcpkg_overlay_ports_linux/sdl2/alsa-dep-fix.patch b/dependencies/vcpkg_overlay_ports_linux/sdl2/alsa-dep-fix.patch new file mode 100644 index 00000000..5b2c77b9 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_linux/sdl2/alsa-dep-fix.patch @@ -0,0 +1,13 @@ +diff --git a/SDL2Config.cmake.in b/SDL2Config.cmake.in +index cc8bcf26d..ead829767 100644 +--- a/SDL2Config.cmake.in ++++ b/SDL2Config.cmake.in +@@ -35,7 +35,7 @@ include("${CMAKE_CURRENT_LIST_DIR}/sdlfind.cmake") + + set(SDL_ALSA @SDL_ALSA@) + set(SDL_ALSA_SHARED @SDL_ALSA_SHARED@) +-if(SDL_ALSA AND NOT SDL_ALSA_SHARED AND TARGET SDL2::SDL2-static) ++if(SDL_ALSA) + sdlFindALSA() + endif() + unset(SDL_ALSA) diff --git a/dependencies/vcpkg_overlay_ports_linux/sdl2/deps.patch b/dependencies/vcpkg_overlay_ports_linux/sdl2/deps.patch new file mode 100644 index 00000000..a8637d8c --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_linux/sdl2/deps.patch @@ -0,0 +1,13 @@ +diff --git a/cmake/sdlchecks.cmake b/cmake/sdlchecks.cmake +index 65a98efbe..2f99f28f1 100644 +--- a/cmake/sdlchecks.cmake ++++ b/cmake/sdlchecks.cmake +@@ -352,7 +352,7 @@ endmacro() + # - HAVE_SDL_LOADSO opt + macro(CheckLibSampleRate) + if(SDL_LIBSAMPLERATE) +- find_package(SampleRate QUIET) ++ find_package(SampleRate CONFIG REQUIRED) + if(SampleRate_FOUND AND TARGET SampleRate::samplerate) + set(HAVE_LIBSAMPLERATE TRUE) + set(HAVE_LIBSAMPLERATE_H TRUE) diff --git a/dependencies/vcpkg_overlay_ports_linux/sdl2/portfile.cmake b/dependencies/vcpkg_overlay_ports_linux/sdl2/portfile.cmake new file mode 100644 index 00000000..22685e6a --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_linux/sdl2/portfile.cmake @@ -0,0 +1,137 @@ +vcpkg_from_github( + OUT_SOURCE_PATH SOURCE_PATH + REPO libsdl-org/SDL + REF "release-${VERSION}" + SHA512 c7635a83a52f3970a372b804a8631f0a7e6b8d89aed1117bcc54a2040ad0928122175004cf2b42cf84a4fd0f86236f779229eaa63dfa6ca9c89517f999c5ff1c + HEAD_REF main + PATCHES + deps.patch + alsa-dep-fix.patch +) + +string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "static" SDL_STATIC) +string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "dynamic" SDL_SHARED) +string(COMPARE EQUAL "${VCPKG_CRT_LINKAGE}" "static" FORCE_STATIC_VCRT) + +vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS + FEATURES + alsa SDL_ALSA + alsa CMAKE_REQUIRE_FIND_PACKAGE_ALSA + ibus SDL_IBUS + samplerate SDL_LIBSAMPLERATE + vulkan SDL_VULKAN + wayland SDL_WAYLAND + x11 SDL_X11 + INVERTED_FEATURES + alsa CMAKE_DISABLE_FIND_PACKAGE_ALSA +) + +if ("x11" IN_LIST FEATURES) + message(WARNING "You will need to install Xorg dependencies to use feature x11:\nsudo apt install libx11-dev libxft-dev libxext-dev\n") +endif() +if ("wayland" IN_LIST FEATURES) + message(WARNING "You will need to install Wayland dependencies to use feature wayland:\nsudo apt install libwayland-dev libxkbcommon-dev libegl1-mesa-dev\n") +endif() +if ("ibus" IN_LIST FEATURES) + message(WARNING "You will need to install ibus dependencies to use feature ibus:\nsudo apt install libibus-1.0-dev\n") +endif() + +if(VCPKG_TARGET_IS_UWP) + set(configure_opts WINDOWS_USE_MSBUILD) +endif() + +vcpkg_cmake_configure( + SOURCE_PATH "${SOURCE_PATH}" + ${configure_opts} + OPTIONS ${FEATURE_OPTIONS} + -DSDL_STATIC=${SDL_STATIC} + -DSDL_SHARED=${SDL_SHARED} + -DSDL_FORCE_STATIC_VCRT=${FORCE_STATIC_VCRT} + -DSDL_LIBC=ON + -DSDL_TEST=OFF + -DSDL_INSTALL_CMAKEDIR="cmake" + -DCMAKE_DISABLE_FIND_PACKAGE_Git=ON + -DPKG_CONFIG_USE_CMAKE_PREFIX_PATH=ON + -DSDL_LIBSAMPLERATE_SHARED=OFF + MAYBE_UNUSED_VARIABLES + SDL_FORCE_STATIC_VCRT + PKG_CONFIG_USE_CMAKE_PREFIX_PATH +) + +vcpkg_cmake_install() +vcpkg_cmake_config_fixup(CONFIG_PATH cmake) + +file(REMOVE_RECURSE + "${CURRENT_PACKAGES_DIR}/debug/include" + "${CURRENT_PACKAGES_DIR}/debug/share" + "${CURRENT_PACKAGES_DIR}/bin/sdl2-config" + "${CURRENT_PACKAGES_DIR}/debug/bin/sdl2-config" + "${CURRENT_PACKAGES_DIR}/SDL2.framework" + "${CURRENT_PACKAGES_DIR}/debug/SDL2.framework" + "${CURRENT_PACKAGES_DIR}/share/licenses" + "${CURRENT_PACKAGES_DIR}/share/aclocal" +) + +file(GLOB BINS "${CURRENT_PACKAGES_DIR}/debug/bin/*" "${CURRENT_PACKAGES_DIR}/bin/*") +if(NOT BINS) + file(REMOVE_RECURSE + "${CURRENT_PACKAGES_DIR}/bin" + "${CURRENT_PACKAGES_DIR}/debug/bin" + ) +endif() + +if(VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_UWP AND NOT VCPKG_TARGET_IS_MINGW) + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") + file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/lib/manual-link") + file(RENAME "${CURRENT_PACKAGES_DIR}/lib/SDL2main.lib" "${CURRENT_PACKAGES_DIR}/lib/manual-link/SDL2main.lib") + endif() + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") + file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link") + file(RENAME "${CURRENT_PACKAGES_DIR}/debug/lib/SDL2maind.lib" "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link/SDL2maind.lib") + endif() + + file(GLOB SHARE_FILES "${CURRENT_PACKAGES_DIR}/share/sdl2/*.cmake") + foreach(SHARE_FILE ${SHARE_FILES}) + vcpkg_replace_string("${SHARE_FILE}" "lib/SDL2main" "lib/manual-link/SDL2main") + endforeach() +endif() + +vcpkg_copy_pdbs() + +set(DYLIB_COMPATIBILITY_VERSION_REGEX "set\\(DYLIB_COMPATIBILITY_VERSION (.+)\\)") +set(DYLIB_CURRENT_VERSION_REGEX "set\\(DYLIB_CURRENT_VERSION (.+)\\)") +file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_COMPATIBILITY_VERSION REGEX ${DYLIB_COMPATIBILITY_VERSION_REGEX}) +file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_CURRENT_VERSION REGEX ${DYLIB_CURRENT_VERSION_REGEX}) +string(REGEX REPLACE ${DYLIB_COMPATIBILITY_VERSION_REGEX} "\\1" DYLIB_COMPATIBILITY_VERSION "${DYLIB_COMPATIBILITY_VERSION}") +string(REGEX REPLACE ${DYLIB_CURRENT_VERSION_REGEX} "\\1" DYLIB_CURRENT_VERSION "${DYLIB_CURRENT_VERSION}") + +if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2main" "-lSDL2maind") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2 " "-lSDL2d ") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-static " "-lSDL2-staticd ") +endif() + +if(VCPKG_LIBRARY_LINKAGE STREQUAL "dynamic" AND VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_MINGW) + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-lSDL2-static " " ") + endif() + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-staticd " " ") + endif() +endif() + +if(VCPKG_TARGET_IS_UWP) + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "$<$:d>.lib" "") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:") + endif() + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "$<$:d>.lib" "d") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:") + endif() +endif() + +vcpkg_fixup_pkgconfig() + +file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") +vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.txt") diff --git a/dependencies/vcpkg_overlay_ports_linux/sdl2/usage b/dependencies/vcpkg_overlay_ports_linux/sdl2/usage new file mode 100644 index 00000000..1cddcd46 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_linux/sdl2/usage @@ -0,0 +1,8 @@ +sdl2 provides CMake targets: + + find_package(SDL2 CONFIG REQUIRED) + target_link_libraries(main + PRIVATE + $ + $,SDL2::SDL2,SDL2::SDL2-static> + ) diff --git a/dependencies/vcpkg_overlay_ports_linux/sdl2/vcpkg.json b/dependencies/vcpkg_overlay_ports_linux/sdl2/vcpkg.json new file mode 100644 index 00000000..1f460375 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_linux/sdl2/vcpkg.json @@ -0,0 +1,68 @@ +{ + "name": "sdl2", + "version": "2.30.0", + "description": "Simple DirectMedia Layer is a cross-platform development library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL and Direct3D.", + "homepage": "https://www.libsdl.org/download-2.0.php", + "license": "Zlib", + "dependencies": [ + { + "name": "dbus", + "default-features": false, + "platform": "linux" + }, + { + "name": "vcpkg-cmake", + "host": true + }, + { + "name": "vcpkg-cmake-config", + "host": true + } + ], + "default-features": [ + { + "name": "ibus", + "platform": "linux" + }, + { + "name": "wayland", + "platform": "linux" + }, + { + "name": "x11", + "platform": "linux" + } + ], + "features": { + "alsa": { + "description": "Support for alsa audio", + "dependencies": [ + { + "name": "alsa", + "platform": "linux" + } + ] + }, + "ibus": { + "description": "Build with ibus IME support", + "supports": "linux" + }, + "samplerate": { + "description": "Use libsamplerate for audio rate conversion", + "dependencies": [ + "libsamplerate" + ] + }, + "vulkan": { + "description": "Vulkan functionality for SDL" + }, + "wayland": { + "description": "Build with Wayland support", + "supports": "linux" + }, + "x11": { + "description": "Build with X11 support", + "supports": "!windows" + } + } +} diff --git a/dependencies/vcpkg_overlay_ports_mac/sdl2/alsa-dep-fix.patch b/dependencies/vcpkg_overlay_ports_mac/sdl2/alsa-dep-fix.patch new file mode 100644 index 00000000..5b2c77b9 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_mac/sdl2/alsa-dep-fix.patch @@ -0,0 +1,13 @@ +diff --git a/SDL2Config.cmake.in b/SDL2Config.cmake.in +index cc8bcf26d..ead829767 100644 +--- a/SDL2Config.cmake.in ++++ b/SDL2Config.cmake.in +@@ -35,7 +35,7 @@ include("${CMAKE_CURRENT_LIST_DIR}/sdlfind.cmake") + + set(SDL_ALSA @SDL_ALSA@) + set(SDL_ALSA_SHARED @SDL_ALSA_SHARED@) +-if(SDL_ALSA AND NOT SDL_ALSA_SHARED AND TARGET SDL2::SDL2-static) ++if(SDL_ALSA) + sdlFindALSA() + endif() + unset(SDL_ALSA) diff --git a/dependencies/vcpkg_overlay_ports_mac/sdl2/deps.patch b/dependencies/vcpkg_overlay_ports_mac/sdl2/deps.patch new file mode 100644 index 00000000..a8637d8c --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_mac/sdl2/deps.patch @@ -0,0 +1,13 @@ +diff --git a/cmake/sdlchecks.cmake b/cmake/sdlchecks.cmake +index 65a98efbe..2f99f28f1 100644 +--- a/cmake/sdlchecks.cmake ++++ b/cmake/sdlchecks.cmake +@@ -352,7 +352,7 @@ endmacro() + # - HAVE_SDL_LOADSO opt + macro(CheckLibSampleRate) + if(SDL_LIBSAMPLERATE) +- find_package(SampleRate QUIET) ++ find_package(SampleRate CONFIG REQUIRED) + if(SampleRate_FOUND AND TARGET SampleRate::samplerate) + set(HAVE_LIBSAMPLERATE TRUE) + set(HAVE_LIBSAMPLERATE_H TRUE) diff --git a/dependencies/vcpkg_overlay_ports_mac/sdl2/portfile.cmake b/dependencies/vcpkg_overlay_ports_mac/sdl2/portfile.cmake new file mode 100644 index 00000000..22685e6a --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_mac/sdl2/portfile.cmake @@ -0,0 +1,137 @@ +vcpkg_from_github( + OUT_SOURCE_PATH SOURCE_PATH + REPO libsdl-org/SDL + REF "release-${VERSION}" + SHA512 c7635a83a52f3970a372b804a8631f0a7e6b8d89aed1117bcc54a2040ad0928122175004cf2b42cf84a4fd0f86236f779229eaa63dfa6ca9c89517f999c5ff1c + HEAD_REF main + PATCHES + deps.patch + alsa-dep-fix.patch +) + +string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "static" SDL_STATIC) +string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "dynamic" SDL_SHARED) +string(COMPARE EQUAL "${VCPKG_CRT_LINKAGE}" "static" FORCE_STATIC_VCRT) + +vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS + FEATURES + alsa SDL_ALSA + alsa CMAKE_REQUIRE_FIND_PACKAGE_ALSA + ibus SDL_IBUS + samplerate SDL_LIBSAMPLERATE + vulkan SDL_VULKAN + wayland SDL_WAYLAND + x11 SDL_X11 + INVERTED_FEATURES + alsa CMAKE_DISABLE_FIND_PACKAGE_ALSA +) + +if ("x11" IN_LIST FEATURES) + message(WARNING "You will need to install Xorg dependencies to use feature x11:\nsudo apt install libx11-dev libxft-dev libxext-dev\n") +endif() +if ("wayland" IN_LIST FEATURES) + message(WARNING "You will need to install Wayland dependencies to use feature wayland:\nsudo apt install libwayland-dev libxkbcommon-dev libegl1-mesa-dev\n") +endif() +if ("ibus" IN_LIST FEATURES) + message(WARNING "You will need to install ibus dependencies to use feature ibus:\nsudo apt install libibus-1.0-dev\n") +endif() + +if(VCPKG_TARGET_IS_UWP) + set(configure_opts WINDOWS_USE_MSBUILD) +endif() + +vcpkg_cmake_configure( + SOURCE_PATH "${SOURCE_PATH}" + ${configure_opts} + OPTIONS ${FEATURE_OPTIONS} + -DSDL_STATIC=${SDL_STATIC} + -DSDL_SHARED=${SDL_SHARED} + -DSDL_FORCE_STATIC_VCRT=${FORCE_STATIC_VCRT} + -DSDL_LIBC=ON + -DSDL_TEST=OFF + -DSDL_INSTALL_CMAKEDIR="cmake" + -DCMAKE_DISABLE_FIND_PACKAGE_Git=ON + -DPKG_CONFIG_USE_CMAKE_PREFIX_PATH=ON + -DSDL_LIBSAMPLERATE_SHARED=OFF + MAYBE_UNUSED_VARIABLES + SDL_FORCE_STATIC_VCRT + PKG_CONFIG_USE_CMAKE_PREFIX_PATH +) + +vcpkg_cmake_install() +vcpkg_cmake_config_fixup(CONFIG_PATH cmake) + +file(REMOVE_RECURSE + "${CURRENT_PACKAGES_DIR}/debug/include" + "${CURRENT_PACKAGES_DIR}/debug/share" + "${CURRENT_PACKAGES_DIR}/bin/sdl2-config" + "${CURRENT_PACKAGES_DIR}/debug/bin/sdl2-config" + "${CURRENT_PACKAGES_DIR}/SDL2.framework" + "${CURRENT_PACKAGES_DIR}/debug/SDL2.framework" + "${CURRENT_PACKAGES_DIR}/share/licenses" + "${CURRENT_PACKAGES_DIR}/share/aclocal" +) + +file(GLOB BINS "${CURRENT_PACKAGES_DIR}/debug/bin/*" "${CURRENT_PACKAGES_DIR}/bin/*") +if(NOT BINS) + file(REMOVE_RECURSE + "${CURRENT_PACKAGES_DIR}/bin" + "${CURRENT_PACKAGES_DIR}/debug/bin" + ) +endif() + +if(VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_UWP AND NOT VCPKG_TARGET_IS_MINGW) + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") + file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/lib/manual-link") + file(RENAME "${CURRENT_PACKAGES_DIR}/lib/SDL2main.lib" "${CURRENT_PACKAGES_DIR}/lib/manual-link/SDL2main.lib") + endif() + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") + file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link") + file(RENAME "${CURRENT_PACKAGES_DIR}/debug/lib/SDL2maind.lib" "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link/SDL2maind.lib") + endif() + + file(GLOB SHARE_FILES "${CURRENT_PACKAGES_DIR}/share/sdl2/*.cmake") + foreach(SHARE_FILE ${SHARE_FILES}) + vcpkg_replace_string("${SHARE_FILE}" "lib/SDL2main" "lib/manual-link/SDL2main") + endforeach() +endif() + +vcpkg_copy_pdbs() + +set(DYLIB_COMPATIBILITY_VERSION_REGEX "set\\(DYLIB_COMPATIBILITY_VERSION (.+)\\)") +set(DYLIB_CURRENT_VERSION_REGEX "set\\(DYLIB_CURRENT_VERSION (.+)\\)") +file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_COMPATIBILITY_VERSION REGEX ${DYLIB_COMPATIBILITY_VERSION_REGEX}) +file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_CURRENT_VERSION REGEX ${DYLIB_CURRENT_VERSION_REGEX}) +string(REGEX REPLACE ${DYLIB_COMPATIBILITY_VERSION_REGEX} "\\1" DYLIB_COMPATIBILITY_VERSION "${DYLIB_COMPATIBILITY_VERSION}") +string(REGEX REPLACE ${DYLIB_CURRENT_VERSION_REGEX} "\\1" DYLIB_CURRENT_VERSION "${DYLIB_CURRENT_VERSION}") + +if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2main" "-lSDL2maind") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2 " "-lSDL2d ") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-static " "-lSDL2-staticd ") +endif() + +if(VCPKG_LIBRARY_LINKAGE STREQUAL "dynamic" AND VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_MINGW) + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-lSDL2-static " " ") + endif() + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-staticd " " ") + endif() +endif() + +if(VCPKG_TARGET_IS_UWP) + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "$<$:d>.lib" "") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:") + endif() + if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "$<$:d>.lib" "d") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:") + endif() +endif() + +vcpkg_fixup_pkgconfig() + +file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") +vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.txt") diff --git a/dependencies/vcpkg_overlay_ports_mac/sdl2/usage b/dependencies/vcpkg_overlay_ports_mac/sdl2/usage new file mode 100644 index 00000000..1cddcd46 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_mac/sdl2/usage @@ -0,0 +1,8 @@ +sdl2 provides CMake targets: + + find_package(SDL2 CONFIG REQUIRED) + target_link_libraries(main + PRIVATE + $ + $,SDL2::SDL2,SDL2::SDL2-static> + ) diff --git a/dependencies/vcpkg_overlay_ports_mac/sdl2/vcpkg.json b/dependencies/vcpkg_overlay_ports_mac/sdl2/vcpkg.json new file mode 100644 index 00000000..1f460375 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_mac/sdl2/vcpkg.json @@ -0,0 +1,68 @@ +{ + "name": "sdl2", + "version": "2.30.0", + "description": "Simple DirectMedia Layer is a cross-platform development library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL and Direct3D.", + "homepage": "https://www.libsdl.org/download-2.0.php", + "license": "Zlib", + "dependencies": [ + { + "name": "dbus", + "default-features": false, + "platform": "linux" + }, + { + "name": "vcpkg-cmake", + "host": true + }, + { + "name": "vcpkg-cmake-config", + "host": true + } + ], + "default-features": [ + { + "name": "ibus", + "platform": "linux" + }, + { + "name": "wayland", + "platform": "linux" + }, + { + "name": "x11", + "platform": "linux" + } + ], + "features": { + "alsa": { + "description": "Support for alsa audio", + "dependencies": [ + { + "name": "alsa", + "platform": "linux" + } + ] + }, + "ibus": { + "description": "Build with ibus IME support", + "supports": "linux" + }, + "samplerate": { + "description": "Use libsamplerate for audio rate conversion", + "dependencies": [ + "libsamplerate" + ] + }, + "vulkan": { + "description": "Vulkan functionality for SDL" + }, + "wayland": { + "description": "Build with Wayland support", + "supports": "linux" + }, + "x11": { + "description": "Build with X11 support", + "supports": "!windows" + } + } +} From 8b37e316d0537da9c717cb0698c9141e668d6fff Mon Sep 17 00:00:00 2001 From: Leif Liddy Date: Sat, 24 Feb 2024 20:47:06 +0100 Subject: [PATCH 020/299] BUILD.md: Add llvm package for Fedora (#1101) --- BUILD.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BUILD.md b/BUILD.md index 9f3a35be..3ff2254f 100644 --- a/BUILD.md +++ b/BUILD.md @@ -57,7 +57,7 @@ At Step 3 in [Build Cemu using cmake and clang](#build-cemu-using-cmake-and-clan `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang-15 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-15 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja` #### For Fedora and derivatives: -`sudo dnf install clang cmake cubeb-devel freeglut-devel git glm-devel gtk3-devel kernel-headers libgcrypt-devel libsecret-devel libtool libusb1-devel nasm ninja-build perl-core systemd-devel zlib-devel` +`sudo dnf install clang cmake cubeb-devel freeglut-devel git glm-devel gtk3-devel kernel-headers libgcrypt-devel libsecret-devel libtool libusb1-devel llvm nasm ninja-build perl-core systemd-devel zlib-devel` ### Build Cemu @@ -128,7 +128,7 @@ If you are getting a different error than any of the errors listed above, you ma ##### Building Errors -This section refers to running `cmake --build build`. +This section refers to running `cmake --build build`. * `main.cpp.o: in function 'std::__cxx11::basic_string...` * You likely are experiencing a clang-14 issue. This can only be fixed by either lowering the clang version or using GCC, see [GCC](#gcc). From 49c55a3f561eed2da750cbacfcef4fc5ffe1075e Mon Sep 17 00:00:00 2001 From: Simon <113838661+ssievert42@users.noreply.github.com> Date: Wed, 6 Mar 2024 14:37:36 +0100 Subject: [PATCH 021/299] nsyshid: remove stray print statements (#1106) --- src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp | 1 - src/Cafe/OS/libs/nsyshid/nsyshid.cpp | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp index 520a0d31..23da5798 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp +++ b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp @@ -446,7 +446,6 @@ namespace nsyshid::backend::windows { sprintf(debugOutput + i * 3, "%02x ", data[i]); } - fmt::print("{} Data: {}\n", prefix, debugOutput); cemuLog_logDebug(LogType::Force, "[{}] Data: {}", prefix, debugOutput); } } // namespace nsyshid::backend::windows diff --git a/src/Cafe/OS/libs/nsyshid/nsyshid.cpp b/src/Cafe/OS/libs/nsyshid/nsyshid.cpp index b21e2a43..ba3e3b96 100644 --- a/src/Cafe/OS/libs/nsyshid/nsyshid.cpp +++ b/src/Cafe/OS/libs/nsyshid/nsyshid.cpp @@ -332,7 +332,6 @@ namespace nsyshid { sprintf(debugOutput + i * 3, "%02x ", data[i]); } - fmt::print("{} Data: {}\n", prefix, debugOutput); cemuLog_logDebug(LogType::Force, "[{}] Data: {}", prefix, debugOutput); } From 8f1cd4f9255e16aeddb2e72d35a47f37e1e478bc Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Mon, 26 Feb 2024 23:52:33 +0100 Subject: [PATCH 022/299] Vulkan: Update some code to use VK_KHR_synchronization2 --- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 30 ++- .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 220 +++++++++--------- .../Renderer/Vulkan/VulkanRendererCore.cpp | 35 +-- 3 files changed, 140 insertions(+), 145 deletions(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 616f57e2..631f1d0c 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -468,6 +468,15 @@ VulkanRenderer::VulkanRenderer() void* deviceExtensionFeatures = nullptr; + // enable VK_KHR_synchonization_2 + VkPhysicalDeviceSynchronization2FeaturesKHR sync2Feature{}; + { + sync2Feature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES_KHR; + sync2Feature.pNext = deviceExtensionFeatures; + deviceExtensionFeatures = &sync2Feature; + sync2Feature.synchronization2 = VK_TRUE; + } + // enable VK_EXT_pipeline_creation_cache_control VkPhysicalDevicePipelineCreationCacheControlFeaturesEXT cacheControlFeature{}; if (m_featureControl.deviceExtensions.pipeline_creation_cache_control) @@ -2852,13 +2861,20 @@ void VulkanRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu ClearColorbuffer(padView); // barrier for input texture - VkMemoryBarrier memoryBarrier{}; - memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; - VkPipelineStageFlags srcStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT; - VkPipelineStageFlags dstStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - memoryBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; - memoryBarrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_SHADER_READ_BIT; - vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStage, dstStage, 0, 1, &memoryBarrier, 0, nullptr, 0, nullptr); + { + VkMemoryBarrier2 memoryBarrier2{}; + memoryBarrier2.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2; + memoryBarrier2.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR | VK_PIPELINE_STAGE_2_TRANSFER_BIT_KHR; + memoryBarrier2.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR | VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT_KHR | VK_PIPELINE_STAGE_2_GEOMETRY_SHADER_BIT_KHR | VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT_KHR; + memoryBarrier2.srcAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT; + memoryBarrier2.dstAccessMask = VK_ACCESS_2_MEMORY_READ_BIT; + VkDependencyInfo dependencyInfo{}; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.dependencyFlags = 0; + dependencyInfo.memoryBarrierCount = 1; + dependencyInfo.pMemoryBarriers = &memoryBarrier2; + vkCmdPipelineBarrier2KHR(m_state.currentCommandBuffer, &dependencyInfo); + } auto pipeline = backbufferBlit_createGraphicsPipeline(m_swapchainDescriptorSetLayout, padView, shader); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index b61a0b40..7565d260 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -728,201 +728,192 @@ private: IMAGE_READ = 0x20, IMAGE_WRITE = 0x40, - }; template - void barrier_calcStageAndMask(VkPipelineStageFlags& stages, VkAccessFlags& accessFlags) + void barrier_calcStageAndMask(VkPipelineStageFlags2& stages, VkAccessFlags2& accessFlags) { stages = 0; accessFlags = 0; if constexpr ((TSyncOp & BUFFER_SHADER_READ) != 0) { - // in theory: VK_ACCESS_INDEX_READ_BIT should be set here too but indices are currently separated - stages |= VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - accessFlags |= VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_UNIFORM_READ_BIT | VK_ACCESS_SHADER_READ_BIT; + // in theory: VK_ACCESS_2_INDEX_READ_BIT should be set here too but indices are currently separated + stages |= VK_PIPELINE_STAGE_2_VERTEX_INPUT_BIT | VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; + accessFlags |= VK_ACCESS_2_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_2_UNIFORM_READ_BIT | VK_ACCESS_2_SHADER_READ_BIT; } - + if constexpr ((TSyncOp & BUFFER_SHADER_WRITE) != 0) { - stages |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - accessFlags |= VK_ACCESS_SHADER_WRITE_BIT; + stages |= VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; + accessFlags |= VK_ACCESS_2_SHADER_WRITE_BIT; } if constexpr ((TSyncOp & ANY_TRANSFER) != 0) { - //stages |= VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_HOST_BIT; - //accessFlags |= VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_READ_BIT | VK_ACCESS_HOST_WRITE_BIT; - stages |= VK_PIPELINE_STAGE_TRANSFER_BIT; - accessFlags |= VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; - - //accessFlags |= VK_ACCESS_MEMORY_READ_BIT; - //accessFlags |= VK_ACCESS_MEMORY_WRITE_BIT; + stages |= VK_PIPELINE_STAGE_2_TRANSFER_BIT; + accessFlags |= VK_ACCESS_2_TRANSFER_READ_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT; } if constexpr ((TSyncOp & TRANSFER_READ) != 0) { - stages |= VK_PIPELINE_STAGE_TRANSFER_BIT; - accessFlags |= VK_ACCESS_TRANSFER_READ_BIT; - - //accessFlags |= VK_ACCESS_MEMORY_READ_BIT; + stages |= VK_PIPELINE_STAGE_2_TRANSFER_BIT; + accessFlags |= VK_ACCESS_2_TRANSFER_READ_BIT; } if constexpr ((TSyncOp & TRANSFER_WRITE) != 0) { - stages |= VK_PIPELINE_STAGE_TRANSFER_BIT; - accessFlags |= VK_ACCESS_TRANSFER_WRITE_BIT; - - //accessFlags |= VK_ACCESS_MEMORY_WRITE_BIT; + stages |= VK_PIPELINE_STAGE_2_TRANSFER_BIT; + accessFlags |= VK_ACCESS_2_TRANSFER_WRITE_BIT; } if constexpr ((TSyncOp & HOST_WRITE) != 0) { - stages |= VK_PIPELINE_STAGE_HOST_BIT; - accessFlags |= VK_ACCESS_HOST_WRITE_BIT; + stages |= VK_PIPELINE_STAGE_2_HOST_BIT; + accessFlags |= VK_ACCESS_2_HOST_WRITE_BIT; } if constexpr ((TSyncOp & HOST_READ) != 0) { - stages |= VK_PIPELINE_STAGE_HOST_BIT; - accessFlags |= VK_ACCESS_HOST_READ_BIT; + stages |= VK_PIPELINE_STAGE_2_HOST_BIT; + accessFlags |= VK_ACCESS_2_HOST_READ_BIT; } if constexpr ((TSyncOp & IMAGE_READ) != 0) { - stages |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - accessFlags |= VK_ACCESS_SHADER_READ_BIT; + stages |= VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; + accessFlags |= VK_ACCESS_2_SHADER_READ_BIT; - stages |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - accessFlags |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT; + stages |= VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + accessFlags |= VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT; - stages |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; - accessFlags |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT; + stages |= VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT; + accessFlags |= VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT; } if constexpr ((TSyncOp & IMAGE_WRITE) != 0) { - stages |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - accessFlags |= VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + stages |= VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + accessFlags |= VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; - stages |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; - accessFlags |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + stages |= VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT; + accessFlags |= VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; } } template void barrier_bufferRange(VkBuffer buffer, VkDeviceSize offset, VkDeviceSize size) { - VkBufferMemoryBarrier bufMemBarrier{}; - bufMemBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; + VkBufferMemoryBarrier2 bufMemBarrier{}; + bufMemBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2; bufMemBarrier.pNext = nullptr; bufMemBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; bufMemBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - VkPipelineStageFlags srcStages = 0; - VkPipelineStageFlags dstStages = 0; - - bufMemBarrier.srcAccessMask = 0; - bufMemBarrier.dstAccessMask = 0; - - barrier_calcStageAndMask(srcStages, bufMemBarrier.srcAccessMask); - barrier_calcStageAndMask(dstStages, bufMemBarrier.dstAccessMask); + barrier_calcStageAndMask(bufMemBarrier.srcStageMask, bufMemBarrier.srcAccessMask); + barrier_calcStageAndMask(bufMemBarrier.dstStageMask, bufMemBarrier.dstAccessMask); bufMemBarrier.buffer = buffer; bufMemBarrier.offset = offset; bufMemBarrier.size = size; - vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStages, dstStages, 0, 0, nullptr, 1, &bufMemBarrier, 0, nullptr); + + VkDependencyInfo depInfo{}; + depInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + depInfo.pNext = nullptr; + depInfo.bufferMemoryBarrierCount = 1; + depInfo.pBufferMemoryBarriers = &bufMemBarrier; + vkCmdPipelineBarrier2KHR(m_state.currentCommandBuffer, &depInfo); } template void barrier_bufferRange(VkBuffer bufferA, VkDeviceSize offsetA, VkDeviceSize sizeA, VkBuffer bufferB, VkDeviceSize offsetB, VkDeviceSize sizeB) { - VkPipelineStageFlags srcStagesA = 0; - VkPipelineStageFlags dstStagesA = 0; - VkPipelineStageFlags srcStagesB = 0; - VkPipelineStageFlags dstStagesB = 0; + VkBufferMemoryBarrier2 bufMemBarrier2[2] = {}; + bufMemBarrier2[0].sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2_KHR; + bufMemBarrier2[0].pNext = nullptr; + bufMemBarrier2[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufMemBarrier2[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufMemBarrier2[0].buffer = bufferA; + bufMemBarrier2[0].offset = offsetA; + bufMemBarrier2[0].size = sizeA; + barrier_calcStageAndMask(bufMemBarrier2[0].srcStageMask, bufMemBarrier2[0].srcAccessMask); + barrier_calcStageAndMask(bufMemBarrier2[0].dstStageMask, bufMemBarrier2[0].dstAccessMask); - VkBufferMemoryBarrier bufMemBarrier[2]; + bufMemBarrier2[1].sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2_KHR; + bufMemBarrier2[1].pNext = nullptr; + bufMemBarrier2[1].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufMemBarrier2[1].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufMemBarrier2[1].buffer = bufferB; + bufMemBarrier2[1].offset = offsetB; + bufMemBarrier2[1].size = sizeB; + barrier_calcStageAndMask(bufMemBarrier2[1].srcStageMask, bufMemBarrier2[1].srcAccessMask); + barrier_calcStageAndMask(bufMemBarrier2[1].dstStageMask, bufMemBarrier2[1].dstAccessMask); - bufMemBarrier[0].sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; - bufMemBarrier[0].pNext = nullptr; - bufMemBarrier[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - bufMemBarrier[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - bufMemBarrier[0].srcAccessMask = 0; - bufMemBarrier[0].dstAccessMask = 0; - barrier_calcStageAndMask(srcStagesA, bufMemBarrier[0].srcAccessMask); - barrier_calcStageAndMask(dstStagesA, bufMemBarrier[0].dstAccessMask); - bufMemBarrier[0].buffer = bufferA; - bufMemBarrier[0].offset = offsetA; - bufMemBarrier[0].size = sizeA; - - bufMemBarrier[1].sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; - bufMemBarrier[1].pNext = nullptr; - bufMemBarrier[1].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - bufMemBarrier[1].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - bufMemBarrier[1].srcAccessMask = 0; - bufMemBarrier[1].dstAccessMask = 0; - barrier_calcStageAndMask(srcStagesB, bufMemBarrier[1].srcAccessMask); - barrier_calcStageAndMask(dstStagesB, bufMemBarrier[1].dstAccessMask); - bufMemBarrier[1].buffer = bufferB; - bufMemBarrier[1].offset = offsetB; - bufMemBarrier[1].size = sizeB; - - vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStagesA|srcStagesB, dstStagesA|dstStagesB, 0, 0, nullptr, 2, bufMemBarrier, 0, nullptr); + VkDependencyInfo dependencyInfo = {}; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.pNext = nullptr; + dependencyInfo.bufferMemoryBarrierCount = 2; + dependencyInfo.pBufferMemoryBarriers = bufMemBarrier2; + vkCmdPipelineBarrier2KHR(m_state.currentCommandBuffer, &dependencyInfo); } void barrier_sequentializeTransfer() { - VkMemoryBarrier memBarrier{}; - memBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; - memBarrier.pNext = nullptr; + VkMemoryBarrier2 memoryBarrier2{}; + memoryBarrier2.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2; + memoryBarrier2.pNext = nullptr; + memoryBarrier2.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; + memoryBarrier2.srcAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT; + memoryBarrier2.dstStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT; + memoryBarrier2.dstAccessMask = VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT; - VkPipelineStageFlags srcStages = VK_PIPELINE_STAGE_TRANSFER_BIT; - VkPipelineStageFlags dstStages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - - memBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; - memBarrier.dstAccessMask = 0; - - memBarrier.srcAccessMask |= (VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT); - memBarrier.dstAccessMask |= (VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT); - - vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStages, dstStages, 0, 1, &memBarrier, 0, nullptr, 0, nullptr); + VkDependencyInfo dependencyInfo{}; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.pNext = nullptr; + dependencyInfo.memoryBarrierCount = 1; + dependencyInfo.pMemoryBarriers = &memoryBarrier2; + dependencyInfo.bufferMemoryBarrierCount = 0; + dependencyInfo.imageMemoryBarrierCount = 0; + vkCmdPipelineBarrier2KHR(m_state.currentCommandBuffer, &dependencyInfo); } void barrier_sequentializeCommand() { - VkPipelineStageFlags srcStages = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; - VkPipelineStageFlags dstStages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkMemoryBarrier2 memoryBarrier = {}; + memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2; + memoryBarrier.srcStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT_KHR; + memoryBarrier.srcAccessMask = 0; + memoryBarrier.dstStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT_KHR; + memoryBarrier.dstAccessMask = 0; - vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStages, dstStages, 0, 0, nullptr, 0, nullptr, 0, nullptr); + VkDependencyInfo dependencyInfo = {}; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.dependencyFlags = 0; + dependencyInfo.memoryBarrierCount = 1; + dependencyInfo.pMemoryBarriers = &memoryBarrier; + vkCmdPipelineBarrier2KHR(m_state.currentCommandBuffer, &dependencyInfo); } template void barrier_image(VkImage imageVk, VkImageSubresourceRange& subresourceRange, VkImageLayout oldLayout, VkImageLayout newLayout) { - VkPipelineStageFlags srcStages = 0; - VkPipelineStageFlags dstStages = 0; + VkImageMemoryBarrier2 imageMemBarrier2{}; + imageMemBarrier2.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + imageMemBarrier2.oldLayout = oldLayout; + imageMemBarrier2.newLayout = newLayout; + imageMemBarrier2.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + imageMemBarrier2.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + imageMemBarrier2.image = imageVk; + imageMemBarrier2.subresourceRange = subresourceRange; - VkImageMemoryBarrier imageMemBarrier{}; - imageMemBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; - imageMemBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - imageMemBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - imageMemBarrier.srcAccessMask = 0; - imageMemBarrier.dstAccessMask = 0; - barrier_calcStageAndMask(srcStages, imageMemBarrier.srcAccessMask); - barrier_calcStageAndMask(dstStages, imageMemBarrier.dstAccessMask); - imageMemBarrier.image = imageVk; - imageMemBarrier.subresourceRange = subresourceRange; - imageMemBarrier.oldLayout = oldLayout; - imageMemBarrier.newLayout = newLayout; + barrier_calcStageAndMask(imageMemBarrier2.srcStageMask, imageMemBarrier2.srcAccessMask); + barrier_calcStageAndMask(imageMemBarrier2.dstStageMask, imageMemBarrier2.dstAccessMask); - vkCmdPipelineBarrier(m_state.currentCommandBuffer, - srcStages, dstStages, - 0, - 0, NULL, - 0, NULL, - 1, &imageMemBarrier); + VkDependencyInfo dependencyInfo{}; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.imageMemoryBarrierCount = 1; + dependencyInfo.pImageMemoryBarriers = &imageMemBarrier2; + vkCmdPipelineBarrier2KHR(m_state.currentCommandBuffer, &dependencyInfo); } template @@ -942,7 +933,6 @@ private: vkTexture->SetImageLayout(subresourceRange, newLayout); } - public: bool GetDisableMultithreadedCompilation() const { return m_featureControl.disableMultithreadedCompilation; } bool UseTFViaSSBO() const { return m_featureControl.mode.useTFEmulationViaSSBO; } diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp index 320357f1..b6cae7f7 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp @@ -1033,29 +1033,18 @@ void VulkanRenderer::sync_inputTexturesChanged() // barrier here if (writeFlushRequired) { - VkMemoryBarrier memoryBarrier{}; - memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; - memoryBarrier.srcAccessMask = 0; - memoryBarrier.dstAccessMask = 0; - - VkPipelineStageFlags srcStage = 0; - VkPipelineStageFlags dstStage = 0; - - // src - srcStage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - memoryBarrier.srcAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - - srcStage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; - memoryBarrier.srcAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; - - // dst - dstStage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - memoryBarrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT; - - dstStage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; - memoryBarrier.dstAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT; - - vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStage, dstStage, 0, 1, &memoryBarrier, 0, nullptr, 0, nullptr); + VkMemoryBarrier2 memoryBarrier2{}; + memoryBarrier2.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2_KHR; + memoryBarrier2.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR | VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT_KHR | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT_KHR; + memoryBarrier2.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT_KHR | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT_KHR; + memoryBarrier2.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR | VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT_KHR | VK_PIPELINE_STAGE_2_GEOMETRY_SHADER_BIT_KHR | VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT_KHR | VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT_KHR | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT_KHR; + memoryBarrier2.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT_KHR | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT_KHR | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT_KHR | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT_KHR | VK_ACCESS_2_SHADER_READ_BIT_KHR; + VkDependencyInfo dependencyInfo{}; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO_KHR; + dependencyInfo.dependencyFlags = 0; + dependencyInfo.memoryBarrierCount = 1; + dependencyInfo.pMemoryBarriers = &memoryBarrier2; + vkCmdPipelineBarrier2KHR(m_state.currentCommandBuffer, &dependencyInfo); performanceMonitor.vk.numDrawBarriersPerFrame.increment(); From b8d81283e86f91238a166b25ac46281620a3260b Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Tue, 27 Feb 2024 00:15:43 +0100 Subject: [PATCH 023/299] Vulkan: Remove unnecessary index buffer for backbuffer drawcall --- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 18 +----------------- .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 7 ------- 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 631f1d0c..a86d3a1f 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -540,7 +540,6 @@ VulkanRenderer::VulkanRenderer() QueryMemoryInfo(); QueryAvailableFormats(); - CreateBackbufferIndexBuffer(); CreateCommandPool(); CreateCommandBuffers(); CreateDescriptorPool(); @@ -624,7 +623,6 @@ VulkanRenderer::~VulkanRenderer() DeleteNullObjects(); // delete buffers - memoryManager->DeleteBuffer(m_indexBuffer, m_indexBufferMemory); memoryManager->DeleteBuffer(m_uniformVarBuffer, m_uniformVarBufferMemory); memoryManager->DeleteBuffer(m_textureReadbackBuffer, m_textureReadbackBufferMemory); memoryManager->DeleteBuffer(m_xfbRingBuffer, m_xfbRingBufferMemory); @@ -2836,18 +2834,6 @@ void VulkanRenderer::ClearColorImage(LatteTextureVk* vkTexture, uint32 sliceInde vkTexture->SetImageLayout(subresourceRange, outputLayout); } -void VulkanRenderer::CreateBackbufferIndexBuffer() -{ - const VkDeviceSize bufferSize = sizeof(uint16) * 6; - memoryManager->CreateBuffer(bufferSize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, m_indexBuffer, m_indexBufferMemory); - - uint16* data; - vkMapMemory(m_logicalDevice, m_indexBufferMemory, 0, bufferSize, 0, (void**)&data); - const uint16 tmp[] = { 0, 1, 2, 3, 4, 5 }; - std::copy(std::begin(tmp), std::end(tmp), data); - vkUnmapMemory(m_logicalDevice, m_indexBufferMemory); -} - void VulkanRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutputShader* shader, bool useLinearTexFilter, sint32 imageX, sint32 imageY, sint32 imageWidth, sint32 imageHeight, bool padView, bool clearBackground) { if(!AcquireNextSwapchainImage(!padView)) @@ -2906,11 +2892,9 @@ void VulkanRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu vkCmdBindPipeline(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); m_state.currentPipeline = pipeline; - vkCmdBindIndexBuffer(m_state.currentCommandBuffer, m_indexBuffer, 0, VK_INDEX_TYPE_UINT16); - vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &descriptSet, 0, nullptr); - vkCmdDrawIndexed(m_state.currentCommandBuffer, 6, 1, 0, 0, 0); + vkCmdDraw(m_state.currentCommandBuffer, 6, 1, 0, 0); vkCmdEndRenderPass(m_state.currentCommandBuffer); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index 7565d260..3e55fc0c 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -548,15 +548,11 @@ private: void sync_RenderPassStoreTextures(CachedFBOVk* fboVk); // command buffer - VkCommandBuffer getCurrentCommandBuffer() const { return m_state.currentCommandBuffer; } // uniform void uniformData_updateUniformVars(uint32 shaderStageIndex, LatteDecompilerShader* shader); - // indices - void CreateBackbufferIndexBuffer(); - // misc void CreatePipelineCache(); VkPipelineShaderStageCreateInfo CreatePipelineShaderStageCreateInfo(VkShaderStageFlagBits stage, VkShaderModule& module, const char* entryName) const; @@ -580,9 +576,6 @@ private: void occlusionQuery_notifyBeginCommandBuffer(); private: - VkBuffer m_indexBuffer = VK_NULL_HANDLE; - VkDeviceMemory m_indexBufferMemory = VK_NULL_HANDLE; - std::vector m_layerNames; VkInstance m_instance = VK_NULL_HANDLE; VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE; From 9f9bc9865f23d3a1f07ce905003b7d5d640aab82 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Fri, 8 Mar 2024 03:12:26 +0100 Subject: [PATCH 024/299] Vulkan: Avoid calling vkCmdClearColorImage() on compressed textures This is not allowed according to the spec and can crash drivers. Fixes #1100 --- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 58 ++++++++++++------- .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 1 - 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index a86d3a1f..bb83607f 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -1412,8 +1412,7 @@ bool VulkanRenderer::IsSwapchainInfoValid(bool mainWindow) const void VulkanRenderer::CreateNullTexture(NullTexture& nullTex, VkImageType imageType) { - // these are used when the game requests NULL ptr textures or buffers - // texture + // these are used when the game requests NULL ptr textures VkImageCreateInfo imageInfo{}; imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; if (imageType == VK_IMAGE_TYPE_1D) @@ -2818,6 +2817,35 @@ void VulkanRenderer::ClearColorImageRaw(VkImage image, uint32 sliceIndex, uint32 void VulkanRenderer::ClearColorImage(LatteTextureVk* vkTexture, uint32 sliceIndex, uint32 mipIndex, const VkClearColorValue& color, VkImageLayout outputLayout) { + if(vkTexture->isDepth) + { + cemu_assert_suspicious(); + return; + } + if (vkTexture->IsCompressedFormat()) + { + // vkCmdClearColorImage cannot be called on compressed formats + // for now we ignore affected clears but still transition the image to the correct layout + auto imageObj = vkTexture->GetImageObj(); + imageObj->flagForCurrentCommandBuffer(); + VkImageSubresourceLayers subresourceRange{}; + subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + subresourceRange.mipLevel = mipIndex; + subresourceRange.baseArrayLayer = sliceIndex; + subresourceRange.layerCount = 1; + barrier_image(vkTexture, subresourceRange, outputLayout); + if(color.float32[0] == 0.0f && color.float32[1] == 0.0f && color.float32[2] == 0.0f && color.float32[3] == 0.0f) + { + static bool dbgMsgPrinted = false; + if(!dbgMsgPrinted) + { + cemuLog_logDebug(LogType::Force, "Unsupported compressed texture clear to zero"); + dbgMsgPrinted = true; + } + } + return; + } + VkImageSubresourceRange subresourceRange; subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; @@ -3154,32 +3182,18 @@ void VulkanRenderer::texture_clearSlice(LatteTexture* hostTexture, sint32 sliceI else { cemu_assert_debug(vkTexture->dim != Latte::E_DIM::DIM_3D); - if (hostTexture->IsCompressedFormat()) - { - auto imageObj = vkTexture->GetImageObj(); - imageObj->flagForCurrentCommandBuffer(); - - cemuLog_logDebug(LogType::Force, "Compressed texture ({}/{} fmt {:04x}) unsupported clear", vkTexture->width, vkTexture->height, (uint32)vkTexture->format); - - VkImageSubresourceLayers subresourceRange{}; - subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - subresourceRange.mipLevel = mipIndex; - subresourceRange.baseArrayLayer = sliceIndex; - subresourceRange.layerCount = 1; - barrier_image(vkTexture, subresourceRange, VK_IMAGE_LAYOUT_GENERAL); - } - else - { - ClearColorImage(vkTexture, sliceIndex, mipIndex, { 0,0,0,0 }, VK_IMAGE_LAYOUT_GENERAL); - } + ClearColorImage(vkTexture, sliceIndex, mipIndex, { 0,0,0,0 }, VK_IMAGE_LAYOUT_GENERAL); } } void VulkanRenderer::texture_clearColorSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex, float r, float g, float b, float a) { auto vkTexture = (LatteTextureVk*)hostTexture; - cemu_assert_debug(vkTexture->dim != Latte::E_DIM::DIM_3D); - ClearColorImage(vkTexture, sliceIndex, mipIndex, { r,g,b,a }, VK_IMAGE_LAYOUT_GENERAL); + if(vkTexture->dim == Latte::E_DIM::DIM_3D) + { + cemu_assert_unimplemented(); + } + ClearColorImage(vkTexture, sliceIndex, mipIndex, {r, g, b, a}, VK_IMAGE_LAYOUT_GENERAL); } void VulkanRenderer::texture_clearDepthSlice(LatteTexture* hostTexture, uint32 sliceIndex, sint32 mipIndex, bool clearDepth, bool clearStencil, float depthValue, uint32 stencilValue) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index 3e55fc0c..d4eda785 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -130,7 +130,6 @@ class VulkanRenderer : public Renderer using QueueFamilyIndices = SwapchainInfoVk::QueueFamilyIndices; static const inline int UNIFORMVAR_RINGBUFFER_SIZE = 1024 * 1024 * 16; // 16MB - static const inline int INDEX_STREAM_BUFFER_SIZE = 16 * 1024 * 1024; // 16 MB static const inline int TEXTURE_READBACK_SIZE = 32 * 1024 * 1024; // 32 MB From ea68f787eb6b8054805502a8b4aabae08ae59d94 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Fri, 8 Mar 2024 14:41:01 +0100 Subject: [PATCH 025/299] Vulkan: For MSAA surface copies make the target MSAA too Fixes #1108 --- src/Cafe/HW/Latte/Core/LatteSurfaceCopy.cpp | 2 +- src/Cafe/HW/Latte/ISA/LatteReg.h | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Cafe/HW/Latte/Core/LatteSurfaceCopy.cpp b/src/Cafe/HW/Latte/Core/LatteSurfaceCopy.cpp index 4f5b24ad..45be6843 100644 --- a/src/Cafe/HW/Latte/Core/LatteSurfaceCopy.cpp +++ b/src/Cafe/HW/Latte/Core/LatteSurfaceCopy.cpp @@ -37,7 +37,7 @@ void LatteSurfaceCopy_copySurfaceNew(MPTR srcPhysAddr, MPTR srcMipAddr, uint32 s if (!destinationTexture) { LatteTexture* renderTargetConf = nullptr; - destinationView = LatteTexture_CreateMapping(dstPhysAddr, dstMipAddr, dstWidth, dstHeight, dstDepth, dstPitch, dstTilemode, dstSwizzle, dstLevel, 1, dstSlice, 1, dstSurfaceFormat, dstDim, Latte::E_DIM::DIM_2D, false); + destinationView = LatteTexture_CreateMapping(dstPhysAddr, dstMipAddr, dstWidth, dstHeight, dstDepth, dstPitch, dstTilemode, dstSwizzle, dstLevel, 1, dstSlice, 1, dstSurfaceFormat, dstDim, Latte::IsMSAA(dstDim) ? Latte::E_DIM::DIM_2D_MSAA : Latte::E_DIM::DIM_2D, false); destinationTexture = destinationView->baseTexture; } // copy texture diff --git a/src/Cafe/HW/Latte/ISA/LatteReg.h b/src/Cafe/HW/Latte/ISA/LatteReg.h index d571dc6e..d1a2a028 100644 --- a/src/Cafe/HW/Latte/ISA/LatteReg.h +++ b/src/Cafe/HW/Latte/ISA/LatteReg.h @@ -345,6 +345,11 @@ namespace Latte return IsCompressedFormat((Latte::E_HWSURFFMT)((uint32)format & 0x3F)); } + inline bool IsMSAA(Latte::E_DIM dim) + { + return dim == E_DIM::DIM_2D_MSAA || dim == E_DIM::DIM_2D_ARRAY_MSAA; + } + enum GPU_LIMITS { NUM_VERTEX_BUFFERS = 16, From b390023bc5b04a0b50c6fd2a1bc10feb19a22f59 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Fri, 8 Mar 2024 14:48:59 +0100 Subject: [PATCH 026/299] README.md: Fix minor ambiguity --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e57cb483..dfd35791 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Before submitting a pull request, please read and follow our code style guidelin If coding isn't your thing, testing games and making detailed bug reports or updating the (usually outdated) compatibility wiki is also appreciated! -Questions about Cemu's software architecture can also be answered on Discord (through the Matrix bridge). +Questions about Cemu's software architecture can also be answered on Discord (or through the Matrix bridge). ## License Cemu is licensed under [Mozilla Public License 2.0](/LICENSE.txt). Exempt from this are all files in the dependencies directory for which the licenses of the original code apply as well as some individual files in the src folder, as specified in those file headers respectively. From d9e8ca2c833e2b2adf8f1c1cc71f7846fd87b816 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 9 Mar 2024 02:25:40 +0100 Subject: [PATCH 027/299] Revert "Vulkan: Update some code to use VK_KHR_synchronization2" This reverts commit 8f1cd4f9255e16aeddb2e72d35a47f37e1e478bc. We received reports from users stuck with Vulkan drivers from 2019. (E.g. Kepler on Windows). So let's not unnecessarily increase the Vulkan requirement for now and postpone this to after the next stable release --- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 30 +-- .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 220 +++++++++--------- .../Renderer/Vulkan/VulkanRendererCore.cpp | 35 ++- 3 files changed, 145 insertions(+), 140 deletions(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index bb83607f..8711359e 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -468,15 +468,6 @@ VulkanRenderer::VulkanRenderer() void* deviceExtensionFeatures = nullptr; - // enable VK_KHR_synchonization_2 - VkPhysicalDeviceSynchronization2FeaturesKHR sync2Feature{}; - { - sync2Feature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES_KHR; - sync2Feature.pNext = deviceExtensionFeatures; - deviceExtensionFeatures = &sync2Feature; - sync2Feature.synchronization2 = VK_TRUE; - } - // enable VK_EXT_pipeline_creation_cache_control VkPhysicalDevicePipelineCreationCacheControlFeaturesEXT cacheControlFeature{}; if (m_featureControl.deviceExtensions.pipeline_creation_cache_control) @@ -2875,20 +2866,13 @@ void VulkanRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu ClearColorbuffer(padView); // barrier for input texture - { - VkMemoryBarrier2 memoryBarrier2{}; - memoryBarrier2.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2; - memoryBarrier2.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR | VK_PIPELINE_STAGE_2_TRANSFER_BIT_KHR; - memoryBarrier2.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR | VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT_KHR | VK_PIPELINE_STAGE_2_GEOMETRY_SHADER_BIT_KHR | VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT_KHR; - memoryBarrier2.srcAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT; - memoryBarrier2.dstAccessMask = VK_ACCESS_2_MEMORY_READ_BIT; - VkDependencyInfo dependencyInfo{}; - dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; - dependencyInfo.dependencyFlags = 0; - dependencyInfo.memoryBarrierCount = 1; - dependencyInfo.pMemoryBarriers = &memoryBarrier2; - vkCmdPipelineBarrier2KHR(m_state.currentCommandBuffer, &dependencyInfo); - } + VkMemoryBarrier memoryBarrier{}; + memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; + VkPipelineStageFlags srcStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT; + VkPipelineStageFlags dstStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + memoryBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; + memoryBarrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_SHADER_READ_BIT; + vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStage, dstStage, 0, 1, &memoryBarrier, 0, nullptr, 0, nullptr); auto pipeline = backbufferBlit_createGraphicsPipeline(m_swapchainDescriptorSetLayout, padView, shader); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index d4eda785..479c9e54 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -720,192 +720,201 @@ private: IMAGE_READ = 0x20, IMAGE_WRITE = 0x40, + }; template - void barrier_calcStageAndMask(VkPipelineStageFlags2& stages, VkAccessFlags2& accessFlags) + void barrier_calcStageAndMask(VkPipelineStageFlags& stages, VkAccessFlags& accessFlags) { stages = 0; accessFlags = 0; if constexpr ((TSyncOp & BUFFER_SHADER_READ) != 0) { - // in theory: VK_ACCESS_2_INDEX_READ_BIT should be set here too but indices are currently separated - stages |= VK_PIPELINE_STAGE_2_VERTEX_INPUT_BIT | VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; - accessFlags |= VK_ACCESS_2_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_2_UNIFORM_READ_BIT | VK_ACCESS_2_SHADER_READ_BIT; + // in theory: VK_ACCESS_INDEX_READ_BIT should be set here too but indices are currently separated + stages |= VK_PIPELINE_STAGE_VERTEX_INPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + accessFlags |= VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_UNIFORM_READ_BIT | VK_ACCESS_SHADER_READ_BIT; } - + if constexpr ((TSyncOp & BUFFER_SHADER_WRITE) != 0) { - stages |= VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; - accessFlags |= VK_ACCESS_2_SHADER_WRITE_BIT; + stages |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + accessFlags |= VK_ACCESS_SHADER_WRITE_BIT; } if constexpr ((TSyncOp & ANY_TRANSFER) != 0) { - stages |= VK_PIPELINE_STAGE_2_TRANSFER_BIT; - accessFlags |= VK_ACCESS_2_TRANSFER_READ_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT; + //stages |= VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_HOST_BIT; + //accessFlags |= VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_READ_BIT | VK_ACCESS_HOST_WRITE_BIT; + stages |= VK_PIPELINE_STAGE_TRANSFER_BIT; + accessFlags |= VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; + + //accessFlags |= VK_ACCESS_MEMORY_READ_BIT; + //accessFlags |= VK_ACCESS_MEMORY_WRITE_BIT; } if constexpr ((TSyncOp & TRANSFER_READ) != 0) { - stages |= VK_PIPELINE_STAGE_2_TRANSFER_BIT; - accessFlags |= VK_ACCESS_2_TRANSFER_READ_BIT; + stages |= VK_PIPELINE_STAGE_TRANSFER_BIT; + accessFlags |= VK_ACCESS_TRANSFER_READ_BIT; + + //accessFlags |= VK_ACCESS_MEMORY_READ_BIT; } if constexpr ((TSyncOp & TRANSFER_WRITE) != 0) { - stages |= VK_PIPELINE_STAGE_2_TRANSFER_BIT; - accessFlags |= VK_ACCESS_2_TRANSFER_WRITE_BIT; + stages |= VK_PIPELINE_STAGE_TRANSFER_BIT; + accessFlags |= VK_ACCESS_TRANSFER_WRITE_BIT; + + //accessFlags |= VK_ACCESS_MEMORY_WRITE_BIT; } if constexpr ((TSyncOp & HOST_WRITE) != 0) { - stages |= VK_PIPELINE_STAGE_2_HOST_BIT; - accessFlags |= VK_ACCESS_2_HOST_WRITE_BIT; + stages |= VK_PIPELINE_STAGE_HOST_BIT; + accessFlags |= VK_ACCESS_HOST_WRITE_BIT; } if constexpr ((TSyncOp & HOST_READ) != 0) { - stages |= VK_PIPELINE_STAGE_2_HOST_BIT; - accessFlags |= VK_ACCESS_2_HOST_READ_BIT; + stages |= VK_PIPELINE_STAGE_HOST_BIT; + accessFlags |= VK_ACCESS_HOST_READ_BIT; } if constexpr ((TSyncOp & IMAGE_READ) != 0) { - stages |= VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; - accessFlags |= VK_ACCESS_2_SHADER_READ_BIT; + stages |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + accessFlags |= VK_ACCESS_SHADER_READ_BIT; - stages |= VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; - accessFlags |= VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT; + stages |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + accessFlags |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT; - stages |= VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT; - accessFlags |= VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT; + stages |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + accessFlags |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT; } if constexpr ((TSyncOp & IMAGE_WRITE) != 0) { - stages |= VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; - accessFlags |= VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT; + stages |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + accessFlags |= VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - stages |= VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT; - accessFlags |= VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + stages |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + accessFlags |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; } } template void barrier_bufferRange(VkBuffer buffer, VkDeviceSize offset, VkDeviceSize size) { - VkBufferMemoryBarrier2 bufMemBarrier{}; - bufMemBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2; + VkBufferMemoryBarrier bufMemBarrier{}; + bufMemBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; bufMemBarrier.pNext = nullptr; bufMemBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; bufMemBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier_calcStageAndMask(bufMemBarrier.srcStageMask, bufMemBarrier.srcAccessMask); - barrier_calcStageAndMask(bufMemBarrier.dstStageMask, bufMemBarrier.dstAccessMask); + VkPipelineStageFlags srcStages = 0; + VkPipelineStageFlags dstStages = 0; + + bufMemBarrier.srcAccessMask = 0; + bufMemBarrier.dstAccessMask = 0; + + barrier_calcStageAndMask(srcStages, bufMemBarrier.srcAccessMask); + barrier_calcStageAndMask(dstStages, bufMemBarrier.dstAccessMask); bufMemBarrier.buffer = buffer; bufMemBarrier.offset = offset; bufMemBarrier.size = size; - - VkDependencyInfo depInfo{}; - depInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; - depInfo.pNext = nullptr; - depInfo.bufferMemoryBarrierCount = 1; - depInfo.pBufferMemoryBarriers = &bufMemBarrier; - vkCmdPipelineBarrier2KHR(m_state.currentCommandBuffer, &depInfo); + vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStages, dstStages, 0, 0, nullptr, 1, &bufMemBarrier, 0, nullptr); } template void barrier_bufferRange(VkBuffer bufferA, VkDeviceSize offsetA, VkDeviceSize sizeA, VkBuffer bufferB, VkDeviceSize offsetB, VkDeviceSize sizeB) { - VkBufferMemoryBarrier2 bufMemBarrier2[2] = {}; - bufMemBarrier2[0].sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2_KHR; - bufMemBarrier2[0].pNext = nullptr; - bufMemBarrier2[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - bufMemBarrier2[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - bufMemBarrier2[0].buffer = bufferA; - bufMemBarrier2[0].offset = offsetA; - bufMemBarrier2[0].size = sizeA; - barrier_calcStageAndMask(bufMemBarrier2[0].srcStageMask, bufMemBarrier2[0].srcAccessMask); - barrier_calcStageAndMask(bufMemBarrier2[0].dstStageMask, bufMemBarrier2[0].dstAccessMask); + VkPipelineStageFlags srcStagesA = 0; + VkPipelineStageFlags dstStagesA = 0; + VkPipelineStageFlags srcStagesB = 0; + VkPipelineStageFlags dstStagesB = 0; - bufMemBarrier2[1].sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2_KHR; - bufMemBarrier2[1].pNext = nullptr; - bufMemBarrier2[1].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - bufMemBarrier2[1].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - bufMemBarrier2[1].buffer = bufferB; - bufMemBarrier2[1].offset = offsetB; - bufMemBarrier2[1].size = sizeB; - barrier_calcStageAndMask(bufMemBarrier2[1].srcStageMask, bufMemBarrier2[1].srcAccessMask); - barrier_calcStageAndMask(bufMemBarrier2[1].dstStageMask, bufMemBarrier2[1].dstAccessMask); + VkBufferMemoryBarrier bufMemBarrier[2]; - VkDependencyInfo dependencyInfo = {}; - dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; - dependencyInfo.pNext = nullptr; - dependencyInfo.bufferMemoryBarrierCount = 2; - dependencyInfo.pBufferMemoryBarriers = bufMemBarrier2; - vkCmdPipelineBarrier2KHR(m_state.currentCommandBuffer, &dependencyInfo); + bufMemBarrier[0].sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; + bufMemBarrier[0].pNext = nullptr; + bufMemBarrier[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufMemBarrier[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufMemBarrier[0].srcAccessMask = 0; + bufMemBarrier[0].dstAccessMask = 0; + barrier_calcStageAndMask(srcStagesA, bufMemBarrier[0].srcAccessMask); + barrier_calcStageAndMask(dstStagesA, bufMemBarrier[0].dstAccessMask); + bufMemBarrier[0].buffer = bufferA; + bufMemBarrier[0].offset = offsetA; + bufMemBarrier[0].size = sizeA; + + bufMemBarrier[1].sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER; + bufMemBarrier[1].pNext = nullptr; + bufMemBarrier[1].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufMemBarrier[1].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufMemBarrier[1].srcAccessMask = 0; + bufMemBarrier[1].dstAccessMask = 0; + barrier_calcStageAndMask(srcStagesB, bufMemBarrier[1].srcAccessMask); + barrier_calcStageAndMask(dstStagesB, bufMemBarrier[1].dstAccessMask); + bufMemBarrier[1].buffer = bufferB; + bufMemBarrier[1].offset = offsetB; + bufMemBarrier[1].size = sizeB; + + vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStagesA|srcStagesB, dstStagesA|dstStagesB, 0, 0, nullptr, 2, bufMemBarrier, 0, nullptr); } void barrier_sequentializeTransfer() { - VkMemoryBarrier2 memoryBarrier2{}; - memoryBarrier2.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2; - memoryBarrier2.pNext = nullptr; - memoryBarrier2.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; - memoryBarrier2.srcAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT; - memoryBarrier2.dstStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT; - memoryBarrier2.dstAccessMask = VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT; + VkMemoryBarrier memBarrier{}; + memBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; + memBarrier.pNext = nullptr; - VkDependencyInfo dependencyInfo{}; - dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; - dependencyInfo.pNext = nullptr; - dependencyInfo.memoryBarrierCount = 1; - dependencyInfo.pMemoryBarriers = &memoryBarrier2; - dependencyInfo.bufferMemoryBarrierCount = 0; - dependencyInfo.imageMemoryBarrierCount = 0; - vkCmdPipelineBarrier2KHR(m_state.currentCommandBuffer, &dependencyInfo); + VkPipelineStageFlags srcStages = VK_PIPELINE_STAGE_TRANSFER_BIT; + VkPipelineStageFlags dstStages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + + memBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; + memBarrier.dstAccessMask = 0; + + memBarrier.srcAccessMask |= (VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT); + memBarrier.dstAccessMask |= (VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT); + + vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStages, dstStages, 0, 1, &memBarrier, 0, nullptr, 0, nullptr); } void barrier_sequentializeCommand() { - VkMemoryBarrier2 memoryBarrier = {}; - memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2; - memoryBarrier.srcStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT_KHR; - memoryBarrier.srcAccessMask = 0; - memoryBarrier.dstStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT_KHR; - memoryBarrier.dstAccessMask = 0; + VkPipelineStageFlags srcStages = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + VkPipelineStageFlags dstStages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; - VkDependencyInfo dependencyInfo = {}; - dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; - dependencyInfo.dependencyFlags = 0; - dependencyInfo.memoryBarrierCount = 1; - dependencyInfo.pMemoryBarriers = &memoryBarrier; - vkCmdPipelineBarrier2KHR(m_state.currentCommandBuffer, &dependencyInfo); + vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStages, dstStages, 0, 0, nullptr, 0, nullptr, 0, nullptr); } template void barrier_image(VkImage imageVk, VkImageSubresourceRange& subresourceRange, VkImageLayout oldLayout, VkImageLayout newLayout) { - VkImageMemoryBarrier2 imageMemBarrier2{}; - imageMemBarrier2.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; - imageMemBarrier2.oldLayout = oldLayout; - imageMemBarrier2.newLayout = newLayout; - imageMemBarrier2.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - imageMemBarrier2.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - imageMemBarrier2.image = imageVk; - imageMemBarrier2.subresourceRange = subresourceRange; + VkPipelineStageFlags srcStages = 0; + VkPipelineStageFlags dstStages = 0; - barrier_calcStageAndMask(imageMemBarrier2.srcStageMask, imageMemBarrier2.srcAccessMask); - barrier_calcStageAndMask(imageMemBarrier2.dstStageMask, imageMemBarrier2.dstAccessMask); + VkImageMemoryBarrier imageMemBarrier{}; + imageMemBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + imageMemBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + imageMemBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + imageMemBarrier.srcAccessMask = 0; + imageMemBarrier.dstAccessMask = 0; + barrier_calcStageAndMask(srcStages, imageMemBarrier.srcAccessMask); + barrier_calcStageAndMask(dstStages, imageMemBarrier.dstAccessMask); + imageMemBarrier.image = imageVk; + imageMemBarrier.subresourceRange = subresourceRange; + imageMemBarrier.oldLayout = oldLayout; + imageMemBarrier.newLayout = newLayout; - VkDependencyInfo dependencyInfo{}; - dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; - dependencyInfo.imageMemoryBarrierCount = 1; - dependencyInfo.pImageMemoryBarriers = &imageMemBarrier2; - vkCmdPipelineBarrier2KHR(m_state.currentCommandBuffer, &dependencyInfo); + vkCmdPipelineBarrier(m_state.currentCommandBuffer, + srcStages, dstStages, + 0, + 0, NULL, + 0, NULL, + 1, &imageMemBarrier); } template @@ -925,6 +934,7 @@ private: vkTexture->SetImageLayout(subresourceRange, newLayout); } + public: bool GetDisableMultithreadedCompilation() const { return m_featureControl.disableMultithreadedCompilation; } bool UseTFViaSSBO() const { return m_featureControl.mode.useTFEmulationViaSSBO; } diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp index b6cae7f7..320357f1 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp @@ -1033,18 +1033,29 @@ void VulkanRenderer::sync_inputTexturesChanged() // barrier here if (writeFlushRequired) { - VkMemoryBarrier2 memoryBarrier2{}; - memoryBarrier2.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2_KHR; - memoryBarrier2.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR | VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT_KHR | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT_KHR; - memoryBarrier2.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT_KHR | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT_KHR; - memoryBarrier2.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT_KHR | VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT_KHR | VK_PIPELINE_STAGE_2_GEOMETRY_SHADER_BIT_KHR | VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT_KHR | VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT_KHR | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT_KHR; - memoryBarrier2.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT_KHR | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT_KHR | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT_KHR | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT_KHR | VK_ACCESS_2_SHADER_READ_BIT_KHR; - VkDependencyInfo dependencyInfo{}; - dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO_KHR; - dependencyInfo.dependencyFlags = 0; - dependencyInfo.memoryBarrierCount = 1; - dependencyInfo.pMemoryBarriers = &memoryBarrier2; - vkCmdPipelineBarrier2KHR(m_state.currentCommandBuffer, &dependencyInfo); + VkMemoryBarrier memoryBarrier{}; + memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; + memoryBarrier.srcAccessMask = 0; + memoryBarrier.dstAccessMask = 0; + + VkPipelineStageFlags srcStage = 0; + VkPipelineStageFlags dstStage = 0; + + // src + srcStage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + memoryBarrier.srcAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + + srcStage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + memoryBarrier.srcAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; + + // dst + dstStage |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + memoryBarrier.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT; + + dstStage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + memoryBarrier.dstAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT; + + vkCmdPipelineBarrier(m_state.currentCommandBuffer, srcStage, dstStage, 0, 1, &memoryBarrier, 0, nullptr, 0, nullptr); performanceMonitor.vk.numDrawBarriersPerFrame.increment(); From f69fddc6e50aabf71d1c78e73d7bcd6545b8ab92 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Sun, 10 Mar 2024 23:25:16 +0100 Subject: [PATCH 028/299] TitleManager: Fix crash when sorting by format (#1113) --- src/gui/components/wxTitleManagerList.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/gui/components/wxTitleManagerList.cpp b/src/gui/components/wxTitleManagerList.cpp index d6ad8118..c02bffb7 100644 --- a/src/gui/components/wxTitleManagerList.cpp +++ b/src/gui/components/wxTitleManagerList.cpp @@ -1143,7 +1143,7 @@ bool wxTitleManagerList::SortFunc(int column, const Type_t& v1, const Type_t& v2 // check column: title id -> type -> path if (column == 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?) + // ensure strong ordering -> use type since only one entry should be now (should be changed if every save for every user is displayed separately?) if (entry1.title_id == entry2.title_id) return SortFunc(ColumnType, v1, v2); @@ -1159,7 +1159,7 @@ bool wxTitleManagerList::SortFunc(int column, const Type_t& v1, const Type_t& v2 } else if (column == ColumnType) { - if(std::underlying_type_t(entry1.type) == std::underlying_type_t(entry2.type)) + if(entry1.type == entry2.type) return SortFunc(-1, v1, v2); return std::underlying_type_t(entry1.type) < std::underlying_type_t(entry2.type); @@ -1178,6 +1178,13 @@ bool wxTitleManagerList::SortFunc(int column, const Type_t& v1, const Type_t& v2 return std::underlying_type_t(entry1.region) < std::underlying_type_t(entry2.region); } + else if (column == ColumnFormat) + { + if(entry1.format == entry2.format) + return SortFunc(ColumnType, v1, v2); + + return std::underlying_type_t(entry1.format) < std::underlying_type_t(entry2.format); + } return false; } From a2d74972d4e0cd3b61dc4deb32566fc385942963 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Mon, 11 Mar 2024 00:55:31 +0100 Subject: [PATCH 029/299] Prevent changing of console language while a game is running (#1114) --- src/gui/MainWindow.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index d271ca3a..311ddfb7 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -1019,8 +1019,11 @@ void MainWindow::OnConsoleLanguage(wxCommandEvent& event) default: cemu_assert_debug(false); } - m_game_list->DeleteCachedStrings(); - m_game_list->ReloadGameEntries(false); + if (m_game_list) + { + m_game_list->DeleteCachedStrings(); + m_game_list->ReloadGameEntries(false); + } g_config.Save(); } @@ -2159,6 +2162,14 @@ void MainWindow::RecreateMenu() optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_PORTUGUESE, _("&Portuguese"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::PT); optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_RUSSIAN, _("&Russian"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::RU); optionsConsoleLanguageMenu->AppendRadioItem(MAINFRAME_MENU_ID_OPTIONS_LANGUAGE_TAIWANESE, _("&Taiwanese"), wxEmptyString)->Check(config.console_language == CafeConsoleLanguage::TW); + if(IsGameLaunched()) + { + auto items = optionsConsoleLanguageMenu->GetMenuItems(); + for (auto& item : items) + { + item->Enable(false); + } + } // options submenu wxMenu* optionsMenu = new wxMenu(); From e1435066ee0ccc65e3ec6244c334214243236883 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Mon, 11 Mar 2024 00:57:31 +0100 Subject: [PATCH 030/299] OpenGL: Fix crash related to wxWidgets handling of vsync (#1112) --- .../HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp | 14 ++++++++++++++ src/Common/GLInclude/GLInclude.h | 4 ++++ 2 files changed, 18 insertions(+) diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp index f09f04f1..8548fa1c 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp @@ -24,11 +24,14 @@ #define STRINGIFY2(X) #X #define STRINGIFY(X) STRINGIFY2(X) +namespace CemuGL +{ #define GLFUNC(__type, __name) __type __name; #define EGLFUNC(__type, __name) __type __name; #include "Common/GLInclude/glFunctions.h" #undef GLFUNC #undef EGLFUNC +} #include "config/ActiveSettings.h" #include "config/LaunchSettings.h" @@ -241,6 +244,17 @@ void LoadOpenGLImports() #undef GLFUNC #undef EGLFUNC } + +#if BOOST_OS_LINUX +// dummy function for all code that is statically linked with cemu and attempts to use eglSwapInterval +// used to suppress wxWidgets calls to eglSwapInterval +extern "C" +EGLAPI EGLBoolean EGLAPIENTRY eglSwapInterval(EGLDisplay dpy, EGLint interval) +{ + return EGL_TRUE; +} +#endif + #elif BOOST_OS_MACOS void LoadOpenGLImports() { diff --git a/src/Common/GLInclude/GLInclude.h b/src/Common/GLInclude/GLInclude.h index bf7a6bf8..86df0232 100644 --- a/src/Common/GLInclude/GLInclude.h +++ b/src/Common/GLInclude/GLInclude.h @@ -36,6 +36,8 @@ typedef struct __GLXFBConfigRec *GLXFBConfig; #endif +namespace CemuGL +{ #define GLFUNC(__type, __name) extern __type __name; #define EGLFUNC(__type, __name) extern __type __name; #include "glFunctions.h" @@ -213,6 +215,8 @@ static void glCompressedTextureSubImage3DWrapper(GLenum target, GLuint texture, glBindTexture(target, originalTexture); } +} +using namespace CemuGL; // this prevents Windows GL.h from being included: #define __gl_h_ #define __GL_H__ From 788da3cdf73741a10f714772b74b0675e2e98282 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Mon, 11 Mar 2024 01:47:31 +0100 Subject: [PATCH 031/299] CafeSystem: Init recompiler after game profile has been loaded (#1115) --- src/Cafe/CafeSystem.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Cafe/CafeSystem.cpp b/src/Cafe/CafeSystem.cpp index 30dab1d4..76f8ae70 100644 --- a/src/Cafe/CafeSystem.cpp +++ b/src/Cafe/CafeSystem.cpp @@ -779,10 +779,10 @@ namespace CafeSystem return r; // setup memory space and PPC recompiler SetupMemorySpace(); - PPCRecompiler_init(); r = SetupExecutable(); // load RPX if (r != STATUS_CODE::SUCCESS) return r; + PPCRecompiler_init(); InitVirtualMlcStorage(); return STATUS_CODE::SUCCESS; } @@ -821,11 +821,11 @@ namespace CafeSystem uint32 h = generateHashFromRawRPXData(execData->data(), execData->size()); sForegroundTitleId = 0xFFFFFFFF00000000ULL | (uint64)h; cemuLog_log(LogType::Force, "Generated placeholder TitleId: {:016x}", sForegroundTitleId); - // setup memory space and ppc recompiler + // setup memory space SetupMemorySpace(); - PPCRecompiler_init(); // load executable SetupExecutable(); + PPCRecompiler_init(); InitVirtualMlcStorage(); return STATUS_CODE::SUCCESS; } From ccabd9315947cedbbf198c1e0dabffe963b67550 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Mon, 11 Mar 2024 02:13:53 +0100 Subject: [PATCH 032/299] Linux: Exit on SIGTERM (#1116) --- src/Common/ExceptionHandler/ExceptionHandler_posix.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Common/ExceptionHandler/ExceptionHandler_posix.cpp b/src/Common/ExceptionHandler/ExceptionHandler_posix.cpp index cf547110..7afbf191 100644 --- a/src/Common/ExceptionHandler/ExceptionHandler_posix.cpp +++ b/src/Common/ExceptionHandler/ExceptionHandler_posix.cpp @@ -155,6 +155,7 @@ void ExceptionHandler_Init() action.sa_handler = handler_SIGINT; sigaction(SIGINT, &action, nullptr); + sigaction(SIGTERM, &action, nullptr); action.sa_flags = SA_SIGINFO; action.sa_handler = nullptr; From bb88b5c36dd145c4d176b26b749c0817c300b2e6 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Mon, 11 Mar 2024 02:40:47 +0100 Subject: [PATCH 033/299] Fix crash introduced by #1115 (#1117) * Revert "CafeSystem: Init recompiler after game profile has been loaded (#1115)" * Instead move gameprofile load call --- src/Cafe/CafeSystem.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Cafe/CafeSystem.cpp b/src/Cafe/CafeSystem.cpp index 76f8ae70..75cb1116 100644 --- a/src/Cafe/CafeSystem.cpp +++ b/src/Cafe/CafeSystem.cpp @@ -748,7 +748,6 @@ namespace CafeSystem } } LoadMainExecutable(); - gameProfile_load(); return STATUS_CODE::SUCCESS; } @@ -777,12 +776,13 @@ namespace CafeSystem STATUS_CODE r = LoadAndMountForegroundTitle(titleId); if (r != STATUS_CODE::SUCCESS) return r; + gameProfile_load(); // setup memory space and PPC recompiler SetupMemorySpace(); + PPCRecompiler_init(); r = SetupExecutable(); // load RPX if (r != STATUS_CODE::SUCCESS) return r; - PPCRecompiler_init(); InitVirtualMlcStorage(); return STATUS_CODE::SUCCESS; } @@ -821,11 +821,11 @@ namespace CafeSystem uint32 h = generateHashFromRawRPXData(execData->data(), execData->size()); sForegroundTitleId = 0xFFFFFFFF00000000ULL | (uint64)h; cemuLog_log(LogType::Force, "Generated placeholder TitleId: {:016x}", sForegroundTitleId); - // setup memory space + // setup memory space and ppc recompiler SetupMemorySpace(); + PPCRecompiler_init(); // load executable SetupExecutable(); - PPCRecompiler_init(); InitVirtualMlcStorage(); return STATUS_CODE::SUCCESS; } From 3d0d987d895686d749073006c9eca96aa650b9ac Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 10 Mar 2024 01:10:19 +0100 Subject: [PATCH 034/299] Logging: Introduce logOnce helper For cases where printing a message once is enough and to avoid spamming log.txt --- src/Cemu/Logging/CemuLogging.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Cemu/Logging/CemuLogging.h b/src/Cemu/Logging/CemuLogging.h index 728c8b93..388e51ab 100644 --- a/src/Cemu/Logging/CemuLogging.h +++ b/src/Cemu/Logging/CemuLogging.h @@ -7,7 +7,7 @@ enum class LogType : sint32 // note: IDs must be in range 1-64 Force = 63, // always enabled Placeholder = 62, // always disabled - APIErrors = Force, // alias for Force. Logs bad parameters or other API errors in OS libs + APIErrors = Force, // alias for Force. Logs bad parameters or other API usage mistakes or unintended errors in OS libs CoreinitFile = 0, GX2 = 1, @@ -99,6 +99,8 @@ bool cemuLog_log(LogType type, const T* format, TArgs&&... args) return cemuLog_log(type, format_str, std::forward(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 template bool cemuLog_logDebug(LogType type, TFmt format, TArgs&&... args) From 0993658c82e89dbda35d29b36a69d1e9e3d47678 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 10 Mar 2024 01:21:04 +0100 Subject: [PATCH 035/299] GX2: Rework GX2Set*UniformReg - Use cafeExportRegister() instead of legacy export - Submit as a single PM4 packet - Add logging for the special case of the size parameter (not sure if this is used by any game?) - Add some extra validation and logging which may be helpful to homebrew devs --- src/Cafe/OS/libs/gx2/GX2.cpp | 3 -- src/Cafe/OS/libs/gx2/GX2.h | 2 -- src/Cafe/OS/libs/gx2/GX2_Shader.cpp | 34 +++++++++++++++++++++ src/Cafe/OS/libs/gx2/GX2_shader_legacy.cpp | 35 ---------------------- src/Cafe/OS/libs/nn_act/nn_act.cpp | 2 -- 5 files changed, 34 insertions(+), 42 deletions(-) diff --git a/src/Cafe/OS/libs/gx2/GX2.cpp b/src/Cafe/OS/libs/gx2/GX2.cpp index 82aef164..c2ea34a4 100644 --- a/src/Cafe/OS/libs/gx2/GX2.cpp +++ b/src/Cafe/OS/libs/gx2/GX2.cpp @@ -396,16 +396,13 @@ void gx2_load() osLib_addFunction("gx2", "GX2GetCurrentScanBuffer", gx2Export_GX2GetCurrentScanBuffer); // shader stuff - //osLib_addFunction("gx2", "GX2SetVertexShader", gx2Export_GX2SetVertexShader); osLib_addFunction("gx2", "GX2SetPixelShader", gx2Export_GX2SetPixelShader); osLib_addFunction("gx2", "GX2SetGeometryShader", gx2Export_GX2SetGeometryShader); osLib_addFunction("gx2", "GX2SetComputeShader", gx2Export_GX2SetComputeShader); - osLib_addFunction("gx2", "GX2SetVertexUniformReg", gx2Export_GX2SetVertexUniformReg); osLib_addFunction("gx2", "GX2SetVertexUniformBlock", gx2Export_GX2SetVertexUniformBlock); osLib_addFunction("gx2", "GX2RSetVertexUniformBlock", gx2Export_GX2RSetVertexUniformBlock); osLib_addFunction("gx2", "GX2SetPixelUniformBlock", gx2Export_GX2SetPixelUniformBlock); - osLib_addFunction("gx2", "GX2SetPixelUniformReg", gx2Export_GX2SetPixelUniformReg); osLib_addFunction("gx2", "GX2SetGeometryUniformBlock", gx2Export_GX2SetGeometryUniformBlock); osLib_addFunction("gx2", "GX2SetShaderModeEx", gx2Export_GX2SetShaderModeEx); diff --git a/src/Cafe/OS/libs/gx2/GX2.h b/src/Cafe/OS/libs/gx2/GX2.h index 58d98191..a22719f4 100644 --- a/src/Cafe/OS/libs/gx2/GX2.h +++ b/src/Cafe/OS/libs/gx2/GX2.h @@ -18,11 +18,9 @@ void gx2_load(); void gx2Export_GX2SetPixelShader(PPCInterpreter_t* hCPU); void gx2Export_GX2SetGeometryShader(PPCInterpreter_t* hCPU); void gx2Export_GX2SetComputeShader(PPCInterpreter_t* hCPU); -void gx2Export_GX2SetVertexUniformReg(PPCInterpreter_t* hCPU); void gx2Export_GX2SetVertexUniformBlock(PPCInterpreter_t* hCPU); void gx2Export_GX2RSetVertexUniformBlock(PPCInterpreter_t* hCPU); void gx2Export_GX2SetPixelUniformBlock(PPCInterpreter_t* hCPU); -void gx2Export_GX2SetPixelUniformReg(PPCInterpreter_t* hCPU); void gx2Export_GX2SetGeometryUniformBlock(PPCInterpreter_t* hCPU); void gx2Export_GX2SetShaderModeEx(PPCInterpreter_t* hCPU); void gx2Export_GX2CalcGeometryShaderInputRingBufferSize(PPCInterpreter_t* hCPU); diff --git a/src/Cafe/OS/libs/gx2/GX2_Shader.cpp b/src/Cafe/OS/libs/gx2/GX2_Shader.cpp index ad17dc49..d004288b 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Shader.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_Shader.cpp @@ -417,6 +417,37 @@ namespace GX2 } } + void _GX2SubmitUniformReg(uint32 offsetRegBase, uint32 aluRegisterOffset, uint32be* dataWords, uint32 sizeInU32s) + { + if(aluRegisterOffset&0x8000) + { + cemuLog_logDebug(LogType::Force, "_GX2SubmitUniformReg(): Unhandled loop const special case or invalid offset"); + return; + } + if((aluRegisterOffset+sizeInU32s) > 0x400) + { + cemuLog_logOnce(LogType::APIErrors, "GX2SetVertexUniformReg values are out of range (offset {} + size {} must be equal or smaller than 0x400)", aluRegisterOffset, sizeInU32s); + } + if( (sizeInU32s&3) != 0) + { + cemuLog_logOnce(LogType::APIErrors, "GX2Set*UniformReg must be called with a size that is a multiple of 4 (size: {:})", sizeInU32s); + sizeInU32s &= ~3; + } + GX2ReserveCmdSpace(2 + sizeInU32s); + gx2WriteGather_submit(pm4HeaderType3(IT_SET_ALU_CONST, 1 + sizeInU32s), offsetRegBase + aluRegisterOffset); + gx2WriteGather_submitU32AsLEArray((uint32*)dataWords, sizeInU32s); + } + + void GX2SetVertexUniformReg(uint32 offset, uint32 sizeInU32s, uint32be* values) + { + _GX2SubmitUniformReg(0x400, offset, values, sizeInU32s); + } + + void GX2SetPixelUniformReg(uint32 offset, uint32 sizeInU32s, uint32be* values) + { + _GX2SubmitUniformReg(0, offset, values, sizeInU32s); + } + void GX2ShaderInit() { cafeExportRegister("gx2", GX2CalcFetchShaderSizeEx, LogType::GX2); @@ -428,5 +459,8 @@ namespace GX2 cafeExportRegister("gx2", GX2GetPixelShaderStackEntries, LogType::GX2); cafeExportRegister("gx2", GX2SetFetchShader, LogType::GX2); cafeExportRegister("gx2", GX2SetVertexShader, LogType::GX2); + + cafeExportRegister("gx2", GX2SetVertexUniformReg, LogType::GX2); + cafeExportRegister("gx2", GX2SetPixelUniformReg, LogType::GX2); } } \ No newline at end of file diff --git a/src/Cafe/OS/libs/gx2/GX2_shader_legacy.cpp b/src/Cafe/OS/libs/gx2/GX2_shader_legacy.cpp index 1cb61a7e..b0a5d2fa 100644 --- a/src/Cafe/OS/libs/gx2/GX2_shader_legacy.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_shader_legacy.cpp @@ -270,41 +270,6 @@ void gx2Export_GX2SetComputeShader(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, 0); } -void _GX2SubmitUniformReg(uint32 aluRegisterOffset, MPTR virtualAddress, uint32 count) -{ - uint32* dataWords = (uint32*)memory_getPointerFromVirtualOffset(virtualAddress); - GX2ReserveCmdSpace(2 + (count / 0xFF) * 2 + count); - // write PM4 command(s) - uint32 currentRegisterOffset = aluRegisterOffset; - while (count > 0) - { - uint32 subCount = std::min(count, 0xFFu); // a single command can write at most 0xFF values - gx2WriteGather_submit(pm4HeaderType3(IT_SET_ALU_CONST, 1 + subCount), - currentRegisterOffset); - gx2WriteGather_submitU32AsLEArray(dataWords, subCount); - - dataWords += subCount; - count -= subCount; - currentRegisterOffset += subCount; - } -} - -void gx2Export_GX2SetVertexUniformReg(PPCInterpreter_t* hCPU) -{ - cemuLog_log(LogType::GX2, "GX2SetVertexUniformReg(0x{:08x},0x{:x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); - _GX2SubmitUniformReg(hCPU->gpr[3] + 0x400, hCPU->gpr[5], hCPU->gpr[4]); - cemu_assert_debug((hCPU->gpr[3] + hCPU->gpr[4]) <= 0x400); - osLib_returnFromFunction(hCPU, 0); -} - -void gx2Export_GX2SetPixelUniformReg(PPCInterpreter_t* hCPU) -{ - cemuLog_log(LogType::GX2, "GX2SetPixelUniformReg(0x{:08x},0x{:x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); - _GX2SubmitUniformReg(hCPU->gpr[3], hCPU->gpr[5], hCPU->gpr[4]); - cemu_assert_debug((hCPU->gpr[3] + hCPU->gpr[4]) <= 0x400); - osLib_returnFromFunction(hCPU, 0); -} - void _GX2SubmitUniformBlock(uint32 registerBase, uint32 index, MPTR virtualAddress, uint32 size) { GX2ReserveCmdSpace(9); diff --git a/src/Cafe/OS/libs/nn_act/nn_act.cpp b/src/Cafe/OS/libs/nn_act/nn_act.cpp index 0fd9df5a..2a9f61bc 100644 --- a/src/Cafe/OS/libs/nn_act/nn_act.cpp +++ b/src/Cafe/OS/libs/nn_act/nn_act.cpp @@ -543,8 +543,6 @@ void nnActExport_GetDefaultAccount(PPCInterpreter_t* hCPU) void nnActExport_GetSlotNo(PPCInterpreter_t* hCPU) { // id of active account - // uint8 GetSlotNo(void); - cemuLog_logDebug(LogType::Force, "nn_act.GetSlotNo()"); osLib_returnFromFunction(hCPU, 1); // 1 is the first slot (0 is invalid) } From dd7cb74cd21202471634e991865be242f8e45c58 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 10 Mar 2024 23:36:47 +0100 Subject: [PATCH 036/299] Latte: Small refactor and clean up for texture size code --- .gitignore | 1 + src/Cafe/HW/Latte/Core/LatteBufferCache.cpp | 2 +- src/Cafe/HW/Latte/Core/LatteCachedFBO.h | 4 +- src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp | 28 ++++------- src/Cafe/HW/Latte/Core/LatteTexture.cpp | 8 +-- src/Cafe/HW/Latte/Core/LatteTexture.h | 25 +++++++++- src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp | 50 ++----------------- .../Latte/Renderer/OpenGL/OpenGLRenderer.cpp | 23 ++++----- .../Renderer/OpenGL/OpenGLSurfaceCopy.cpp | 5 +- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 2 +- .../Renderer/Vulkan/VulkanSurfaceCopy.cpp | 22 +++----- src/Cafe/OS/libs/gx2/GX2_Resource.cpp | 2 +- src/Cemu/Logging/CemuLogging.h | 2 + 13 files changed, 64 insertions(+), 110 deletions(-) diff --git a/.gitignore b/.gitignore index 18f14cf3..c10b38da 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ bin/sdcard/* bin/screenshots/* bin/dump/* bin/cafeLibs/* +bin/keys.txt !bin/shaderCache/info.txt bin/shaderCache/* diff --git a/src/Cafe/HW/Latte/Core/LatteBufferCache.cpp b/src/Cafe/HW/Latte/Core/LatteBufferCache.cpp index 92c2d1b0..716312a3 100644 --- a/src/Cafe/HW/Latte/Core/LatteBufferCache.cpp +++ b/src/Cafe/HW/Latte/Core/LatteBufferCache.cpp @@ -309,7 +309,7 @@ public: { if ((rangeBegin & 0xF)) { - cemuLog_logDebug(LogType::Force, "writeStreamout(): RangeBegin not aligned to 16. Begin {:08x} End {:08x}", rangeBegin, rangeEnd); + cemuLog_logDebugOnce(LogType::Force, "writeStreamout(): RangeBegin not aligned to 16. Begin {:08x} End {:08x}", rangeBegin, rangeEnd); rangeBegin = (rangeBegin + 0xF) & ~0xF; rangeEnd = std::max(rangeBegin, rangeEnd); } diff --git a/src/Cafe/HW/Latte/Core/LatteCachedFBO.h b/src/Cafe/HW/Latte/Core/LatteCachedFBO.h index 6d5925fe..5f3aaed4 100644 --- a/src/Cafe/HW/Latte/Core/LatteCachedFBO.h +++ b/src/Cafe/HW/Latte/Core/LatteCachedFBO.h @@ -42,7 +42,7 @@ private: if(colorBuffer[i].texture == nullptr) continue; sint32 effectiveWidth, effectiveHeight; - LatteTexture_getEffectiveSize(colorBuffer[i].texture->baseTexture, &effectiveWidth, &effectiveHeight, nullptr, colorBuffer[i].texture->firstMip); + colorBuffer[i].texture->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, colorBuffer[i].texture->firstMip); if (rtEffectiveSize.x == 0 && rtEffectiveSize.y == 0) { rtEffectiveSize.x = effectiveWidth; @@ -64,7 +64,7 @@ private: if (depthBuffer.texture) { sint32 effectiveWidth, effectiveHeight; - LatteTexture_getEffectiveSize(depthBuffer.texture->baseTexture, &effectiveWidth, &effectiveHeight, nullptr, depthBuffer.texture->firstMip); + depthBuffer.texture->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, depthBuffer.texture->firstMip); if (rtEffectiveSize.x == 0 && rtEffectiveSize.y == 0) { rtEffectiveSize.x = effectiveWidth; diff --git a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp index abdfda21..8c29ccc5 100644 --- a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp +++ b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp @@ -516,14 +516,12 @@ bool LatteMRT::UpdateCurrentFBO() sLatteRenderTargetState.rtUpdateList[sLatteRenderTargetState.rtUpdateListCount] = colorAttachmentView; sLatteRenderTargetState.rtUpdateListCount++; - sint32 colorAttachmentWidth; - sint32 colorAttachmentHeight; - - LatteTexture_getSize(colorAttachmentView->baseTexture, &colorAttachmentWidth, &colorAttachmentHeight, nullptr, colorAttachmentView->firstMip); + sint32 colorAttachmentWidth, colorAttachmentHeight; + colorAttachmentView->baseTexture->GetSize(colorAttachmentWidth, colorAttachmentHeight, colorAttachmentView->firstMip); // set effective size sint32 effectiveWidth, effectiveHeight; - LatteTexture_getEffectiveSize(colorAttachmentView->baseTexture, &effectiveWidth, &effectiveHeight, nullptr, colorAttachmentView->firstMip); + colorAttachmentView->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, colorAttachmentView->firstMip); if (rtEffectiveSize->width == 0 && rtEffectiveSize->height == 0) { rtEffectiveSize->width = effectiveWidth; @@ -531,9 +529,7 @@ bool LatteMRT::UpdateCurrentFBO() } else if (rtEffectiveSize->width != effectiveWidth && rtEffectiveSize->height != effectiveHeight) { -#ifdef CEMU_DEBUG_ASSERT - cemuLog_log(LogType::Force, "Color buffer size mismatch ({}x{}). Effective size: {}x{} Real size: {}x{} Mismatching texture: {:08x} {}x{} fmt {:04x}", rtEffectiveSize->width, rtEffectiveSize->height, effectiveWidth, effectiveHeight, colorAttachmentView->baseTexture->width, colorAttachmentView->baseTexture->height, colorAttachmentView->baseTexture->physAddress, colorAttachmentView->baseTexture->width, colorAttachmentView->baseTexture->height, (uint32)colorAttachmentView->baseTexture->format); -#endif + cemuLog_logDebug(LogType::Force, "Color buffer size mismatch ({}x{}). Effective size: {}x{} Real size: {}x{} Mismatching texture: {:08x} {}x{} fmt {:04x}", rtEffectiveSize->width, rtEffectiveSize->height, effectiveWidth, effectiveHeight, colorAttachmentView->baseTexture->width, colorAttachmentView->baseTexture->height, colorAttachmentView->baseTexture->physAddress, colorAttachmentView->baseTexture->width, colorAttachmentView->baseTexture->height, (uint32)colorAttachmentView->baseTexture->format); } // currently the first color attachment defines the size of the current render target if (rtRealSize->width == 0 && rtRealSize->height == 0) @@ -608,15 +604,11 @@ bool LatteMRT::UpdateCurrentFBO() if (depthBufferPhysMem != MPTR_NULL) { - bool depthBufferWasFound = false; LatteTextureView* depthBufferView = LatteTextureViewLookupCache::lookupSliceEx(depthBufferPhysMem, depthBufferWidth, depthBufferHeight, depthBufferPitch, 0, depthBufferViewFirstSlice, depthBufferFormat, true); if (depthBufferView == nullptr) { - // create depth buffer view - if(depthBufferViewFirstSlice == 0) - depthBufferView = LatteTexture_CreateMapping(depthBufferPhysMem, 0, depthBufferWidth, depthBufferHeight, 1, depthBufferPitch, depthBufferTileMode, depthBufferSwizzle, 0, 1, 0, 1, depthBufferFormat, Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, true); - else - depthBufferView = LatteTexture_CreateMapping(depthBufferPhysMem, 0, depthBufferWidth, depthBufferHeight, depthBufferViewFirstSlice+1, depthBufferPitch, depthBufferTileMode, depthBufferSwizzle, 0, 1, depthBufferViewFirstSlice, 1, depthBufferFormat, Latte::E_DIM::DIM_2D_ARRAY, Latte::E_DIM::DIM_2D, true); + // create new depth buffer view and if it doesn't exist then also create the texture + depthBufferView = LatteTexture_CreateMapping(depthBufferPhysMem, 0, depthBufferWidth, depthBufferHeight, depthBufferViewFirstSlice+1, depthBufferPitch, depthBufferTileMode, depthBufferSwizzle, 0, 1, depthBufferViewFirstSlice, 1, depthBufferFormat, depthBufferViewFirstSlice > 0 ? Latte::E_DIM::DIM_2D_ARRAY : Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, true); LatteGPUState.repeatTextureInitialization = true; } else @@ -626,7 +618,7 @@ bool LatteMRT::UpdateCurrentFBO() } // set effective size sint32 effectiveWidth, effectiveHeight; - LatteTexture_getEffectiveSize(depthBufferView->baseTexture, &effectiveWidth, &effectiveHeight, NULL); + depthBufferView->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, depthBufferViewFirstSlice); if (rtEffectiveSize->width == 0 && rtEffectiveSize->height == 0) { rtEffectiveSize->width = effectiveWidth; @@ -917,10 +909,8 @@ void LatteRenderTarget_copyToBackbuffer(LatteTextureView* textureView, bool isPa // mark source texture as still in use LatteTC_MarkTextureStillInUse(textureView->baseTexture); - sint32 effectiveWidth; - sint32 effectiveHeight; - sint32 effectiveDepth; - LatteTexture_getEffectiveSize(textureView->baseTexture, &effectiveWidth, &effectiveHeight, &effectiveDepth, 0); + sint32 effectiveWidth, effectiveHeight; + textureView->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, 0); _currentOutputImageWidth = effectiveWidth; _currentOutputImageHeight = effectiveHeight; diff --git a/src/Cafe/HW/Latte/Core/LatteTexture.cpp b/src/Cafe/HW/Latte/Core/LatteTexture.cpp index 707428af..91a1aa56 100644 --- a/src/Cafe/HW/Latte/Core/LatteTexture.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTexture.cpp @@ -297,9 +297,9 @@ void LatteTexture_copyData(LatteTexture* srcTexture, LatteTexture* dstTexture, s else { sint32 effectiveWidth_dst, effectiveHeight_dst; - LatteTexture_getEffectiveSize(srcTexture, &effectiveWidth_dst, &effectiveHeight_dst, NULL, 0); + srcTexture->GetEffectiveSize(effectiveWidth_dst, effectiveHeight_dst, 0); sint32 effectiveWidth_src, effectiveHeight_src; - LatteTexture_getEffectiveSize(dstTexture, &effectiveWidth_src, &effectiveHeight_src, NULL, 0); + dstTexture->GetEffectiveSize(effectiveWidth_src, effectiveHeight_src, 0); debug_printf("texture_copyData(): Effective size mismatch\n"); cemuLog_logDebug(LogType::Force, "texture_copyData(): Effective size mismatch (due to texture rule)"); @@ -307,8 +307,6 @@ void LatteTexture_copyData(LatteTexture* srcTexture, LatteTexture* dstTexture, s cemuLog_logDebug(LogType::Force, "Source: origResolution {:04}x{:04} effectiveResolution {:04}x{:04} fmt {:04x} mipIndex {}", srcTexture->width, srcTexture->height, effectiveWidth_src, effectiveHeight_src, (uint32)srcTexture->format, 0); return; } - catchOpenGLError(); - for (sint32 mipIndex = 0; mipIndex < mipCount; mipIndex++) { sint32 sliceCopyWidth = std::max(effectiveCopyWidth >> mipIndex, 1); @@ -323,9 +321,7 @@ void LatteTexture_copyData(LatteTexture* srcTexture, LatteTexture* dstTexture, s LatteTextureSliceMipInfo* dstTexSliceInfo = dstTexture->sliceMipInfo + dstTexture->GetSliceMipArrayIndex(sliceIndex, mipIndex); dstTexSliceInfo->lastDynamicUpdate = srcTexSliceInfo->lastDynamicUpdate; } - catchOpenGLError(); } - catchOpenGLError(); } template diff --git a/src/Cafe/HW/Latte/Core/LatteTexture.h b/src/Cafe/HW/Latte/Core/LatteTexture.h index d5e872e6..b46c1323 100644 --- a/src/Cafe/HW/Latte/Core/LatteTexture.h +++ b/src/Cafe/HW/Latte/Core/LatteTexture.h @@ -55,6 +55,29 @@ public: bool Is3DTexture() const { return dim == Latte::E_DIM::DIM_3D; }; + void GetSize(sint32& width, sint32& height, sint32 mipLevel) const + { + width = std::max(1, this->width >> mipLevel); + height = std::max(1, this->height >> mipLevel); + } + + // similar to GetSize, but returns the real size of the texture taking into account any resolution overwrite by gfx pack rules + void GetEffectiveSize(sint32& effectiveWidth, sint32& effectiveHeight, sint32 mipLevel) const + { + if( overwriteInfo.hasResolutionOverwrite ) + { + effectiveWidth = overwriteInfo.width; + effectiveHeight = overwriteInfo.height; + } + else + { + effectiveWidth = this->width; + effectiveHeight = this->height; + } + effectiveWidth = std::max(1, effectiveWidth >> mipLevel); + effectiveHeight = std::max(1, effectiveHeight >> mipLevel); + } + sint32 GetMipDepth(sint32 mipIndex) { cemu_assert_debug(mipIndex >= 0 && mipIndex < this->mipLevels); @@ -310,8 +333,6 @@ void LatteTexture_Delete(LatteTexture* texture); void LatteTextureLoader_writeReadbackTextureToMemory(LatteTextureDefinition* textureData, uint32 sliceIndex, uint32 mipIndex, uint8* linearPixelData); -void LatteTexture_getSize(LatteTexture* texture, sint32* width, sint32* height, sint32* depth, sint32 mipLevel); -void LatteTexture_getEffectiveSize(LatteTexture* texture, sint32* effectiveWidth, sint32* effectiveHeight, sint32* effectiveDepth, sint32 mipLevel = 0); sint32 LatteTexture_getEffectiveWidth(LatteTexture* texture); bool LatteTexture_doesEffectiveRescaleRatioMatch(LatteTexture* texture1, sint32 mipLevel1, LatteTexture* texture2, sint32 mipLevel2); void LatteTexture_scaleToEffectiveSize(LatteTexture* texture, sint32* x, sint32* y, sint32 mipLevel); diff --git a/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp b/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp index 0260002b..b35f608c 100644 --- a/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp @@ -206,14 +206,10 @@ void LatteTexture_updateTexturesForStage(LatteDecompilerShader* shaderContext, u bool isDepthSampler = shaderContext->textureUsesDepthCompare[textureIndex]; // look for already existing texture - LatteTextureView* textureView; - if (isDepthSampler == false) - textureView = LatteTextureViewLookupCache::lookup(physAddr, width, height, depth, pitch, viewFirstMip, viewNumMips, viewFirstSlice, viewNumSlices, format, dim); - else - textureView = LatteTextureViewLookupCache::lookup(physAddr, width, height, depth, pitch, viewFirstMip, viewNumMips, viewFirstSlice, viewNumSlices, format, dim, true); - if (textureView == nullptr) + LatteTextureView* textureView = LatteTextureViewLookupCache::lookup(physAddr, width, height, depth, pitch, viewFirstMip, viewNumMips, viewFirstSlice, viewNumSlices, format, dim, isDepthSampler); + if (!textureView) { - // create new mapping + // view not found, create a new mapping which will also create a new texture if necessary textureView = LatteTexture_CreateMapping(physAddr, physMipAddr, width, height, depth, pitch, tileMode, swizzle, viewFirstMip, viewNumMips, viewFirstSlice, viewNumSlices, format, dim, dim, isDepthSampler); if (textureView == nullptr) continue; @@ -273,9 +269,7 @@ void LatteTexture_updateTexturesForStage(LatteDecompilerShader* shaderContext, u // check for changes if (LatteTC_HasTextureChanged(textureView->baseTexture) || swizzleChanged) { -#ifdef CEMU_DEBUG_ASSERT debug_printf("Reload texture 0x%08x res %dx%d memRange %08x-%08x SwizzleChange: %s\n", textureView->baseTexture->physAddress, textureView->baseTexture->width, textureView->baseTexture->height, textureView->baseTexture->texDataPtrLow, textureView->baseTexture->texDataPtrHigh, swizzleChanged ? "yes" : "no"); -#endif // update swizzle / changed mip address if (swizzleChanged) { @@ -338,44 +332,6 @@ void LatteTexture_updateTextures() LatteTexture_updateTexturesForStage(geometryShader, LATTE_CEMU_GS_TEX_UNIT_BASE, LatteGPUState.contextNew.SQ_TEX_START_GS); } -// returns the width, height, depth of the texture -void LatteTexture_getSize(LatteTexture* texture, sint32* width, sint32* height, sint32* depth, sint32 mipLevel) -{ - *width = texture->width; - *height = texture->height; - if (depth != NULL) - *depth = texture->depth; - // handle mip level - *width = std::max(1, *width >> mipLevel); - *height = std::max(1, *height >> mipLevel); - if(texture->Is3DTexture() && depth) - *depth = std::max(1, *depth >> mipLevel); -} - -/* - * Returns the internally used width/height/depth of the texture - * Usually this is the width/height/depth specified by the game, - * unless the texture resolution was redefined via graphic pack texture rules - */ -void LatteTexture_getEffectiveSize(LatteTexture* texture, sint32* effectiveWidth, sint32* effectiveHeight, sint32* effectiveDepth, sint32 mipLevel) -{ - *effectiveWidth = texture->width; - *effectiveHeight = texture->height; - if( effectiveDepth != NULL ) - *effectiveDepth = texture->depth; - if( texture->overwriteInfo.hasResolutionOverwrite ) - { - *effectiveWidth = texture->overwriteInfo.width; - *effectiveHeight = texture->overwriteInfo.height; - if( effectiveDepth != NULL ) - *effectiveDepth = texture->overwriteInfo.depth; - } - // handle mipLevel - // todo: Mip-mapped 3D textures decrease in depth also? - *effectiveWidth = std::max(1, *effectiveWidth >> mipLevel); - *effectiveHeight = std::max(1, *effectiveHeight >> mipLevel); -} - sint32 LatteTexture_getEffectiveWidth(LatteTexture* texture) { if (texture->overwriteInfo.hasResolutionOverwrite) diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp index 8548fa1c..68d7def6 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp @@ -569,10 +569,8 @@ void OpenGLRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu g_renderer->ClearColorbuffer(padView); } - // calculate effective size - sint32 effectiveWidth; - sint32 effectiveHeight; - LatteTexture_getEffectiveSize(texView->baseTexture, &effectiveWidth, &effectiveHeight, nullptr, 0); + sint32 effectiveWidth, effectiveHeight; + texView->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, 0); shader_unbind(RendererShader::ShaderType::kGeometry); shader_bind(shader->GetVertexShader()); @@ -1127,8 +1125,8 @@ void OpenGLRenderer::texture_clearColorSlice(LatteTexture* hostTexture, sint32 s LatteTextureGL* texGL = (LatteTextureGL*)hostTexture; cemu_assert_debug(!texGL->isDepth); - sint32 eWidth, eHeight, eDepth; - LatteTexture_getEffectiveSize(hostTexture, &eWidth, &eHeight, &eDepth, mipIndex); + sint32 eWidth, eHeight; + hostTexture->GetEffectiveSize(eWidth, eHeight, mipIndex); renderstate_resetColorControl(); renderTarget_setViewport(0, 0, eWidth, eHeight, 0.0f, 1.0f); LatteMRT::BindColorBufferOnly(hostTexture->GetOrCreateView(mipIndex, 1, sliceIndex, 1)); @@ -1141,8 +1139,8 @@ void OpenGLRenderer::texture_clearDepthSlice(LatteTexture* hostTexture, uint32 s LatteTextureGL* texGL = (LatteTextureGL*)hostTexture; cemu_assert_debug(texGL->isDepth); - sint32 eWidth, eHeight, eDepth; - LatteTexture_getEffectiveSize(hostTexture, &eWidth, &eHeight, &eDepth, mipIndex); + sint32 eWidth, eHeight; + hostTexture->GetEffectiveSize(eWidth, eHeight, mipIndex); renderstate_resetColorControl(); renderstate_resetDepthControl(); renderTarget_setViewport(0, 0, eWidth, eHeight, 0.0f, 1.0f); @@ -1170,13 +1168,12 @@ void OpenGLRenderer::texture_clearSlice(LatteTexture* hostTextureGeneric, sint32 LatteTextureGL::FormatInfoGL formatInfoGL; LatteTextureGL::GetOpenGLFormatInfo(hostTexture->isDepth, hostTexture->format, hostTexture->dim, &formatInfoGL); // get effective size of mip - sint32 effectiveWidth; - sint32 effectiveHeight; - LatteTexture_getEffectiveSize(hostTexture, &effectiveWidth, &effectiveHeight, nullptr, mipIndex); + sint32 effectiveWidth, effectiveHeight; + hostTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, mipIndex); // on Nvidia glClearTexImage and glClearTexSubImage has bad performance (clearing a 4K texture takes up to 50ms) // clearing with glTextureSubImage2D from a CPU RAM buffer is only slightly slower - // clearing with glTextureSubImage2D from a OpenGL buffer is 10-20% faster than glClearTexImage) + // clearing with glTextureSubImage2D from a OpenGL buffer is 10-20% faster than glClearTexImage // clearing with FBO and glClear is orders of magnitude faster than the other methods // (these are results from 2018, may be different now) @@ -1207,7 +1204,6 @@ void OpenGLRenderer::texture_clearSlice(LatteTexture* hostTextureGeneric, sint32 } if (glClearTexSubImage == nullptr) return; - // clear glClearTexSubImage(hostTexture->glId_texture, mipIndex, 0, 0, sliceIndex, effectiveWidth, effectiveHeight, 1, formatInfoGL.glSuppliedFormat, formatInfoGL.glSuppliedFormatType, NULL); } @@ -1215,7 +1211,6 @@ LatteTexture* OpenGLRenderer::texture_createTextureEx(Latte::E_DIM dim, MPTR phy uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth) { return new LatteTextureGL(dim, physAddress, physMipAddress, format, width, height, depth, pitch, mipLevels, swizzle, tileMode, isDepth); - } void OpenGLRenderer::texture_setActiveTextureUnit(sint32 index) diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLSurfaceCopy.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLSurfaceCopy.cpp index c49a57e4..d578b842 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLSurfaceCopy.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLSurfaceCopy.cpp @@ -30,9 +30,8 @@ void OpenGLRenderer::surfaceCopy_copySurfaceWithFormatConversion(LatteTexture* s sint32 effectiveCopyWidth = width; sint32 effectiveCopyHeight = height; LatteTexture_scaleToEffectiveSize(sourceTexture, &effectiveCopyWidth, &effectiveCopyHeight, 0); - sint32 sourceEffectiveWidth; - sint32 sourceEffectiveHeight; - LatteTexture_getEffectiveSize(sourceTexture, &sourceEffectiveWidth, &sourceEffectiveHeight, nullptr, srcMip); + sint32 sourceEffectiveWidth, sourceEffectiveHeight; + sourceTexture->GetEffectiveSize(sourceEffectiveWidth, sourceEffectiveHeight, srcMip); // reset everything renderstate_resetColorControl(); renderstate_resetDepthControl(); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 8711359e..5285e4ac 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -764,7 +764,7 @@ void VulkanRenderer::HandleScreenshotRequest(LatteTextureView* texView, bool pad //dumpImage->flagForCurrentCommandBuffer(); int width, height; - LatteTexture_getEffectiveSize(baseImageTex, &width, &height, nullptr, 0); + baseImageTex->GetEffectiveSize(width, height, 0); VkImage image = nullptr; VkDeviceMemory imageMemory = nullptr;; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp index 6d5d9402..d89cdaa1 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp @@ -464,9 +464,8 @@ VKRObjectFramebuffer* VulkanRenderer::surfaceCopy_getOrCreateFramebuffer(VkCopyS VKRObjectTextureView* vkObjTextureView = surfaceCopy_createImageView(state.destinationTexture, state.dstSlice, state.dstMip); // create new framebuffer - sint32 effectiveWidth = 0; - sint32 effectiveHeight = 0; - LatteTexture_getEffectiveSize(state.destinationTexture, &effectiveWidth, &effectiveHeight, nullptr, state.dstMip); + sint32 effectiveWidth, effectiveHeight; + state.destinationTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, state.dstMip); std::array fbAttachments; fbAttachments[0] = vkObjTextureView; @@ -595,15 +594,11 @@ void VulkanRenderer::surfaceCopy_viaDrawcall(LatteTextureVk* srcTextureVk, sint3 // get descriptor set VKRObjectDescriptorSet* vkObjDescriptorSet = surfaceCopy_getOrCreateDescriptorSet(copySurfaceState, copySurfacePipelineInfo); - // get extend - sint32 effectiveWidth = 0; - sint32 effectiveHeight = 0; - LatteTexture_getEffectiveSize(dstTextureVk, &effectiveWidth, &effectiveHeight, nullptr, texDstMip); + sint32 dstEffectiveWidth, dstEffectiveHeight; + dstTextureVk->GetEffectiveSize(dstEffectiveWidth, dstEffectiveHeight, texDstMip); - // get extend - sint32 srcEffectiveWidth = 0; - sint32 srcEffectiveHeight = 0; - LatteTexture_getEffectiveSize(srcTextureVk, &srcEffectiveWidth, &srcEffectiveHeight, nullptr, texSrcMip); + sint32 srcEffectiveWidth, srcEffectiveHeight; + srcTextureVk->GetEffectiveSize(srcEffectiveWidth, srcEffectiveHeight, texSrcMip); CopyShaderPushConstantData_t pushConstantData; @@ -878,9 +873,8 @@ void VulkanRenderer::surfaceCopy_copySurfaceWithFormatConversion(LatteTexture* s sint32 effectiveCopyWidth = width; sint32 effectiveCopyHeight = height; LatteTexture_scaleToEffectiveSize(sourceTexture, &effectiveCopyWidth, &effectiveCopyHeight, 0); - sint32 sourceEffectiveWidth; - sint32 sourceEffectiveHeight; - LatteTexture_getEffectiveSize(sourceTexture, &sourceEffectiveWidth, &sourceEffectiveHeight, nullptr, srcMip); + sint32 sourceEffectiveWidth, sourceEffectiveHeight; + sourceTexture->GetEffectiveSize(sourceEffectiveWidth, sourceEffectiveHeight, srcMip); sint32 texSrcMip = srcMip; sint32 texSrcSlice = srcSlice; diff --git a/src/Cafe/OS/libs/gx2/GX2_Resource.cpp b/src/Cafe/OS/libs/gx2/GX2_Resource.cpp index 70390921..97f51a0d 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Resource.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_Resource.cpp @@ -114,7 +114,7 @@ namespace GX2 void GX2RSetStreamOutBuffer(uint32 bufferIndex, GX2StreamOutBuffer* soBuffer) { - // seen in CoD: Ghosts + // seen in CoD: Ghosts and CoD: Black Ops 2 GX2SetStreamOutBuffer(bufferIndex, soBuffer); } diff --git a/src/Cemu/Logging/CemuLogging.h b/src/Cemu/Logging/CemuLogging.h index 388e51ab..bbffd164 100644 --- a/src/Cemu/Logging/CemuLogging.h +++ b/src/Cemu/Logging/CemuLogging.h @@ -112,6 +112,8 @@ bool cemuLog_logDebug(LogType type, TFmt format, TArgs&&... args) #endif } +#define cemuLog_logDebugOnce(...) { static bool _not_first_call = false; if (!_not_first_call) { _not_first_call = true; cemuLog_logDebug(__VA_ARGS__); } } + // cafe lib calls bool cemuLog_advancedPPCLoggingEnabled(); From 40d1eaeb72f050916b29396805b8ea990345d418 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Mon, 11 Mar 2024 21:37:44 +0100 Subject: [PATCH 037/299] nn_ac: Refactor and implement more API Doesn't fix any issue as far as I know but it removes some of the unsupported API complaints in debug logging --- src/Cafe/OS/libs/nn_ac/nn_ac.cpp | 189 +++++++++++++++++-------------- 1 file changed, 105 insertions(+), 84 deletions(-) diff --git a/src/Cafe/OS/libs/nn_ac/nn_ac.cpp b/src/Cafe/OS/libs/nn_ac/nn_ac.cpp index bb7d4af6..5f231499 100644 --- a/src/Cafe/OS/libs/nn_ac/nn_ac.cpp +++ b/src/Cafe/OS/libs/nn_ac/nn_ac.cpp @@ -8,83 +8,14 @@ // AC lib (manages internet connection) -#define AC_STATUS_FAILED (-1) -#define AC_STATUS_OK (0) - -void nn_acExport_ConnectAsync(PPCInterpreter_t* hCPU) +enum class AC_STATUS : uint32 { - cemuLog_logDebug(LogType::Force, "nn_ac.ConnectAsync();"); - uint32 nnResultCode = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); - osLib_returnFromFunction(hCPU, nnResultCode); -} - -void nn_acExport_Connect(PPCInterpreter_t* hCPU) -{ - cemuLog_logDebug(LogType::Force, "nn_ac.Connect();"); - - // Terraria expects this (or GetLastErrorCode) to return 0 on success - // investigate on the actual console - // maybe all success codes are always 0 and dont have any of the other fields set? - - uint32 nnResultCode = 0;// BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); // Splatoon freezes if this function fails? - osLib_returnFromFunction(hCPU, nnResultCode); -} + FAILED = (uint32)-1, + OK = 0, +}; static_assert(TRUE == 1, "TRUE not 1"); -void nn_acExport_IsApplicationConnected(PPCInterpreter_t* hCPU) -{ - //cemuLog_logDebug(LogType::Force, "nn_ac.IsApplicationConnected(0x{:08x})", hCPU->gpr[3]); - ppcDefineParamMEMPTR(connected, uint8, 0); - if (connected) - *connected = TRUE; - //memory_writeU8(hCPU->gpr[3], 1); // always return true regardless of actual online state - - const uint32 nnResultCode = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); - osLib_returnFromFunction(hCPU, nnResultCode); -} - -void nn_acExport_GetConnectStatus(PPCInterpreter_t* hCPU) -{ - ppcDefineParamMEMPTR(status, uint32, 0); - if (status) - *status = AC_STATUS_OK; - - const uint32 nnResultCode = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); - osLib_returnFromFunction(hCPU, nnResultCode); -} - -void nn_acExport_GetLastErrorCode(PPCInterpreter_t* hCPU) -{ - //cemuLog_logDebug(LogType::Force, "nn_ac.GetLastErrorCode();"); - ppcDefineParamMEMPTR(errorCode, uint32, 0); - if (errorCode) - *errorCode = 0; - const uint32 nnResultCode = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); - osLib_returnFromFunction(hCPU, nnResultCode); -} - -void nn_acExport_GetStatus(PPCInterpreter_t* hCPU) -{ - cemuLog_logDebug(LogType::Force, "nn_ac.GetStatus();"); - ppcDefineParamMEMPTR(status, uint32, 0); - if (status) - *status = AC_STATUS_OK; - const uint32 nnResultCode = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); - osLib_returnFromFunction(hCPU, nnResultCode); -} - -void nn_acExport_GetConnectResult(PPCInterpreter_t* hCPU) -{ - // GetConnectStatus__Q2_2nn2acFPQ3_2nn2ac6Status - cemuLog_logDebug(LogType::Force, "nn_ac.GetConnectResult(0x{:08x})", hCPU->gpr[3]); - ppcDefineParamMEMPTR(result, uint32, 0); - const uint32 nnResultCode = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); - if (result) - *result = nnResultCode; - osLib_returnFromFunction(hCPU, nnResultCode); -} - void _GetLocalIPAndSubnetMaskFallback(uint32& localIp, uint32& subnetMask) { // default to some hardcoded values @@ -227,37 +158,127 @@ void nnAcExport_IsConfigExisting(PPCInterpreter_t* hCPU) namespace nn_ac { + nnResult Initialize() + { + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); + } + + nnResult ConnectAsync() + { + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); + } + + nnResult IsApplicationConnected(uint8be* connected) + { + if (connected) + *connected = TRUE; + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); + } + + uint32 Connect() + { + // Terraria expects this (or GetLastErrorCode) to return 0 on success + // investigate on the actual console + // maybe all success codes are always 0 and dont have any of the other fields set? + uint32 nnResultCode = 0;// BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); // Splatoon freezes if this function fails? + return nnResultCode; + } + + nnResult GetConnectStatus(betype* status) + { + if (status) + *status = AC_STATUS::OK; + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); + } + + nnResult GetStatus(betype* status) + { + return GetConnectStatus(status); + } + + nnResult GetLastErrorCode(uint32be* errorCode) + { + if (errorCode) + *errorCode = 0; + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); + } + + nnResult GetConnectResult(uint32be* connectResult) + { + const uint32 nnResultCode = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_AC, 0); + if (connectResult) + *connectResult = nnResultCode; + return nnResultCode; + } + + static_assert(sizeof(betype) == 4); + static_assert(sizeof(betype) == 4); + + nnResult ACInitialize() + { + return Initialize(); + } + bool ACIsSuccess(betype* r) { return NN_RESULT_IS_SUCCESS(*r) ? 1 : 0; } - nnResult ACGetConnectStatus(uint32be* connectionStatus) + bool ACIsFailure(betype* r) { + return NN_RESULT_IS_FAILURE(*r) ? 1 : 0; + } - *connectionStatus = 0; // 0 means connected? + nnResult ACGetConnectStatus(betype* connectionStatus) + { + return GetConnectStatus(connectionStatus); + } - return NN_RESULT_SUCCESS; + nnResult ACGetStatus(betype* connectionStatus) + { + return GetStatus(connectionStatus); + } + + nnResult ACConnectAsync() + { + return ConnectAsync(); + } + + nnResult ACIsApplicationConnected(uint32be* connectedU32) + { + uint8be connected = 0; + nnResult r = IsApplicationConnected(&connected); + *connectedU32 = connected; // convert to uint32 + return r; } void load() { + cafeExportRegisterFunc(Initialize, "nn_ac", "Initialize__Q2_2nn2acFv", LogType::Placeholder); + + cafeExportRegisterFunc(Connect, "nn_ac", "Connect__Q2_2nn2acFv", LogType::Placeholder); + cafeExportRegisterFunc(ConnectAsync, "nn_ac", "ConnectAsync__Q2_2nn2acFv", LogType::Placeholder); + + cafeExportRegisterFunc(GetConnectResult, "nn_ac", "GetConnectResult__Q2_2nn2acFPQ2_2nn6Result", LogType::Placeholder); + cafeExportRegisterFunc(GetLastErrorCode, "nn_ac", "GetLastErrorCode__Q2_2nn2acFPUi", LogType::Placeholder); + cafeExportRegisterFunc(GetConnectStatus, "nn_ac", "GetConnectStatus__Q2_2nn2acFPQ3_2nn2ac6Status", LogType::Placeholder); + cafeExportRegisterFunc(GetStatus, "nn_ac", "GetStatus__Q2_2nn2acFPQ3_2nn2ac6Status", LogType::Placeholder); + cafeExportRegisterFunc(IsApplicationConnected, "nn_ac", "IsApplicationConnected__Q2_2nn2acFPb", LogType::Placeholder); + + // AC also offers C-style wrappers + cafeExportRegister("nn_ac", ACInitialize, LogType::Placeholder); cafeExportRegister("nn_ac", ACIsSuccess, LogType::Placeholder); + cafeExportRegister("nn_ac", ACIsFailure, LogType::Placeholder); cafeExportRegister("nn_ac", ACGetConnectStatus, LogType::Placeholder); + cafeExportRegister("nn_ac", ACGetStatus, LogType::Placeholder); + cafeExportRegister("nn_ac", ACConnectAsync, LogType::Placeholder); + cafeExportRegister("nn_ac", ACIsApplicationConnected, LogType::Placeholder); } } void nnAc_load() { - osLib_addFunction("nn_ac", "Connect__Q2_2nn2acFv", nn_acExport_Connect); - osLib_addFunction("nn_ac", "ConnectAsync__Q2_2nn2acFv", nn_acExport_ConnectAsync); - osLib_addFunction("nn_ac", "IsApplicationConnected__Q2_2nn2acFPb", nn_acExport_IsApplicationConnected); - osLib_addFunction("nn_ac", "GetConnectStatus__Q2_2nn2acFPQ3_2nn2ac6Status", nn_acExport_GetConnectStatus); - osLib_addFunction("nn_ac", "GetConnectResult__Q2_2nn2acFPQ2_2nn6Result", nn_acExport_GetConnectResult); - osLib_addFunction("nn_ac", "GetLastErrorCode__Q2_2nn2acFPUi", nn_acExport_GetLastErrorCode); - osLib_addFunction("nn_ac", "GetStatus__Q2_2nn2acFPQ3_2nn2ac6Status", nn_acExport_GetStatus); - osLib_addFunction("nn_ac", "GetAssignedAddress__Q2_2nn2acFPUl", nnAcExport_GetAssignedAddress); osLib_addFunction("nn_ac", "GetAssignedSubnet__Q2_2nn2acFPUl", nnAcExport_GetAssignedSubnet); From 1f9b89116f2bad17f9dbd0017b38e0da181fa4c9 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Mon, 11 Mar 2024 21:55:58 +0100 Subject: [PATCH 038/299] Vulkan: Fix crash during shutdown if shaders are still compiling Make sure the async shader compiler threads are stopped before the shaders are deleted --- .../Renderer/Vulkan/RendererShaderVk.cpp | 23 +++++++++---------- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 4 ++-- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp index 970f5517..437ef51d 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp @@ -129,19 +129,18 @@ class _ShaderVkThreadPool public: void StartThreads() { - if (s_threads.empty()) - { - // create thread pool - m_shutdownThread.store(false); - const uint32 threadCount = 2; - for (uint32 i = 0; i < threadCount; ++i) - s_threads.emplace_back(&_ShaderVkThreadPool::CompilerThreadFunc, this); - } + if (m_threadsActive.exchange(true)) + return; + // create thread pool + const uint32 threadCount = 2; + for (uint32 i = 0; i < threadCount; ++i) + s_threads.emplace_back(&_ShaderVkThreadPool::CompilerThreadFunc, this); } void StopThreads() { - m_shutdownThread.store(true); + if (!m_threadsActive.exchange(false)) + return; for (uint32 i = 0; i < s_threads.size(); ++i) s_compilationQueueCount.increment(); for (auto& it : s_threads) @@ -156,7 +155,7 @@ public: void CompilerThreadFunc() { - while (!m_shutdownThread.load(std::memory_order::relaxed)) + while (m_threadsActive.load(std::memory_order::relaxed)) { s_compilationQueueCount.decrementWithWait(); s_compilationQueueMutex.lock(); @@ -181,7 +180,7 @@ public: } } - bool HasThreadsRunning() const { return !m_shutdownThread; } + bool HasThreadsRunning() const { return m_threadsActive; } public: std::vector s_threads; @@ -191,7 +190,7 @@ public: std::mutex s_compilationQueueMutex; private: - std::atomic m_shutdownThread; + std::atomic m_threadsActive; }ShaderVkThreadPool; RendererShaderVk::RendererShaderVk(ShaderType type, uint64 baseHash, uint64 auxHash, bool isGameShader, bool isGfxPackShader, const std::string& glslCode) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 5285e4ac..876baa07 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -600,7 +600,7 @@ VulkanRenderer::~VulkanRenderer() SubmitCommandBuffer(); WaitDeviceIdle(); WaitCommandBufferFinished(GetCurrentCommandBufferId()); - // shut down compilation threads + // make sure compilation threads have been shut down RendererShaderVk::Shutdown(); // shut down pipeline save thread m_destructionRequested = true; @@ -1558,12 +1558,12 @@ void VulkanRenderer::Shutdown() Renderer::Shutdown(); SubmitCommandBuffer(); WaitDeviceIdle(); - if (m_imguiRenderPass != VK_NULL_HANDLE) { vkDestroyRenderPass(m_logicalDevice, m_imguiRenderPass, nullptr); m_imguiRenderPass = VK_NULL_HANDLE; } + RendererShaderVk::Shutdown(); } void VulkanRenderer::UnrecoverableError(const char* errMsg) const From a50e25300d1c3d4eec9ba3085067facff035815f Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Mon, 11 Mar 2024 23:01:37 +0100 Subject: [PATCH 039/299] Vulkan: Remove unused code path for texture copies In 2020 we switched to drawcalls for texture copies replacing the copy-via-buffer path. It's not been used since so lets remove it --- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 14 -- .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 6 - .../Renderer/Vulkan/VulkanSurfaceCopy.cpp | 127 +----------------- 3 files changed, 1 insertion(+), 146 deletions(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 876baa07..c7f8c043 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -577,20 +577,6 @@ VulkanRenderer::VulkanRenderer() for (sint32 i = 0; i < OCCLUSION_QUERY_POOL_SIZE; i++) m_occlusionQueries.list_availableQueryIndices.emplace_back(i); - // enable surface copies via buffer if we have plenty of memory available (otherwise use drawcalls) - size_t availableSurfaceCopyBufferMem = memoryManager->GetTotalMemoryForBufferType(VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - //m_featureControl.mode.useBufferSurfaceCopies = availableSurfaceCopyBufferMem >= 2000ull * 1024ull * 1024ull; // enable if at least 2000MB VRAM - m_featureControl.mode.useBufferSurfaceCopies = false; - - if (m_featureControl.mode.useBufferSurfaceCopies) - { - //cemuLog_log(LogType::Force, "Enable surface copies via buffer"); - } - else - { - //cemuLog_log(LogType::Force, "Disable surface copies via buffer (Requires 2GB. Has only {}MB available)", availableSurfaceCopyBufferMem / 1024ull / 1024ull); - } - // start compilation threads RendererShaderVk::Init(); } diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index 479c9e54..226edad6 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -311,7 +311,6 @@ public: void surfaceCopy_notifyTextureRelease(LatteTextureVk* hostTexture); private: - void surfaceCopy_viaBuffer(LatteTextureVk* srcTextureVk, sint32 texSrcMip, sint32 texSrcLevel, LatteTextureVk* dstTextureVk, sint32 texDstMip, sint32 texDstLevel, sint32 effectiveCopyWidth, sint32 effectiveCopyHeight); void surfaceCopy_viaDrawcall(LatteTextureVk* srcTextureVk, sint32 texSrcMip, sint32 texSrcSlice, LatteTextureVk* dstTextureVk, sint32 texDstMip, sint32 texDstSlice, sint32 effectiveCopyWidth, sint32 effectiveCopyHeight); void surfaceCopy_cleanup(); @@ -328,10 +327,6 @@ private: std::unordered_map m_copySurfacePipelineCache; - VkBuffer m_surfaceCopyBuffer = VK_NULL_HANDLE; - VkDeviceMemory m_surfaceCopyBufferMemory = VK_NULL_HANDLE; - size_t m_surfaceCopyBufferSize{}; - public: // renderer interface void bufferCache_init(const sint32 bufferSize) override; @@ -470,7 +465,6 @@ private: struct { - bool useBufferSurfaceCopies; // if GPU has enough VRAM to spare, allow to use a buffer to copy surfaces (instead of drawcalls) bool useTFEmulationViaSSBO = true; // emulate transform feedback via shader writes to a storage buffer }mode; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp index d89cdaa1..479b7e60 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp @@ -763,110 +763,6 @@ bool vkIsBitCompatibleColorDepthFormat(VkFormat format1, VkFormat format2) return false; } -void VulkanRenderer::surfaceCopy_viaBuffer(LatteTextureVk* srcTextureVk, sint32 texSrcMip, sint32 texSrcSlice, LatteTextureVk* dstTextureVk, sint32 texDstMip, sint32 texDstSlice, sint32 effectiveCopyWidth, sint32 effectiveCopyHeight) -{ - cemu_assert_debug(false); // not used currently - - cemu_assert_debug(m_featureControl.mode.useBufferSurfaceCopies); - - if (srcTextureVk->dim == Latte::E_DIM::DIM_3D) - { - cemu_assert_debug(false); - return; - } - if (dstTextureVk->dim == Latte::E_DIM::DIM_3D) - { - cemu_assert_debug(false); - return; - } - - draw_endRenderPass(); - - // calculate buffer size required for copy - VkDeviceSize copySize = std::max(srcTextureVk->getAllocation()->getAllocationSize(), dstTextureVk->getAllocation()->getAllocationSize()); - - // make sure allocated buffer is large enough - if (m_surfaceCopyBuffer == VK_NULL_HANDLE || copySize > m_surfaceCopyBufferSize) - { - if (m_surfaceCopyBuffer != VK_NULL_HANDLE) - { - // free existing buffer - destroyDeviceMemory(m_surfaceCopyBufferMemory); - m_surfaceCopyBufferMemory = VK_NULL_HANDLE; - destroyBuffer(m_surfaceCopyBuffer); - m_surfaceCopyBuffer = VK_NULL_HANDLE; - } - VkDeviceSize allocSize = (copySize + 1024ull * 1024ull - 1ull) & ~(1024ull * 1024ull - 1ull); // align to whole MB - m_surfaceCopyBufferSize = allocSize; - memoryManager->CreateBuffer(m_surfaceCopyBufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, m_surfaceCopyBuffer, m_surfaceCopyBufferMemory); - if (m_surfaceCopyBuffer == VK_NULL_HANDLE) - { - cemuLog_log(LogType::Force, "Vulkan: Failed to allocate surface copy buffer with size {}", allocSize); - return; - } - } - if (m_surfaceCopyBuffer == VK_NULL_HANDLE) - return; - - auto vkObjSrcTexture = srcTextureVk->GetImageObj(); - auto vkObjDstTexture = dstTextureVk->GetImageObj(); - vkObjSrcTexture->flagForCurrentCommandBuffer(); - vkObjDstTexture->flagForCurrentCommandBuffer(); - - VkBufferImageCopy region{}; - region.bufferOffset = 0; - region.bufferRowLength = effectiveCopyWidth; - region.bufferImageHeight = effectiveCopyHeight; - - if (srcTextureVk->isDepth) - region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; - else - region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - region.imageSubresource.baseArrayLayer = texSrcSlice; - region.imageSubresource.layerCount = 1; - region.imageSubresource.mipLevel = texSrcMip; - - region.imageOffset = { 0,0,0 }; - region.imageExtent = { (uint32)effectiveCopyWidth, (uint32)effectiveCopyHeight, 1 }; - - // make sure all write operations to the src image have finished - barrier_image(srcTextureVk, region.imageSubresource, VK_IMAGE_LAYOUT_GENERAL); - - vkCmdCopyImageToBuffer(getCurrentCommandBuffer(), vkObjSrcTexture->m_image, VK_IMAGE_LAYOUT_GENERAL, m_surfaceCopyBuffer, 1, ®ion); - - // copy buffer to image - - VkBufferImageCopy imageRegion[2]{}; - sint32 imageRegionCount = 0; - - // color or depth only copy - imageRegion[0].bufferOffset = 0; - imageRegion[0].imageExtent.width = effectiveCopyWidth; - imageRegion[0].imageExtent.height = effectiveCopyHeight; - imageRegion[0].imageExtent.depth = 1; - - imageRegion[0].imageSubresource.mipLevel = texDstMip; - if (dstTextureVk->isDepth) - imageRegion[0].imageSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; - else - imageRegion[0].imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - imageRegion[0].imageSubresource.baseArrayLayer = texDstSlice; - imageRegion[0].imageSubresource.layerCount = 1; - - imageRegionCount = 1; - - // make sure the transfer to the buffer finished - barrier_bufferRange(m_surfaceCopyBuffer, 0, VK_WHOLE_SIZE); - - // make sure all read and write operations to the dst image have finished - barrier_image(dstTextureVk, imageRegion[0].imageSubresource, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - - vkCmdCopyBufferToImage(m_state.currentCommandBuffer, m_surfaceCopyBuffer, vkObjDstTexture->m_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, imageRegionCount, imageRegion); - - // make sure transfer has finished before any other operation - barrier_image(dstTextureVk, imageRegion[0].imageSubresource, VK_IMAGE_LAYOUT_GENERAL); -} - void VulkanRenderer::surfaceCopy_copySurfaceWithFormatConversion(LatteTexture* sourceTexture, sint32 srcMip, sint32 srcSlice, LatteTexture* destinationTexture, sint32 dstMip, sint32 dstSlice, sint32 width, sint32 height) { // scale copy size to effective size @@ -899,28 +795,7 @@ void VulkanRenderer::surfaceCopy_copySurfaceWithFormatConversion(LatteTexture* s return; } - VkFormat srcFormatVk = srcTextureVk->GetFormat(); - VkFormat dstFormatVk = dstTextureVk->GetFormat(); - - if ((srcTextureVk->isDepth && !dstTextureVk->isDepth) || - !srcTextureVk->isDepth && dstTextureVk->isDepth) - { - // depth to color or - // color to depth - if (m_featureControl.mode.useBufferSurfaceCopies && vkIsBitCompatibleColorDepthFormat(srcFormatVk, dstFormatVk)) - surfaceCopy_viaBuffer(srcTextureVk, texSrcMip, texSrcSlice, dstTextureVk, texDstMip, texDstSlice, effectiveCopyWidth, effectiveCopyHeight); - else - surfaceCopy_viaDrawcall(srcTextureVk, texSrcMip, texSrcSlice, dstTextureVk, texDstMip, texDstSlice, effectiveCopyWidth, effectiveCopyHeight); - } - else - { - // depth to depth or - // color to color - if (m_featureControl.mode.useBufferSurfaceCopies && srcFormatVk == dstFormatVk) - surfaceCopy_viaBuffer(srcTextureVk, texSrcMip, texSrcSlice, dstTextureVk, texDstMip, texDstSlice, effectiveCopyWidth, effectiveCopyHeight); - else - surfaceCopy_viaDrawcall(srcTextureVk, texSrcMip, texSrcSlice, dstTextureVk, texDstMip, texDstSlice, effectiveCopyWidth, effectiveCopyHeight); - } + surfaceCopy_viaDrawcall(srcTextureVk, texSrcMip, texSrcSlice, dstTextureVk, texDstMip, texDstSlice, effectiveCopyWidth, effectiveCopyHeight); } // called whenever a texture is destroyed From 224866c3d218995458d957c1c3777313e289da63 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Tue, 12 Mar 2024 01:37:07 +0100 Subject: [PATCH 040/299] CI: Work around a vcpkg issue by checking out an earlier commit --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 00aac0fe..f3b834b4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,7 +28,7 @@ jobs: run: | cd dependencies/vcpkg git fetch --unshallow - git pull --all + git checkout 431eb6bda0950874c8d4ed929cc66e15d8aae46f - name: Setup release mode parameters (for deploy) if: ${{ inputs.deploymode == 'release' }} @@ -133,7 +133,7 @@ jobs: run: | cd dependencies/vcpkg git fetch --unshallow - git pull --all + git checkout 431eb6bda0950874c8d4ed929cc66e15d8aae46f - name: Setup release mode parameters (for deploy) if: ${{ inputs.deploymode == 'release' }} From 6fa77feba3ec7437b57b6c4c221cde9eb07cd399 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Tue, 12 Mar 2024 05:52:53 +0100 Subject: [PATCH 041/299] Latte: Fix regression in dd7cb74 --- src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp | 2 +- src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp index 8c29ccc5..d7c5408f 100644 --- a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp +++ b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp @@ -618,7 +618,7 @@ bool LatteMRT::UpdateCurrentFBO() } // set effective size sint32 effectiveWidth, effectiveHeight; - depthBufferView->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, depthBufferViewFirstSlice); + depthBufferView->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, 0); if (rtEffectiveSize->width == 0 && rtEffectiveSize->height == 0) { rtEffectiveSize->width = effectiveWidth; diff --git a/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp b/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp index b35f608c..4e5c303c 100644 --- a/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp @@ -206,7 +206,11 @@ void LatteTexture_updateTexturesForStage(LatteDecompilerShader* shaderContext, u bool isDepthSampler = shaderContext->textureUsesDepthCompare[textureIndex]; // look for already existing texture - LatteTextureView* textureView = LatteTextureViewLookupCache::lookup(physAddr, width, height, depth, pitch, viewFirstMip, viewNumMips, viewFirstSlice, viewNumSlices, format, dim, isDepthSampler); + LatteTextureView* textureView; + if (isDepthSampler == false) + textureView = LatteTextureViewLookupCache::lookup(physAddr, width, height, depth, pitch, viewFirstMip, viewNumMips, viewFirstSlice, viewNumSlices, format, dim); + else + textureView = LatteTextureViewLookupCache::lookup(physAddr, width, height, depth, pitch, viewFirstMip, viewNumMips, viewFirstSlice, viewNumSlices, format, dim, true); if (!textureView) { // view not found, create a new mapping which will also create a new texture if necessary From 8bc444bb97cfeb747a66142ad31884323785af32 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Tue, 12 Mar 2024 16:16:52 +0100 Subject: [PATCH 042/299] Latte: Derive framebuffer size from correct mip of depth buffer --- src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp | 2 +- src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp | 4 ++-- src/Cafe/HW/Latte/Core/LatteTextureView.cpp | 3 +-- src/Cafe/HW/Latte/Core/LatteTextureView.h | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp index d7c5408f..f84bbecd 100644 --- a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp +++ b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp @@ -618,7 +618,7 @@ bool LatteMRT::UpdateCurrentFBO() } // set effective size sint32 effectiveWidth, effectiveHeight; - depthBufferView->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, 0); + depthBufferView->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, depthBufferView->firstMip); if (rtEffectiveSize->width == 0 && rtEffectiveSize->height == 0) { rtEffectiveSize->width = effectiveWidth; diff --git a/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp b/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp index 4e5c303c..b9ccbac4 100644 --- a/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp @@ -207,10 +207,10 @@ void LatteTexture_updateTexturesForStage(LatteDecompilerShader* shaderContext, u bool isDepthSampler = shaderContext->textureUsesDepthCompare[textureIndex]; // look for already existing texture LatteTextureView* textureView; - if (isDepthSampler == false) + if (!isDepthSampler) textureView = LatteTextureViewLookupCache::lookup(physAddr, width, height, depth, pitch, viewFirstMip, viewNumMips, viewFirstSlice, viewNumSlices, format, dim); else - textureView = LatteTextureViewLookupCache::lookup(physAddr, width, height, depth, pitch, viewFirstMip, viewNumMips, viewFirstSlice, viewNumSlices, format, dim, true); + textureView = LatteTextureViewLookupCache::lookupWithColorOrDepthType(physAddr, width, height, depth, pitch, viewFirstMip, viewNumMips, viewFirstSlice, viewNumSlices, format, dim, true); if (!textureView) { // view not found, create a new mapping which will also create a new texture if necessary diff --git a/src/Cafe/HW/Latte/Core/LatteTextureView.cpp b/src/Cafe/HW/Latte/Core/LatteTextureView.cpp index cac5bcce..2773a34d 100644 --- a/src/Cafe/HW/Latte/Core/LatteTextureView.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTextureView.cpp @@ -143,7 +143,6 @@ void LatteTextureViewLookupCache::RemoveAll(LatteTextureView* view) } } - LatteTextureView* LatteTextureViewLookupCache::lookup(MPTR physAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dim) { // todo - add tileMode param to this and the other lookup functions? @@ -163,7 +162,7 @@ LatteTextureView* LatteTextureViewLookupCache::lookup(MPTR physAddr, sint32 widt return nullptr; } -LatteTextureView* LatteTextureViewLookupCache::lookup(MPTR physAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dim, bool isDepth) +LatteTextureView* LatteTextureViewLookupCache::lookupWithColorOrDepthType(MPTR physAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dim, bool isDepth) { cemu_assert_debug(firstSlice == 0); uint32 key = _getViewBucketKey(physAddr, width, height, pitch); diff --git a/src/Cafe/HW/Latte/Core/LatteTextureView.h b/src/Cafe/HW/Latte/Core/LatteTextureView.h index a6d2e16c..abda084a 100644 --- a/src/Cafe/HW/Latte/Core/LatteTextureView.h +++ b/src/Cafe/HW/Latte/Core/LatteTextureView.h @@ -41,7 +41,7 @@ public: static void RemoveAll(LatteTextureView* view); static LatteTextureView* lookup(MPTR physAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dim); - static LatteTextureView* lookup(MPTR physAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dim, bool isDepth); + static LatteTextureView* lookupWithColorOrDepthType(MPTR physAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dim, bool isDepth); static LatteTextureView* lookupSlice(MPTR physAddr, sint32 width, sint32 height, sint32 pitch, sint32 firstMip, sint32 firstSlice, Latte::E_GX2SURFFMT format); static LatteTextureView* lookupSliceMinSize(MPTR physAddr, sint32 minWidth, sint32 minHeight, sint32 pitch, sint32 firstMip, sint32 firstSlice, Latte::E_GX2SURFFMT format); static LatteTextureView* lookupSliceEx(MPTR physAddr, sint32 width, sint32 height, sint32 pitch, sint32 firstMip, sint32 firstSlice, Latte::E_GX2SURFFMT format, bool isDepth); From bc04662525acbab50251a28a0a2b39d95fda7707 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 13 Mar 2024 02:41:42 +0100 Subject: [PATCH 043/299] Latte+GL+VK: Improve handling of gfx pack texture overwrite format Graphic packs can overwrite the format of a texture (e.g. for higher bitdepth to lessen banding) but the code for this wasn't correctly working anymore. - Fixes overwrite format being ignored for texture views on Vulkan backend - Fixes overwrite format not being used for texture views on OpenGL Format aliasing is complicated enough as it is, even without overwrites, so this adds a new rule to make behavior more well defined: If two textures share memory but only one uses an overwrite format, then they are no longer synchronized and are considered separate textures. Bonus fixes for OpenGL: - Use fbo 0 instead of -1 as the default. This silences some warnings in debug output - On OpenGL, bind new framebuffers on handle generation so they are considered created --- src/Cafe/HW/Latte/Core/LatteTexture.cpp | 11 ++++++ .../Latte/Renderer/OpenGL/LatteTextureGL.cpp | 2 +- .../Renderer/OpenGL/LatteTextureViewGL.cpp | 14 ++++--- .../Latte/Renderer/OpenGL/OpenGLRenderer.cpp | 37 ++++++++++--------- .../HW/Latte/Renderer/OpenGL/OpenGLRenderer.h | 2 +- .../Renderer/Vulkan/LatteTextureViewVk.cpp | 7 +++- 6 files changed, 48 insertions(+), 25 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/LatteTexture.cpp b/src/Cafe/HW/Latte/Core/LatteTexture.cpp index 91a1aa56..21b49c9a 100644 --- a/src/Cafe/HW/Latte/Core/LatteTexture.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTexture.cpp @@ -434,6 +434,11 @@ void LatteTexture_SyncSlice(LatteTexture* srcTexture, sint32 srcSliceIndex, sint sint32 dstWidth = dstTexture->width; sint32 dstHeight = dstTexture->height; + if(srcTexture->overwriteInfo.hasFormatOverwrite != dstTexture->overwriteInfo.hasFormatOverwrite) + return; // dont sync: format overwrite state needs to match. Not strictly necessary but it simplifies logic down the road + else if(srcTexture->overwriteInfo.hasFormatOverwrite && srcTexture->overwriteInfo.format != dstTexture->overwriteInfo.format) + return; // both are overwritten but with different formats + if (srcMipIndex == 0 && dstMipIndex == 0 && (srcTexture->tileMode == Latte::E_HWTILEMODE::TM_LINEAR_ALIGNED || srcTexture->tileMode == Latte::E_HWTILEMODE::TM_1D_TILED_THIN1) && srcTexture->height > dstTexture->height && (srcTexture->height % dstTexture->height) == 0) { bool isMatch = srcTexture->tileMode == Latte::E_HWTILEMODE::TM_LINEAR_ALIGNED; @@ -816,6 +821,12 @@ VIEWCOMPATIBILITY LatteTexture_CanTextureBeRepresentedAsView(LatteTexture* baseT { relativeMipIndex = 0; relativeSliceIndex = 0; + if (baseTexture->overwriteInfo.hasFormatOverwrite) + { + // if the base format is overwritten, then we only allow aliasing if the view format matches the base format + if (baseTexture->format != format) + return VIEW_NOT_COMPATIBLE; + } if (LatteTexture_IsFormatViewCompatible(baseTexture->format, format) == false) return VIEW_NOT_COMPATIBLE; if (baseTexture->physAddress == physAddr && baseTexture->pitch == pitch) diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp index 584af40c..cd363612 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp @@ -26,7 +26,7 @@ LatteTextureGL::LatteTextureGL(Latte::E_DIM dim, MPTR physAddress, MPTR physMipA GenerateEmptyTextureFromGX2Dim(dim, this->glId_texture, this->glTexTarget, true); // set format info FormatInfoGL glFormatInfo; - GetOpenGLFormatInfo(isDepth, format, dim, &glFormatInfo); + GetOpenGLFormatInfo(isDepth, overwriteInfo.hasFormatOverwrite ? (Latte::E_GX2SURFFMT)overwriteInfo.format : format, dim, &glFormatInfo); this->glInternalFormat = glFormatInfo.glInternalFormat; this->isAlternativeFormat = glFormatInfo.isUsingAlternativeFormat; this->hasStencil = glFormatInfo.hasStencil; // todo - should get this from the GX2 format? diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.cpp index 29085642..3e8abe8e 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureViewGL.cpp @@ -55,12 +55,16 @@ LatteTextureViewGL::~LatteTextureViewGL() void LatteTextureViewGL::InitAliasView() { const auto texture = (LatteTextureGL*)baseTexture; - // get internal format - if (baseTexture->isDepth) + // compute internal format + if(texture->overwriteInfo.hasFormatOverwrite) + { + cemu_assert_debug(format == texture->format); + glInternalFormat = texture->glInternalFormat; // for format overwrite no aliasing is allowed and thus we always inherit the internal format of the base texture + } + else if (baseTexture->isDepth) { // depth is handled differently - cemuLog_logDebug(LogType::Force, "Creating depth view"); - cemu_assert(format == texture->format); // todo + cemu_assert(format == texture->format); // is depth alias with different format intended? glInternalFormat = texture->glInternalFormat; } else @@ -73,7 +77,7 @@ void LatteTextureViewGL::InitAliasView() catchOpenGLError(); if (firstMip >= texture->maxPossibleMipLevels) { - cemuLog_logDebug(LogType::Force, "_createNewView: Out of bounds mip level requested"); + cemuLog_logDebug(LogType::Force, "InitAliasView(): Out of bounds mip level requested"); glTextureView(glTexId, glTexTarget, texture->glId_texture, glInternalFormat, texture->maxPossibleMipLevels - 1, numMip, firstSlice, this->numSlice); } else diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp index 68d7def6..943e39a0 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp @@ -330,13 +330,14 @@ void OpenGLRenderer::Initialize() lock.unlock(); // create framebuffer for fast clearing (avoid glClearTexSubImage on Nvidia) - if (this->m_vendor == GfxVendor::Nvidia || glClearTexSubImage == nullptr) + if (glCreateFramebuffers) + glCreateFramebuffers(1, &glRendererState.clearFBO); + else { - // generate framebuffer - if (glCreateFramebuffers && false) - glCreateFramebuffers(1, &glRendererState.clearFBO); - else - glGenFramebuffers(1, &glRendererState.clearFBO); + glGenFramebuffers(1, &glRendererState.clearFBO); + // bind to initialize + glBindFramebuffer(GL_FRAMEBUFFER_EXT, glRendererState.clearFBO); + glBindFramebuffer(GL_FRAMEBUFFER_EXT, 0); } draw_init(); @@ -425,9 +426,12 @@ void _glDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GL return; if (LatteGPUState.glVendor == GLVENDOR_NVIDIA && strstr(message, "Dithering is enabled")) return; - + if (LatteGPUState.glVendor == GLVENDOR_NVIDIA && strstr(message, "Blending is enabled, but is not supported for integer framebuffers")) + return; if (LatteGPUState.glVendor == GLVENDOR_NVIDIA && strstr(message, "does not have a defined base level")) return; + if(LatteGPUState.glVendor == GLVENDOR_NVIDIA && strstr(message, "has depth comparisons disabled, with a texture object")) + return; cemuLog_log(LogType::Force, "GLDEBUG: {}", message); @@ -670,7 +674,10 @@ void OpenGLRenderer::rendertarget_deleteCachedFBO(LatteCachedFBO* cfbo) { auto cfboGL = (CachedFBOGL*)cfbo; if (prevBoundFBO == cfboGL->glId_fbo) - prevBoundFBO = -1; + { + glBindFramebuffer(GL_FRAMEBUFFER_EXT, 0); + prevBoundFBO = 0; + } glDeleteFramebuffers(1, &cfboGL->glId_fbo); } @@ -1013,9 +1020,6 @@ void OpenGLRenderer::texture_reserveTextureOnGPU(LatteTexture* hostTextureGeneri effectiveBaseHeight = hostTexture->overwriteInfo.height; effectiveBaseDepth = hostTexture->overwriteInfo.depth; } - // get format info - LatteTextureGL::FormatInfoGL glFormatInfo; - LatteTextureGL::GetOpenGLFormatInfo(hostTexture->isDepth, hostTexture->overwriteInfo.hasFormatOverwrite ? (Latte::E_GX2SURFFMT)hostTexture->overwriteInfo.format : hostTexture->format, hostTexture->dim, &glFormatInfo); // calculate mip count sint32 mipLevels = std::min(hostTexture->mipLevels, hostTexture->maxPossibleMipLevels); mipLevels = std::max(mipLevels, 1); @@ -1023,25 +1027,25 @@ void OpenGLRenderer::texture_reserveTextureOnGPU(LatteTexture* hostTextureGeneri if (hostTexture->dim == Latte::E_DIM::DIM_2D || hostTexture->dim == Latte::E_DIM::DIM_2D_MSAA) { cemu_assert_debug(effectiveBaseDepth == 1); - glTextureStorage2DWrapper(GL_TEXTURE_2D, hostTexture->glId_texture, mipLevels, glFormatInfo.glInternalFormat, effectiveBaseWidth, effectiveBaseHeight); + glTextureStorage2DWrapper(GL_TEXTURE_2D, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth, effectiveBaseHeight); } else if (hostTexture->dim == Latte::E_DIM::DIM_1D) { cemu_assert_debug(effectiveBaseHeight == 1); cemu_assert_debug(effectiveBaseDepth == 1); - glTextureStorage1DWrapper(GL_TEXTURE_1D, hostTexture->glId_texture, mipLevels, glFormatInfo.glInternalFormat, effectiveBaseWidth); + glTextureStorage1DWrapper(GL_TEXTURE_1D, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth); } else if (hostTexture->dim == Latte::E_DIM::DIM_2D_ARRAY || hostTexture->dim == Latte::E_DIM::DIM_2D_ARRAY_MSAA) { - glTextureStorage3DWrapper(GL_TEXTURE_2D_ARRAY, hostTexture->glId_texture, mipLevels, glFormatInfo.glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, std::max(1, effectiveBaseDepth)); + glTextureStorage3DWrapper(GL_TEXTURE_2D_ARRAY, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, std::max(1, effectiveBaseDepth)); } else if (hostTexture->dim == Latte::E_DIM::DIM_3D) { - glTextureStorage3DWrapper(GL_TEXTURE_3D, hostTexture->glId_texture, mipLevels, glFormatInfo.glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, std::max(1, effectiveBaseDepth)); + glTextureStorage3DWrapper(GL_TEXTURE_3D, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, std::max(1, effectiveBaseDepth)); } else if (hostTexture->dim == Latte::E_DIM::DIM_CUBEMAP) { - glTextureStorage3DWrapper(GL_TEXTURE_CUBE_MAP_ARRAY, hostTexture->glId_texture, mipLevels, glFormatInfo.glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, effectiveBaseDepth); + glTextureStorage3DWrapper(GL_TEXTURE_CUBE_MAP_ARRAY, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, effectiveBaseDepth); } else { @@ -1279,7 +1283,6 @@ void OpenGLRenderer::texture_copyImageSubData(LatteTexture* src, sint32 srcMip, { auto srcGL = (LatteTextureGL*)src; auto dstGL = (LatteTextureGL*)dst; - if ((srcGL->isAlternativeFormat || dstGL->isAlternativeFormat) && (srcGL->glInternalFormat != dstGL->glInternalFormat)) { if (srcGL->format == Latte::E_GX2SURFFMT::R16_G16_B16_A16_UINT && dstGL->format == Latte::E_GX2SURFFMT::BC4_UNORM) diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h index 8a4b1a1d..026264cf 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h @@ -195,7 +195,7 @@ private: GLuint glStreamoutCacheRingBuffer; // cfbo - GLuint prevBoundFBO = -1; + GLuint prevBoundFBO = 0; GLuint glId_fbo = 0; // renderstate diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.cpp index d87d9ea7..aae7e9d1 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.cpp @@ -57,7 +57,12 @@ uint32 LatteTextureVk_AdjustTextureCompSel(Latte::E_GX2SURFFMT format, uint32 co LatteTextureViewVk::LatteTextureViewVk(VkDevice device, LatteTextureVk* texture, Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount) : LatteTextureView(texture, firstMip, mipCount, firstSlice, sliceCount, dim, format), m_device(device) { - if (dim != texture->dim || format != texture->format) + if(texture->overwriteInfo.hasFormatOverwrite) + { + cemu_assert_debug(format == texture->format); // if format overwrite is used, the texture is no longer taking part in aliasing and the format of any view has to match + m_format = texture->GetFormat(); + } + else if (dim != texture->dim || format != texture->format) { VulkanRenderer::FormatInfoVK texFormatInfo; VulkanRenderer::GetInstance()->GetTextureFormatInfoVK(format, texture->isDepth, dim, 0, 0, &texFormatInfo); From 193767e6cccafd971e9028386fdfe7f8f46b2d21 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Thu, 14 Mar 2024 01:04:05 +0100 Subject: [PATCH 044/299] Latte+Vulkan: Code cleanup Besides a general cleanup: - Remove deprecated resource destruction queues - Move functionality from renderer into Latte base classes to deduplicate code --- src/Cafe/HW/Latte/Core/Latte.h | 2 +- .../HW/Latte/Core/LatteCommandProcessor.cpp | 12 +-- src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp | 43 ++------- src/Cafe/HW/Latte/Core/LatteTexture.cpp | 10 +++ src/Cafe/HW/Latte/Core/LatteTexture.h | 2 + src/Cafe/HW/Latte/Core/LatteTextureCache.cpp | 2 +- src/Cafe/HW/Latte/Core/LatteTextureLoader.cpp | 2 +- .../Latte/Renderer/OpenGL/LatteTextureGL.cpp | 73 +++++++++++----- .../HW/Latte/Renderer/OpenGL/LatteTextureGL.h | 12 +-- .../Latte/Renderer/OpenGL/OpenGLRenderer.cpp | 51 ----------- .../HW/Latte/Renderer/OpenGL/OpenGLRenderer.h | 3 - src/Cafe/HW/Latte/Renderer/Renderer.h | 3 - .../HW/Latte/Renderer/Vulkan/CachedFBOVk.cpp | 4 +- .../Renderer/Vulkan/LatteTextureViewVk.cpp | 6 +- .../Latte/Renderer/Vulkan/LatteTextureVk.cpp | 14 ++- .../HW/Latte/Renderer/Vulkan/LatteTextureVk.h | 5 +- .../Renderer/Vulkan/RendererShaderVk.cpp | 3 +- .../Latte/Renderer/Vulkan/VKRPipelineInfo.cpp | 2 +- .../Vulkan/VulkanPipelineStableCache.cpp | 2 +- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 87 +------------------ .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 25 +----- .../Renderer/Vulkan/VulkanSurfaceCopy.cpp | 8 +- src/imgui/imgui_impl_vulkan.cpp | 13 +-- 23 files changed, 115 insertions(+), 269 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/Latte.h b/src/Cafe/HW/Latte/Core/Latte.h index dc3cbc91..d9419a6a 100644 --- a/src/Cafe/HW/Latte/Core/Latte.h +++ b/src/Cafe/HW/Latte/Core/Latte.h @@ -98,7 +98,7 @@ void LatteRenderTarget_copyToBackbuffer(LatteTextureView* textureView, bool isPa void LatteRenderTarget_GetCurrentVirtualViewportSize(sint32* viewportWidth, sint32* viewportHeight); void LatteRenderTarget_itHLESwapScanBuffer(); -void LatteRenderTarget_itHLEClearColorDepthStencil(uint32 clearMask, MPTR colorBufferMPTR, MPTR colorBufferFormat, Latte::E_HWTILEMODE colorBufferTilemode, uint32 colorBufferWidth, uint32 colorBufferHeight, uint32 colorBufferPitch, uint32 colorBufferViewFirstSlice, uint32 colorBufferViewNumSlice, MPTR depthBufferMPTR, MPTR depthBufferFormat, Latte::E_HWTILEMODE depthBufferTileMode, sint32 depthBufferWidth, sint32 depthBufferHeight, sint32 depthBufferPitch, sint32 depthBufferViewFirstSlice, sint32 depthBufferViewNumSlice, float r, float g, float b, float a, float clearDepth, uint32 clearStencil); +void LatteRenderTarget_itHLEClearColorDepthStencil(uint32 clearMask, MPTR colorBufferMPTR, Latte::E_GX2SURFFMT colorBufferFormat, Latte::E_HWTILEMODE colorBufferTilemode, uint32 colorBufferWidth, uint32 colorBufferHeight, uint32 colorBufferPitch, uint32 colorBufferViewFirstSlice, uint32 colorBufferViewNumSlice, MPTR depthBufferMPTR, Latte::E_GX2SURFFMT depthBufferFormat, Latte::E_HWTILEMODE depthBufferTileMode, sint32 depthBufferWidth, sint32 depthBufferHeight, sint32 depthBufferPitch, sint32 depthBufferViewFirstSlice, sint32 depthBufferViewNumSlice, float r, float g, float b, float a, float clearDepth, uint32 clearStencil); void LatteRenderTarget_itHLECopyColorBufferToScanBuffer(MPTR colorBufferPtr, uint32 colorBufferWidth, uint32 colorBufferHeight, uint32 colorBufferSliceIndex, uint32 colorBufferFormat, uint32 colorBufferPitch, Latte::E_HWTILEMODE colorBufferTilemode, uint32 colorBufferSwizzle, uint32 renderTarget); void LatteRenderTarget_unloadAll(); diff --git a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp index 60e5935c..c928f89f 100644 --- a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp +++ b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp @@ -864,8 +864,8 @@ LatteCMDPtr LatteCP_itHLEClearColorDepthStencil(LatteCMDPtr cmd, uint32 nWords) cemu_assert_debug(nWords == 23); uint32 clearMask = LatteReadCMD(); // color (1), depth (2), stencil (4) // color buffer - MPTR colorBufferMPTR = LatteReadCMD(); // MPTR for color buffer (physical address) - MPTR colorBufferFormat = LatteReadCMD(); // format for color buffer + MPTR colorBufferMPTR = LatteReadCMD(); // physical address for color buffer + Latte::E_GX2SURFFMT colorBufferFormat = (Latte::E_GX2SURFFMT)LatteReadCMD(); Latte::E_HWTILEMODE colorBufferTilemode = (Latte::E_HWTILEMODE)LatteReadCMD(); uint32 colorBufferWidth = LatteReadCMD(); uint32 colorBufferHeight = LatteReadCMD(); @@ -873,8 +873,8 @@ LatteCMDPtr LatteCP_itHLEClearColorDepthStencil(LatteCMDPtr cmd, uint32 nWords) uint32 colorBufferViewFirstSlice = LatteReadCMD(); uint32 colorBufferViewNumSlice = LatteReadCMD(); // depth buffer - MPTR depthBufferMPTR = LatteReadCMD(); // MPTR for depth buffer (physical address) - MPTR depthBufferFormat = LatteReadCMD(); // format for depth buffer + MPTR depthBufferMPTR = LatteReadCMD(); // physical address for depth buffer + Latte::E_GX2SURFFMT depthBufferFormat = (Latte::E_GX2SURFFMT)LatteReadCMD(); Latte::E_HWTILEMODE depthBufferTileMode = (Latte::E_HWTILEMODE)LatteReadCMD(); uint32 depthBufferWidth = LatteReadCMD(); uint32 depthBufferHeight = LatteReadCMD(); @@ -893,8 +893,8 @@ LatteCMDPtr LatteCP_itHLEClearColorDepthStencil(LatteCMDPtr cmd, uint32 nWords) LatteRenderTarget_itHLEClearColorDepthStencil( clearMask, - colorBufferMPTR, colorBufferFormat, colorBufferTilemode, colorBufferWidth, colorBufferHeight, colorBufferPitch, colorBufferViewFirstSlice, colorBufferViewNumSlice, - depthBufferMPTR, depthBufferFormat, depthBufferTileMode, depthBufferWidth, depthBufferHeight, depthBufferPitch, depthBufferViewFirstSlice, depthBufferViewNumSlice, + colorBufferMPTR, colorBufferFormat, colorBufferTilemode, colorBufferWidth, colorBufferHeight, colorBufferPitch, colorBufferViewFirstSlice, colorBufferViewNumSlice, + depthBufferMPTR, depthBufferFormat, depthBufferTileMode, depthBufferWidth, depthBufferHeight, depthBufferPitch, depthBufferViewFirstSlice, depthBufferViewNumSlice, r, g, b, a, clearDepth, clearStencil); return cmd; diff --git a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp index f84bbecd..30069712 100644 --- a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp +++ b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp @@ -221,35 +221,9 @@ void LatteMRT::BindDepthBufferOnly(LatteTextureView* view) ApplyCurrentState(); } -/***************************************************/ - -LatteTextureView* LatteMRT_FindColorBufferForClearing(MPTR colorBufferPtr, sint32 colorBufferWidth, sint32 colorBufferHeight, sint32 colorBufferPitch, uint32 format, sint32 sliceIndex, sint32* searchIndex) -{ - LatteTextureView* view = LatteTC_LookupTextureByData(colorBufferPtr, colorBufferWidth, colorBufferHeight, colorBufferPitch, 0, 1, sliceIndex, 1, searchIndex); - if (view == nullptr) - return nullptr; - return view; -} - -LatteTextureView* LatteMRT_CreateColorBuffer(MPTR colorBufferPhysMem, uint32 width, uint32 height, uint32 pitch, Latte::E_GX2SURFFMT format, Latte::E_HWTILEMODE tileMode, uint32 swizzle, uint32 viewSlice) -{ - cemu_assert_debug(colorBufferPhysMem != MPTR_NULL); - LatteTextureView* textureView; - if(viewSlice != 0) - textureView = LatteTexture_CreateMapping(colorBufferPhysMem, MPTR_NULL, width, height, viewSlice+1, pitch, tileMode, swizzle, 0, 1, viewSlice, 1, format, Latte::E_DIM::DIM_2D_ARRAY, Latte::E_DIM::DIM_2D, false); - else - textureView = LatteTexture_CreateMapping(colorBufferPhysMem, MPTR_NULL, width, height, 1, pitch, tileMode, swizzle, 0, 1, viewSlice, 1, format, Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, false); - return textureView; -} - LatteTextureView* LatteMRT_CreateDepthBuffer(MPTR depthBufferPhysMem, uint32 width, uint32 height, uint32 pitch, Latte::E_HWTILEMODE tileMode, Latte::E_GX2SURFFMT format, uint32 swizzle, sint32 viewSlice) { - LatteTextureView* textureView; - if(viewSlice == 0) - textureView = LatteTexture_CreateMapping(depthBufferPhysMem, MPTR_NULL, width, height, 1, pitch, tileMode, swizzle, 0, 1, viewSlice, 1, format, Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, true); - else - textureView = LatteTexture_CreateMapping(depthBufferPhysMem, MPTR_NULL, width, height, viewSlice+1, pitch, tileMode, swizzle, 0, 1, viewSlice, 1, format, Latte::E_DIM::DIM_2D_ARRAY, Latte::E_DIM::DIM_2D, true); - + LatteTextureView* textureView = LatteTexture_CreateMapping(depthBufferPhysMem, MPTR_NULL, width, height, viewSlice+1, pitch, tileMode, swizzle, 0, 1, viewSlice, 1, format, viewSlice > 0 ? Latte::E_DIM::DIM_2D_ARRAY : Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, true); LatteMRT::SetDepthAndStencilAttachment(textureView, textureView->baseTexture->hasStencil); return textureView; } @@ -605,7 +579,7 @@ bool LatteMRT::UpdateCurrentFBO() if (depthBufferPhysMem != MPTR_NULL) { LatteTextureView* depthBufferView = LatteTextureViewLookupCache::lookupSliceEx(depthBufferPhysMem, depthBufferWidth, depthBufferHeight, depthBufferPitch, 0, depthBufferViewFirstSlice, depthBufferFormat, true); - if (depthBufferView == nullptr) + if (!depthBufferView) { // create new depth buffer view and if it doesn't exist then also create the texture depthBufferView = LatteTexture_CreateMapping(depthBufferPhysMem, 0, depthBufferWidth, depthBufferHeight, depthBufferViewFirstSlice+1, depthBufferPitch, depthBufferTileMode, depthBufferSwizzle, 0, 1, depthBufferViewFirstSlice, 1, depthBufferFormat, depthBufferViewFirstSlice > 0 ? Latte::E_DIM::DIM_2D_ARRAY : Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, true); @@ -768,7 +742,10 @@ void LatteRenderTarget_applyTextureDepthClear(LatteTexture* texture, uint32 slic LatteTexture_MarkDynamicTextureAsChanged(texture->baseView, sliceIndex, mipIndex, eventCounter); } -void LatteRenderTarget_itHLEClearColorDepthStencil(uint32 clearMask, MPTR colorBufferMPTR, MPTR colorBufferFormat, Latte::E_HWTILEMODE colorBufferTilemode, uint32 colorBufferWidth, uint32 colorBufferHeight, uint32 colorBufferPitch, uint32 colorBufferViewFirstSlice, uint32 colorBufferViewNumSlice, MPTR depthBufferMPTR, MPTR depthBufferFormat, Latte::E_HWTILEMODE depthBufferTileMode, sint32 depthBufferWidth, sint32 depthBufferHeight, sint32 depthBufferPitch, sint32 depthBufferViewFirstSlice, sint32 depthBufferViewNumSlice, float r, float g, float b, float a, float clearDepth, uint32 clearStencil) +void LatteRenderTarget_itHLEClearColorDepthStencil(uint32 clearMask, + MPTR colorBufferMPTR, Latte::E_GX2SURFFMT colorBufferFormat, Latte::E_HWTILEMODE colorBufferTilemode, uint32 colorBufferWidth, uint32 colorBufferHeight, uint32 colorBufferPitch, uint32 colorBufferViewFirstSlice, uint32 colorBufferViewNumSlice, + MPTR depthBufferMPTR, Latte::E_GX2SURFFMT depthBufferFormat, Latte::E_HWTILEMODE depthBufferTileMode, sint32 depthBufferWidth, sint32 depthBufferHeight, sint32 depthBufferPitch, sint32 depthBufferViewFirstSlice, sint32 depthBufferViewNumSlice, + float r, float g, float b, float a, float clearDepth, uint32 clearStencil) { uint32 depthBufferMipIndex = 0; // todo uint32 colorBufferMipIndex = 0; // todo @@ -803,13 +780,11 @@ void LatteRenderTarget_itHLEClearColorDepthStencil(uint32 clearMask, MPTR colorB bool targetFound = false; while (true) { - LatteTextureView* colorView = LatteMRT_FindColorBufferForClearing(colorBufferMPTR, colorBufferWidth, colorBufferHeight, colorBufferPitch, colorBufferFormat, colorBufferViewFirstSlice, &searchIndex); + LatteTextureView* colorView = LatteTC_LookupTextureByData(colorBufferMPTR, colorBufferWidth, colorBufferHeight, colorBufferPitch, 0, 1, colorBufferViewFirstSlice, 1, &searchIndex); if (!colorView) break; - if (Latte::GetFormatBits((Latte::E_GX2SURFFMT)colorBufferFormat) != Latte::GetFormatBits(colorView->baseTexture->format)) - { + if (Latte::GetFormatBits(colorBufferFormat) != Latte::GetFormatBits(colorView->baseTexture->format)) continue; - } if (colorView->baseTexture->pitch == colorBufferPitch && colorView->baseTexture->height == colorBufferHeight) targetFound = true; @@ -821,7 +796,7 @@ void LatteRenderTarget_itHLEClearColorDepthStencil(uint32 clearMask, MPTR colorB { // create new texture with matching format cemu_assert_debug(colorBufferViewNumSlice <= 1); - LatteTextureView* newColorView = LatteMRT_CreateColorBuffer(colorBufferMPTR, colorBufferWidth, colorBufferHeight, colorBufferPitch, (Latte::E_GX2SURFFMT)colorBufferFormat, colorBufferTilemode, colorBufferSwizzle, colorBufferViewFirstSlice); + LatteTextureView* newColorView = LatteTexture_CreateMapping(colorBufferMPTR, MPTR_NULL, colorBufferWidth, colorBufferHeight, colorBufferViewFirstSlice+1, colorBufferPitch, colorBufferTilemode, colorBufferSwizzle, 0, 1, colorBufferViewFirstSlice, 1, colorBufferFormat, colorBufferViewFirstSlice > 0 ? Latte::E_DIM::DIM_2D_ARRAY : Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, false); LatteRenderTarget_applyTextureColorClear(newColorView->baseTexture, colorBufferViewFirstSlice, colorBufferMipIndex, r, g, b, a, eventCounter); } } diff --git a/src/Cafe/HW/Latte/Core/LatteTexture.cpp b/src/Cafe/HW/Latte/Core/LatteTexture.cpp index 21b49c9a..d6f576d4 100644 --- a/src/Cafe/HW/Latte/Core/LatteTexture.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTexture.cpp @@ -1199,6 +1199,15 @@ std::vector& LatteTexture::GetAllTextures() return sAllTextures; } +bool LatteTexture_GX2FormatHasStencil(bool isDepth, Latte::E_GX2SURFFMT format) +{ + if (!isDepth) + return false; + return format == Latte::E_GX2SURFFMT::D24_S8_UNORM || + format == Latte::E_GX2SURFFMT::D24_S8_FLOAT || + format == Latte::E_GX2SURFFMT::D32_S8_FLOAT; +} + LatteTexture::LatteTexture(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth) { @@ -1217,6 +1226,7 @@ LatteTexture::LatteTexture(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddre this->mipLevels = mipLevels; this->tileMode = tileMode; this->isDepth = isDepth; + this->hasStencil = LatteTexture_GX2FormatHasStencil(isDepth, format); this->physMipAddress = physMipAddress; this->lastUpdateEventCounter = LatteTexture_getNextUpdateEventCounter(); this->lastWriteEventCounter = LatteTexture_getNextUpdateEventCounter(); diff --git a/src/Cafe/HW/Latte/Core/LatteTexture.h b/src/Cafe/HW/Latte/Core/LatteTexture.h index b46c1323..6c09e840 100644 --- a/src/Cafe/HW/Latte/Core/LatteTexture.h +++ b/src/Cafe/HW/Latte/Core/LatteTexture.h @@ -27,6 +27,8 @@ public: LatteTexture(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth); virtual ~LatteTexture(); + virtual void AllocateOnHost() = 0; + LatteTextureView* GetOrCreateView(Latte::E_DIM dim, Latte::E_GX2SURFFMT format, sint32 firstMip, sint32 mipCount, sint32 firstSlice, sint32 sliceCount) { for (auto& itr : views) diff --git a/src/Cafe/HW/Latte/Core/LatteTextureCache.cpp b/src/Cafe/HW/Latte/Core/LatteTextureCache.cpp index a71bd6a6..3145e90e 100644 --- a/src/Cafe/HW/Latte/Core/LatteTextureCache.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTextureCache.cpp @@ -316,7 +316,7 @@ void LatteTexture_Delete(LatteTexture* texture) delete[] texture->sliceMipInfo; texture->sliceMipInfo = nullptr; } - g_renderer->texture_destroy(texture); + delete texture; } /* diff --git a/src/Cafe/HW/Latte/Core/LatteTextureLoader.cpp b/src/Cafe/HW/Latte/Core/LatteTextureLoader.cpp index 862fff06..c06a3bf1 100644 --- a/src/Cafe/HW/Latte/Core/LatteTextureLoader.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTextureLoader.cpp @@ -621,7 +621,7 @@ void LatteTextureLoader_UpdateTextureSliceData(LatteTexture* tex, uint32 sliceIn if (tex->isDataDefined == false) { - g_renderer->texture_reserveTextureOnGPU(tex); + tex->AllocateOnHost(); tex->isDataDefined = true; // if decoder is not set then clear texture // on Vulkan this is used to make sure the texture is no longer in UNDEFINED layout diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp index cd363612..58805925 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp @@ -5,20 +5,6 @@ #include "config/LaunchSettings.h" -GLuint texIdPool[64]; -sint32 texIdPoolIndex = 64; - -static GLuint _genTextureHandleGL() -{ - if (texIdPoolIndex == 64) - { - glGenTextures(64, texIdPool); - texIdPoolIndex = 0; - } - texIdPoolIndex++; - return texIdPool[texIdPoolIndex - 1]; -} - LatteTextureGL::LatteTextureGL(Latte::E_DIM dim, MPTR physAddress, MPTR physMipAddress, Latte::E_GX2SURFFMT format, uint32 width, uint32 height, uint32 depth, uint32 pitch, uint32 mipLevels, uint32 swizzle, Latte::E_HWTILEMODE tileMode, bool isDepth) : LatteTexture(dim, physAddress, physMipAddress, format, width, height, depth, pitch, mipLevels, swizzle, tileMode, isDepth) @@ -29,7 +15,6 @@ LatteTextureGL::LatteTextureGL(Latte::E_DIM dim, MPTR physAddress, MPTR physMipA GetOpenGLFormatInfo(isDepth, overwriteInfo.hasFormatOverwrite ? (Latte::E_GX2SURFFMT)overwriteInfo.format : format, dim, &glFormatInfo); this->glInternalFormat = glFormatInfo.glInternalFormat; this->isAlternativeFormat = glFormatInfo.isUsingAlternativeFormat; - this->hasStencil = glFormatInfo.hasStencil; // todo - should get this from the GX2 format? // set debug name bool useGLDebugNames = false; #ifdef CEMU_DEBUG_ASSERT @@ -88,34 +73,34 @@ void LatteTextureGL::GetOpenGLFormatInfo(bool isDepth, Latte::E_GX2SURFFMT forma { if (format == Latte::E_GX2SURFFMT::D24_S8_UNORM) { - formatInfoOut->setDepthFormat(GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, true); + formatInfoOut->setFormat(GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8); return; } else if (format == Latte::E_GX2SURFFMT::D24_S8_FLOAT) { - formatInfoOut->setDepthFormat(GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL, GL_FLOAT_32_UNSIGNED_INT_24_8_REV, true); + formatInfoOut->setFormat(GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL, GL_FLOAT_32_UNSIGNED_INT_24_8_REV); formatInfoOut->markAsAlternativeFormat(); return; } else if (format == Latte::E_GX2SURFFMT::D32_S8_FLOAT) { - formatInfoOut->setDepthFormat(GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL, GL_FLOAT_32_UNSIGNED_INT_24_8_REV, true); + formatInfoOut->setFormat(GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL, GL_FLOAT_32_UNSIGNED_INT_24_8_REV); return; } else if (format == Latte::E_GX2SURFFMT::D32_FLOAT) { - formatInfoOut->setDepthFormat(GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT, false); + formatInfoOut->setFormat(GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT); return; } else if (format == Latte::E_GX2SURFFMT::D16_UNORM) { - formatInfoOut->setDepthFormat(GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, false); + formatInfoOut->setFormat(GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT); return; } // unsupported depth format cemuLog_log(LogType::Force, "OpenGL: Unsupported texture depth format 0x{:04x}", (uint32)format); // use placeholder format - formatInfoOut->setDepthFormat(GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, false); + formatInfoOut->setFormat(GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT); formatInfoOut->markAsAlternativeFormat(); return; } @@ -496,3 +481,49 @@ void LatteTextureGL::GetOpenGLFormatInfo(bool isDepth, Latte::E_GX2SURFFMT forma formatInfoOut->glIsCompressed = glIsCompressed; formatInfoOut->isUsingAlternativeFormat = isUsingAlternativeFormat; } + +void LatteTextureGL::AllocateOnHost() +{ + auto hostTexture = this; + cemu_assert_debug(hostTexture->isDataDefined == false); + sint32 effectiveBaseWidth = hostTexture->width; + sint32 effectiveBaseHeight = hostTexture->height; + sint32 effectiveBaseDepth = hostTexture->depth; + if (hostTexture->overwriteInfo.hasResolutionOverwrite) + { + effectiveBaseWidth = hostTexture->overwriteInfo.width; + effectiveBaseHeight = hostTexture->overwriteInfo.height; + effectiveBaseDepth = hostTexture->overwriteInfo.depth; + } + // calculate mip count + sint32 mipLevels = std::min(hostTexture->mipLevels, hostTexture->maxPossibleMipLevels); + mipLevels = std::max(mipLevels, 1); + // create immutable storage + if (hostTexture->dim == Latte::E_DIM::DIM_2D || hostTexture->dim == Latte::E_DIM::DIM_2D_MSAA) + { + cemu_assert_debug(effectiveBaseDepth == 1); + glTextureStorage2DWrapper(GL_TEXTURE_2D, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth, effectiveBaseHeight); + } + else if (hostTexture->dim == Latte::E_DIM::DIM_1D) + { + cemu_assert_debug(effectiveBaseHeight == 1); + cemu_assert_debug(effectiveBaseDepth == 1); + glTextureStorage1DWrapper(GL_TEXTURE_1D, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth); + } + else if (hostTexture->dim == Latte::E_DIM::DIM_2D_ARRAY || hostTexture->dim == Latte::E_DIM::DIM_2D_ARRAY_MSAA) + { + glTextureStorage3DWrapper(GL_TEXTURE_2D_ARRAY, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, std::max(1, effectiveBaseDepth)); + } + else if (hostTexture->dim == Latte::E_DIM::DIM_3D) + { + glTextureStorage3DWrapper(GL_TEXTURE_3D, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, std::max(1, effectiveBaseDepth)); + } + else if (hostTexture->dim == Latte::E_DIM::DIM_CUBEMAP) + { + glTextureStorage3DWrapper(GL_TEXTURE_CUBE_MAP_ARRAY, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, effectiveBaseDepth); + } + else + { + cemu_assert_unimplemented(); + } +} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.h b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.h index 9169bb29..abfb0d43 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.h +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.h @@ -11,6 +11,8 @@ public: ~LatteTextureGL(); + void AllocateOnHost() override; + static void GenerateEmptyTextureFromGX2Dim(Latte::E_DIM dim, GLuint& texId, GLint& texTarget, bool createForTargetType); protected: @@ -23,7 +25,6 @@ public: sint32 glSuppliedFormat; sint32 glSuppliedFormatType; bool glIsCompressed; - bool hasStencil{}; bool isUsingAlternativeFormat{}; void setFormat(sint32 glInternalFormat, sint32 glSuppliedFormat, sint32 glSuppliedFormatType) @@ -34,15 +35,6 @@ public: this->glIsCompressed = false; } - void setDepthFormat(sint32 glInternalFormat, sint32 glSuppliedFormat, sint32 glSuppliedFormatType, bool hasStencil) - { - this->glInternalFormat = glInternalFormat; - this->glSuppliedFormat = glSuppliedFormat; - this->glSuppliedFormatType = glSuppliedFormatType; - this->glIsCompressed = false; - this->hasStencil = hasStencil; - } - void setCompressed(sint32 glInternalFormat, sint32 glSuppliedFormat, sint32 glSuppliedFormatType) { setFormat(glInternalFormat, glSuppliedFormat, glSuppliedFormatType); diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp index 943e39a0..604744cf 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp @@ -1002,57 +1002,6 @@ TextureDecoder* OpenGLRenderer::texture_chooseDecodedFormat(Latte::E_GX2SURFFMT return texDecoder; } -void OpenGLRenderer::texture_destroy(LatteTexture* hostTexture) -{ - delete hostTexture; -} - -void OpenGLRenderer::texture_reserveTextureOnGPU(LatteTexture* hostTextureGeneric) -{ - auto hostTexture = (LatteTextureGL*)hostTextureGeneric; - cemu_assert_debug(hostTexture->isDataDefined == false); - sint32 effectiveBaseWidth = hostTexture->width; - sint32 effectiveBaseHeight = hostTexture->height; - sint32 effectiveBaseDepth = hostTexture->depth; - if (hostTexture->overwriteInfo.hasResolutionOverwrite) - { - effectiveBaseWidth = hostTexture->overwriteInfo.width; - effectiveBaseHeight = hostTexture->overwriteInfo.height; - effectiveBaseDepth = hostTexture->overwriteInfo.depth; - } - // calculate mip count - sint32 mipLevels = std::min(hostTexture->mipLevels, hostTexture->maxPossibleMipLevels); - mipLevels = std::max(mipLevels, 1); - // create immutable storage - if (hostTexture->dim == Latte::E_DIM::DIM_2D || hostTexture->dim == Latte::E_DIM::DIM_2D_MSAA) - { - cemu_assert_debug(effectiveBaseDepth == 1); - glTextureStorage2DWrapper(GL_TEXTURE_2D, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth, effectiveBaseHeight); - } - else if (hostTexture->dim == Latte::E_DIM::DIM_1D) - { - cemu_assert_debug(effectiveBaseHeight == 1); - cemu_assert_debug(effectiveBaseDepth == 1); - glTextureStorage1DWrapper(GL_TEXTURE_1D, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth); - } - else if (hostTexture->dim == Latte::E_DIM::DIM_2D_ARRAY || hostTexture->dim == Latte::E_DIM::DIM_2D_ARRAY_MSAA) - { - glTextureStorage3DWrapper(GL_TEXTURE_2D_ARRAY, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, std::max(1, effectiveBaseDepth)); - } - else if (hostTexture->dim == Latte::E_DIM::DIM_3D) - { - glTextureStorage3DWrapper(GL_TEXTURE_3D, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, std::max(1, effectiveBaseDepth)); - } - else if (hostTexture->dim == Latte::E_DIM::DIM_CUBEMAP) - { - glTextureStorage3DWrapper(GL_TEXTURE_CUBE_MAP_ARRAY, hostTexture->glId_texture, mipLevels, hostTexture->glInternalFormat, effectiveBaseWidth, effectiveBaseHeight, effectiveBaseDepth); - } - else - { - cemu_assert_unimplemented(); - } -} - // use standard API to upload texture data void OpenGLRenderer_texture_loadSlice_normal(LatteTexture* hostTextureGeneric, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 imageSize) { diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h index 026264cf..3a892191 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h @@ -66,14 +66,11 @@ public: void renderstate_updateTextureSettingsGL(LatteDecompilerShader* shaderContext, LatteTextureView* _hostTextureView, uint32 hostTextureUnit, const Latte::LATTE_SQ_TEX_RESOURCE_WORD4_N texUnitWord4, uint32 texUnitIndex, bool isDepthSampler); // texture functions - void texture_destroy(LatteTexture* hostTexture) override; - void* texture_acquireTextureUploadBuffer(uint32 size) override; void texture_releaseTextureUploadBuffer(uint8* mem) override; TextureDecoder* texture_chooseDecodedFormat(Latte::E_GX2SURFFMT format, bool isDepth, Latte::E_DIM dim, uint32 width, uint32 height) override; - void texture_reserveTextureOnGPU(LatteTexture* hostTexture) override; void texture_clearSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex) override; void texture_loadSlice(LatteTexture* hostTexture, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 compressedImageSize) override; void texture_clearColorSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex, float r, float g, float b, float a) override; diff --git a/src/Cafe/HW/Latte/Renderer/Renderer.h b/src/Cafe/HW/Latte/Renderer/Renderer.h index 93edaf8d..2a9a1d1b 100644 --- a/src/Cafe/HW/Latte/Renderer/Renderer.h +++ b/src/Cafe/HW/Latte/Renderer/Renderer.h @@ -97,14 +97,11 @@ public: virtual void rendertarget_bindFramebufferObject(LatteCachedFBO* cfbo) = 0; // texture functions - virtual void texture_destroy(LatteTexture* hostTexture) = 0; - virtual void* texture_acquireTextureUploadBuffer(uint32 size) = 0; virtual void texture_releaseTextureUploadBuffer(uint8* mem) = 0; virtual TextureDecoder* texture_chooseDecodedFormat(Latte::E_GX2SURFFMT format, bool isDepth, Latte::E_DIM dim, uint32 width, uint32 height) = 0; - virtual void texture_reserveTextureOnGPU(LatteTexture* hostTexture) = 0; virtual void texture_clearSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex) = 0; virtual void texture_loadSlice(LatteTexture* hostTexture, sint32 width, sint32 height, sint32 depth, void* pixelData, sint32 sliceIndex, sint32 mipIndex, uint32 compressedImageSize) = 0; virtual void texture_clearColorSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex, float r, float g, float b, float a) = 0; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.cpp index 66f7ba95..8a999000 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/CachedFBOVk.cpp @@ -44,9 +44,9 @@ CachedFBOVk::~CachedFBOVk() while (!m_usedByPipelines.empty()) delete m_usedByPipelines[0]; auto vkr = VulkanRenderer::GetInstance(); - vkr->releaseDestructibleObject(m_vkrObjFramebuffer); + vkr->ReleaseDestructibleObject(m_vkrObjFramebuffer); m_vkrObjFramebuffer = nullptr; - vkr->releaseDestructibleObject(m_vkrObjRenderPass); + vkr->ReleaseDestructibleObject(m_vkrObjRenderPass); m_vkrObjRenderPass = nullptr; } diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.cpp index aae7e9d1..f0e2295e 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.cpp @@ -79,14 +79,14 @@ LatteTextureViewVk::~LatteTextureViewVk() delete list_descriptorSets[0]; if (m_smallCacheView0) - VulkanRenderer::GetInstance()->releaseDestructibleObject(m_smallCacheView0); + VulkanRenderer::GetInstance()->ReleaseDestructibleObject(m_smallCacheView0); if (m_smallCacheView1) - VulkanRenderer::GetInstance()->releaseDestructibleObject(m_smallCacheView1); + VulkanRenderer::GetInstance()->ReleaseDestructibleObject(m_smallCacheView1); if (m_fallbackCache) { for (auto& itr : *m_fallbackCache) - VulkanRenderer::GetInstance()->releaseDestructibleObject(itr.second); + VulkanRenderer::GetInstance()->ReleaseDestructibleObject(itr.second); delete m_fallbackCache; m_fallbackCache = nullptr; } diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp index b5f62707..a62741e4 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.cpp @@ -46,7 +46,7 @@ LatteTextureVk::LatteTextureVk(class VulkanRenderer* vkRenderer, Latte::E_DIM di VulkanRenderer::FormatInfoVK texFormatInfo; vkRenderer->GetTextureFormatInfoVK(format, isDepth, dim, effectiveBaseWidth, effectiveBaseHeight, &texFormatInfo); - hasStencil = (texFormatInfo.vkImageAspect & VK_IMAGE_ASPECT_STENCIL_BIT) != 0; + cemu_assert_debug(hasStencil == ((texFormatInfo.vkImageAspect & VK_IMAGE_ASPECT_STENCIL_BIT) != 0)); imageInfo.format = texFormatInfo.vkImageFormat; vkObjTex->m_imageAspect = texFormatInfo.vkImageAspect; @@ -117,7 +117,7 @@ LatteTextureVk::~LatteTextureVk() m_vkr->surfaceCopy_notifyTextureRelease(this); - VulkanRenderer::GetInstance()->releaseDestructibleObject(vkObjTex); + VulkanRenderer::GetInstance()->ReleaseDestructibleObject(vkObjTex); vkObjTex = nullptr; } @@ -130,12 +130,8 @@ LatteTextureView* LatteTextureVk::CreateView(Latte::E_DIM dim, Latte::E_GX2SURFF return new LatteTextureViewVk(m_vkr->GetLogicalDevice(), this, dim, format, firstMip, mipCount, firstSlice, sliceCount); } -void LatteTextureVk::setAllocation(struct VkImageMemAllocation* memAllocation) +void LatteTextureVk::AllocateOnHost() { - vkObjTex->m_allocation = memAllocation; -} - -struct VkImageMemAllocation* LatteTextureVk::getAllocation() const -{ - return vkObjTex->m_allocation; + auto allocationInfo = VulkanRenderer::GetInstance()->GetMemoryManager()->imageMemoryAllocate(GetImageObj()->m_image); + vkObjTex->m_allocation = allocationInfo; } diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h index 714c4e17..612e2e70 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureVk.h @@ -14,14 +14,13 @@ public: ~LatteTextureVk(); + void AllocateOnHost() override; + VKRObjectTexture* GetImageObj() const { return vkObjTex; }; VkFormat GetFormat() const { return vkObjTex->m_format; } VkImageAspectFlags GetImageAspect() const { return vkObjTex->m_imageAspect; } - void setAllocation(struct VkImageMemAllocation* memAllocation); - struct VkImageMemAllocation* getAllocation() const; - VkImageLayout GetImageLayout(VkImageSubresource& subresource) { cemu_assert_debug(subresource.mipLevel < m_layoutsMips); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp index 437ef51d..15ea6e89 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp @@ -207,7 +207,8 @@ RendererShaderVk::RendererShaderVk(ShaderType type, uint64 baseHash, uint64 auxH RendererShaderVk::~RendererShaderVk() { - VulkanRenderer::GetInstance()->destroyShader(this); + while (!list_pipelineInfo.empty()) + delete list_pipelineInfo[0]; } void RendererShaderVk::Init() diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp index 72a1be4c..fd5a5b78 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp @@ -84,7 +84,7 @@ PipelineInfo::~PipelineInfo() // queue pipeline for destruction if (m_vkrObjPipeline) { - VulkanRenderer::GetInstance()->releaseDestructibleObject(m_vkrObjPipeline); + VulkanRenderer::GetInstance()->ReleaseDestructibleObject(m_vkrObjPipeline); m_vkrObjPipeline = nullptr; } diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp index 0ee9f023..2be9a2f4 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp @@ -300,7 +300,7 @@ void VulkanPipelineStableCache::LoadPipelineFromCache(std::span fileData) delete pipelineInfo; delete lcr; delete cachedPipeline; - VulkanRenderer::GetInstance()->releaseDestructibleObject(renderPass); + VulkanRenderer::GetInstance()->ReleaseDestructibleObject(renderPass); s_spinlockSharedInternal.unlock(); } diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index c7f8c043..d0305317 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -1803,44 +1803,6 @@ void VulkanRenderer::PreparePresentationFrame(bool mainWindow) AcquireNextSwapchainImage(mainWindow); } -void VulkanRenderer::ProcessDestructionQueues(size_t commandBufferIndex) -{ - auto& current_descriptor_cache = m_destructionQueues.m_cmd_descriptor_set_objects[commandBufferIndex]; - if (!current_descriptor_cache.empty()) - { - assert_dbg(); - //for (const auto& descriptor : current_descriptor_cache) - //{ - // vkFreeDescriptorSets(m_logicalDevice, m_descriptorPool, 1, &descriptor); - // performanceMonitor.vk.numDescriptorSets.decrement(); - //} - - current_descriptor_cache.clear(); - } - - // destroy buffers - for (auto& itr : m_destructionQueues.m_buffers[commandBufferIndex]) - vkDestroyBuffer(m_logicalDevice, itr, nullptr); - m_destructionQueues.m_buffers[commandBufferIndex].clear(); - - // destroy device memory objects - for (auto& itr : m_destructionQueues.m_memory[commandBufferIndex]) - vkFreeMemory(m_logicalDevice, itr, nullptr); - m_destructionQueues.m_memory[commandBufferIndex].clear(); - - // destroy image views - for (auto& itr : m_destructionQueues.m_cmd_image_views[commandBufferIndex]) - vkDestroyImageView(m_logicalDevice, itr, nullptr); - m_destructionQueues.m_cmd_image_views[commandBufferIndex].clear(); - - // destroy host textures - for (auto itr : m_destructionQueues.m_host_textures[commandBufferIndex]) - delete itr; - m_destructionQueues.m_host_textures[commandBufferIndex].clear(); - - ProcessDestructionQueue2(); -} - void VulkanRenderer::InitFirstCommandBuffer() { cemu_assert_debug(m_state.currentCommandBuffer == nullptr); @@ -1869,7 +1831,7 @@ void VulkanRenderer::ProcessFinishedCommandBuffers() VkResult fenceStatus = vkGetFenceStatus(m_logicalDevice, m_cmd_buffer_fences[m_commandBufferSyncIndex]); if (fenceStatus == VK_SUCCESS) { - ProcessDestructionQueues(m_commandBufferSyncIndex); + ProcessDestructionQueue(); m_commandBufferSyncIndex = (m_commandBufferSyncIndex + 1) % m_commandBuffers.size(); memoryManager->cleanupBuffers(m_countCommandBufferFinished); m_countCommandBufferFinished++; @@ -3035,48 +2997,7 @@ TextureDecoder* VulkanRenderer::texture_chooseDecodedFormat(Latte::E_GX2SURFFMT return texFormatInfo.decoder; } -void VulkanRenderer::texture_reserveTextureOnGPU(LatteTexture* hostTexture) -{ - LatteTextureVk* vkTexture = (LatteTextureVk*)hostTexture; - auto allocationInfo = memoryManager->imageMemoryAllocate(vkTexture->GetImageObj()->m_image); - vkTexture->setAllocation(allocationInfo); -} - -void VulkanRenderer::texture_destroy(LatteTexture* hostTexture) -{ - LatteTextureVk* texVk = (LatteTextureVk*)hostTexture; - delete texVk; -} - -void VulkanRenderer::destroyViewDepr(VkImageView imageView) -{ - cemu_assert_debug(false); - - m_destructionQueues.m_cmd_image_views[m_commandBufferIndex].emplace_back(imageView); -} - -void VulkanRenderer::destroyBuffer(VkBuffer buffer) -{ - m_destructionQueues.m_buffers[m_commandBufferIndex].emplace_back(buffer); -} - -void VulkanRenderer::destroyDeviceMemory(VkDeviceMemory mem) -{ - m_destructionQueues.m_memory[m_commandBufferIndex].emplace_back(mem); -} - -void VulkanRenderer::destroyPipelineInfo(PipelineInfo* pipelineInfo) -{ - cemu_assert_debug(false); -} - -void VulkanRenderer::destroyShader(RendererShaderVk* shader) -{ - while (!shader->list_pipelineInfo.empty()) - delete shader->list_pipelineInfo[0]; -} - -void VulkanRenderer::releaseDestructibleObject(VKRDestructibleObject* destructibleObject) +void VulkanRenderer::ReleaseDestructibleObject(VKRDestructibleObject* destructibleObject) { // destroy immediately if possible if (destructibleObject->canDestroy()) @@ -3090,7 +3011,7 @@ void VulkanRenderer::releaseDestructibleObject(VKRDestructibleObject* destructib m_spinlockDestructionQueue.unlock(); } -void VulkanRenderer::ProcessDestructionQueue2() +void VulkanRenderer::ProcessDestructionQueue() { m_spinlockDestructionQueue.lock(); for (auto it = m_destructionQueue.begin(); it != m_destructionQueue.end();) @@ -3139,7 +3060,7 @@ VkDescriptorSetInfo::~VkDescriptorSetInfo() performanceMonitor.vk.numDescriptorDynUniformBuffers.decrement(statsNumDynUniformBuffers); performanceMonitor.vk.numDescriptorStorageBuffers.decrement(statsNumStorageBuffers); - VulkanRenderer::GetInstance()->releaseDestructibleObject(m_vkObjDescriptorSet); + VulkanRenderer::GetInstance()->ReleaseDestructibleObject(m_vkObjDescriptorSet); m_vkObjDescriptorSet = nullptr; } diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index 226edad6..e0a4c75b 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -231,7 +231,6 @@ public: void DrawEmptyFrame(bool mainWindow) override; void PreparePresentationFrame(bool mainWindow); - void ProcessDestructionQueues(size_t commandBufferIndex); void InitFirstCommandBuffer(); void ProcessFinishedCommandBuffers(); void WaitForNextFinishedCommandBuffer(); @@ -244,15 +243,9 @@ public: bool HasCommandBufferFinished(uint64 commandBufferId) const; void WaitCommandBufferFinished(uint64 commandBufferId); - // clean up (deprecated) - void destroyViewDepr(VkImageView imageView); - void destroyBuffer(VkBuffer buffer); - void destroyDeviceMemory(VkDeviceMemory mem); - void destroyPipelineInfo(PipelineInfo* pipelineInfo); - void destroyShader(RendererShaderVk* shader); - // clean up (new) - void releaseDestructibleObject(VKRDestructibleObject* destructibleObject); - void ProcessDestructionQueue2(); + // resource destruction queue + void ReleaseDestructibleObject(VKRDestructibleObject* destructibleObject); + void ProcessDestructionQueue(); FSpinlock m_spinlockDestructionQueue; std::vector m_destructionQueue; @@ -290,9 +283,6 @@ public: TextureDecoder* texture_chooseDecodedFormat(Latte::E_GX2SURFFMT format, bool isDepth, Latte::E_DIM dim, uint32 width, uint32 height) override; - void texture_reserveTextureOnGPU(LatteTexture* hostTexture) override; - void texture_destroy(LatteTexture* hostTexture) override; - void texture_clearSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex) override; void texture_clearColorSlice(LatteTexture* hostTexture, sint32 sliceIndex, sint32 mipIndex, float r, float g, float b, float a) override; void texture_clearDepthSlice(LatteTexture* hostTexture, uint32 sliceIndex, sint32 mipIndex, bool clearDepth, bool clearStencil, float depthValue, uint32 stencilValue) override; @@ -634,15 +624,6 @@ private: // command buffer, garbage collection, synchronization static constexpr uint32 kCommandBufferPoolSize = 128; - struct - { - std::array, kCommandBufferPoolSize> m_cmd_descriptor_set_objects; - std::array, kCommandBufferPoolSize> m_cmd_image_views; - std::array, kCommandBufferPoolSize> m_host_textures; - std::array, kCommandBufferPoolSize> m_buffers; - std::array, kCommandBufferPoolSize> m_memory; - }m_destructionQueues; - size_t m_commandBufferIndex = 0; // current buffer being filled size_t m_commandBufferSyncIndex = 0; // latest buffer that finished execution (updated on submit) std::array m_cmd_buffer_fences; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp index 479b7e60..bf33ed90 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp @@ -813,9 +813,9 @@ void VulkanRenderer::surfaceCopy_notifyTextureRelease(LatteTextureVk* hostTextur { if (p) { - VulkanRenderer::GetInstance()->releaseDestructibleObject(p->vkObjDescriptorSet); + VulkanRenderer::GetInstance()->ReleaseDestructibleObject(p->vkObjDescriptorSet); p->vkObjDescriptorSet = nullptr; - VulkanRenderer::GetInstance()->releaseDestructibleObject(p->vkObjImageView); + VulkanRenderer::GetInstance()->ReleaseDestructibleObject(p->vkObjImageView); p->vkObjImageView = nullptr; } } @@ -829,9 +829,9 @@ void VulkanRenderer::surfaceCopy_notifyTextureRelease(LatteTextureVk* hostTextur { if (p) { - VulkanRenderer::GetInstance()->releaseDestructibleObject(p->vkObjFramebuffer); + VulkanRenderer::GetInstance()->ReleaseDestructibleObject(p->vkObjFramebuffer); p->vkObjFramebuffer = nullptr; - VulkanRenderer::GetInstance()->releaseDestructibleObject(p->vkObjImageView); + VulkanRenderer::GetInstance()->ReleaseDestructibleObject(p->vkObjImageView); p->vkObjImageView = nullptr; } } diff --git a/src/imgui/imgui_impl_vulkan.cpp b/src/imgui/imgui_impl_vulkan.cpp index f0006b45..723f153c 100644 --- a/src/imgui/imgui_impl_vulkan.cpp +++ b/src/imgui/imgui_impl_vulkan.cpp @@ -245,18 +245,13 @@ static void check_vk_result(VkResult err) static void CreateOrResizeBuffer(VkBuffer& buffer, VkDeviceMemory& buffer_memory, VkDeviceSize& p_buffer_size, size_t new_size, VkBufferUsageFlagBits usage) { - VulkanRenderer* vkRenderer = VulkanRenderer::GetInstance(); - - ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; + ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; + vkDeviceWaitIdle(v->Device); // make sure previously created buffer is not in use anymore VkResult err; if (buffer != VK_NULL_HANDLE) - { - vkRenderer->destroyBuffer(buffer); - } + vkDestroyBuffer(v->Device, buffer, v->Allocator); if (buffer_memory != VK_NULL_HANDLE) - { - vkRenderer->destroyDeviceMemory(buffer_memory); - } + vkFreeMemory(v->Device, buffer_memory, v->Allocator); VkDeviceSize vertex_buffer_size_aligned = ((new_size - 1) / g_BufferMemoryAlignment + 1) * g_BufferMemoryAlignment; VkBufferCreateInfo buffer_info = {}; buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; From 731713de3ac97e6cee2504fd6ce3e7ca943fc282 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Thu, 14 Mar 2024 03:10:10 +0100 Subject: [PATCH 045/299] OpenGL: Remove "-legacy" flag "Intel legacy mode" was a special mode to workaround various Intel OpenGL driver limitations during the earlier years of Cemu. It's been unmaintained for years and no longer serves a purpose. If we ever bring back compatibility with ancient Intel GPUs it should be done in a more structured way than a blunt yes/no flag. --- src/Cafe/GraphicPack/GraphicPack2.cpp | 3 - src/Cafe/HW/Latte/Core/LatteConst.h | 2 - src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp | 21 +++---- src/Cafe/HW/Latte/Core/LatteThread.cpp | 8 +-- .../Latte/Renderer/OpenGL/LatteTextureGL.cpp | 62 +++++-------------- .../Latte/Renderer/OpenGL/OpenGLRenderer.cpp | 44 +------------ .../Renderer/OpenGL/OpenGLRendererCore.cpp | 2 +- src/Cafe/HW/Latte/Renderer/Renderer.h | 2 - src/config/LaunchSettings.cpp | 2 - src/config/LaunchSettings.h | 2 - src/gui/guiWrapper.cpp | 4 -- 11 files changed, 26 insertions(+), 126 deletions(-) diff --git a/src/Cafe/GraphicPack/GraphicPack2.cpp b/src/Cafe/GraphicPack/GraphicPack2.cpp index 365e6e3e..b581316e 100644 --- a/src/Cafe/GraphicPack/GraphicPack2.cpp +++ b/src/Cafe/GraphicPack/GraphicPack2.cpp @@ -878,9 +878,6 @@ bool GraphicPack2::Activate() if (m_gfx_vendor.has_value()) { auto vendor = g_renderer->GetVendor(); - if (vendor == GfxVendor::IntelLegacy || vendor == GfxVendor::IntelNoLegacy) - vendor = GfxVendor::Intel; - if (m_gfx_vendor.value() != vendor) return false; } diff --git a/src/Cafe/HW/Latte/Core/LatteConst.h b/src/Cafe/HW/Latte/Core/LatteConst.h index 04c7b888..ebe741e9 100644 --- a/src/Cafe/HW/Latte/Core/LatteConst.h +++ b/src/Cafe/HW/Latte/Core/LatteConst.h @@ -82,8 +82,6 @@ #define GLVENDOR_UNKNOWN (0) #define GLVENDOR_AMD (1) // AMD/ATI #define GLVENDOR_NVIDIA (2) -#define GLVENDOR_INTEL_LEGACY (3) -#define GLVENDOR_INTEL_NOLEGACY (4) #define GLVENDOR_INTEL (5) #define GLVENDOR_APPLE (6) diff --git a/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp b/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp index b9ccbac4..50aa4d87 100644 --- a/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTextureLegacy.cpp @@ -229,21 +229,16 @@ void LatteTexture_updateTexturesForStage(LatteDecompilerShader* shaderContext, u // if this texture is bound multiple times then use alternative views if (textureView->lastTextureBindIndex == LatteGPUState.textureBindCounter) { - // Intel driver has issues with textures that have multiple views bound and used by a shader, causes a softlock in BotW - // therefore we disable this on Intel - if (LatteGPUState.glVendor != GLVENDOR_INTEL_NOLEGACY) + LatteTextureViewGL* textureViewGL = (LatteTextureViewGL*)textureView; + // get next unused alternative texture view + while (true) { - LatteTextureViewGL* textureViewGL = (LatteTextureViewGL*)textureView; - // get next unused alternative texture view - while (true) - { - textureViewGL = textureViewGL->GetAlternativeView(); - if (textureViewGL->lastTextureBindIndex != LatteGPUState.textureBindCounter) - break; - } - textureView = textureViewGL; + textureViewGL = textureViewGL->GetAlternativeView(); + if (textureViewGL->lastTextureBindIndex != LatteGPUState.textureBindCounter) + break; } - } + textureView = textureViewGL; + } textureView->lastTextureBindIndex = LatteGPUState.textureBindCounter; rendererGL->renderstate_updateTextureSettingsGL(shaderContext, textureView, textureIndex + glBackendBaseTexUnit, word4, textureIndex, isDepthSampler); } diff --git a/src/Cafe/HW/Latte/Core/LatteThread.cpp b/src/Cafe/HW/Latte/Core/LatteThread.cpp index 60b32ec4..bd312d93 100644 --- a/src/Cafe/HW/Latte/Core/LatteThread.cpp +++ b/src/Cafe/HW/Latte/Core/LatteThread.cpp @@ -140,13 +140,7 @@ int Latte_ThreadEntry() case GfxVendor::AMD: LatteGPUState.glVendor = GLVENDOR_AMD; break; - case GfxVendor::IntelLegacy: - LatteGPUState.glVendor = GLVENDOR_INTEL_LEGACY; - break; - case GfxVendor::IntelNoLegacy: - LatteGPUState.glVendor = GLVENDOR_INTEL_NOLEGACY; - break; - case GfxVendor::Intel: + case GfxVendor::Intel: LatteGPUState.glVendor = GLVENDOR_INTEL; break; case GfxVendor::Nvidia: diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp index 58805925..fb025eba 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/LatteTextureGL.cpp @@ -110,10 +110,6 @@ void LatteTextureGL::GetOpenGLFormatInfo(bool isDepth, Latte::E_GX2SURFFMT forma sint32 glInternalFormat; sint32 glSuppliedFormat; sint32 glSuppliedFormatType; - // check if compressed textures should be used - bool allowCompressedGLFormat = true; - if (LatteGPUState.glVendor == GLVENDOR_INTEL_LEGACY) - allowCompressedGLFormat = false; // compressed formats seem to cause more harm than good on Intel // get format information if (format == Latte::E_GX2SURFFMT::R4_G4_UNORM) { @@ -149,20 +145,11 @@ void LatteTextureGL::GetOpenGLFormatInfo(bool isDepth, Latte::E_GX2SURFFMT forma else if (format == Latte::E_GX2SURFFMT::BC1_UNORM || format == Latte::E_GX2SURFFMT::BC1_SRGB) { - if (allowCompressedGLFormat) - { - if (format == Latte::E_GX2SURFFMT::BC1_SRGB) - formatInfoOut->setCompressed(GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, -1, -1); - else - formatInfoOut->setCompressed(GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, -1, -1); - return; - } + if (format == Latte::E_GX2SURFFMT::BC1_SRGB) + formatInfoOut->setCompressed(GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, -1, -1); else - { - formatInfoOut->setFormat(GL_RGBA16F, GL_RGBA, GL_FLOAT); - formatInfoOut->markAsAlternativeFormat(); - return; - } + formatInfoOut->setCompressed(GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, -1, -1); + return; } else if (format == Latte::E_GX2SURFFMT::BC2_UNORM || format == Latte::E_GX2SURFFMT::BC2_SRGB) { @@ -173,28 +160,18 @@ void LatteTextureGL::GetOpenGLFormatInfo(bool isDepth, Latte::E_GX2SURFFMT forma } else if (format == Latte::E_GX2SURFFMT::BC3_UNORM || format == Latte::E_GX2SURFFMT::BC3_SRGB) { - if (allowCompressedGLFormat) - { - if (format == Latte::E_GX2SURFFMT::BC3_SRGB) - formatInfoOut->setCompressed(GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, -1, -1); - else - formatInfoOut->setCompressed(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, -1, -1); - return; - } + if (format == Latte::E_GX2SURFFMT::BC3_SRGB) + formatInfoOut->setCompressed(GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, -1, -1); else - { - // todo: SRGB support - formatInfoOut->setFormat(GL_RGBA16F, GL_RGBA, GL_FLOAT); - formatInfoOut->markAsAlternativeFormat(); - return; - } + formatInfoOut->setCompressed(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, -1, -1); + return; } else if (format == Latte::E_GX2SURFFMT::BC4_UNORM || format == Latte::E_GX2SURFFMT::BC4_SNORM) { + bool allowCompressed = true; if (dim != Latte::E_DIM::DIM_2D && dim != Latte::E_DIM::DIM_2D_ARRAY) - allowCompressedGLFormat = false; // RGTC1 does not support non-2D textures - - if (allowCompressedGLFormat) + allowCompressed = false; // RGTC1 does not support non-2D textures + if (allowCompressed) { if (format == Latte::E_GX2SURFFMT::BC4_UNORM) formatInfoOut->setCompressed(GL_COMPRESSED_RED_RGTC1, -1, -1); @@ -211,20 +188,11 @@ void LatteTextureGL::GetOpenGLFormatInfo(bool isDepth, Latte::E_GX2SURFFMT forma } else if (format == Latte::E_GX2SURFFMT::BC5_UNORM || format == Latte::E_GX2SURFFMT::BC5_SNORM) { - if (allowCompressedGLFormat) - { - if (format == Latte::E_GX2SURFFMT::BC5_SNORM) - formatInfoOut->setCompressed(GL_COMPRESSED_SIGNED_RG_RGTC2, -1, -1); - else - formatInfoOut->setCompressed(GL_COMPRESSED_RG_RGTC2, -1, -1); - return; - } + if (format == Latte::E_GX2SURFFMT::BC5_SNORM) + formatInfoOut->setCompressed(GL_COMPRESSED_SIGNED_RG_RGTC2, -1, -1); else - { - formatInfoOut->setFormat(GL_RG16F, GL_RG, GL_FLOAT); - formatInfoOut->markAsAlternativeFormat(); - return; - } + formatInfoOut->setCompressed(GL_COMPRESSED_RG_RGTC2, -1, -1); + return; } else if (format == Latte::E_GX2SURFFMT::R32_FLOAT) { diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp index 604744cf..28e91b8a 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp @@ -407,10 +407,7 @@ void OpenGLRenderer::GetVendorInformation() } else if (memcmp(glVendorString, "Intel", 5) == 0) { - if (LaunchSettings::ForceIntelLegacyEnabled()) - m_vendor = GfxVendor::IntelLegacy; - else - m_vendor = GfxVendor::IntelNoLegacy; + m_vendor = GfxVendor::Intel; return; } } @@ -849,45 +846,6 @@ TextureDecoder* OpenGLRenderer::texture_chooseDecodedFormat(Latte::E_GX2SURFFMT } return nullptr; } - - if (LatteGPUState.glVendor == GLVENDOR_INTEL_LEGACY) - { - if (format == Latte::E_GX2SURFFMT::BC1_UNORM) - { - texDecoder = TextureDecoder_BC1_UNORM_uncompress::getInstance(); - } - else if (format == Latte::E_GX2SURFFMT::BC1_SRGB) - { - texDecoder = TextureDecoder_BC1_SRGB_uncompress::getInstance(); - } - else if (format == Latte::E_GX2SURFFMT::BC3_UNORM) - { - texDecoder = TextureDecoder_BC3_UNORM_uncompress::getInstance(); - } - else if (format == Latte::E_GX2SURFFMT::BC3_SRGB) - { - texDecoder = TextureDecoder_BC3_SRGB_uncompress::getInstance(); - } - else if (format == Latte::E_GX2SURFFMT::BC4_UNORM) - { - texDecoder = TextureDecoder_BC4_UNORM_uncompress::getInstance(); - } - else if (format == Latte::E_GX2SURFFMT::BC4_SNORM) - { - cemu_assert_debug(false); // todo - } - else if (format == Latte::E_GX2SURFFMT::BC5_UNORM) - { - texDecoder = TextureDecoder_BC5_UNORM_uncompress::getInstance(); - } - else if (format == Latte::E_GX2SURFFMT::BC5_SNORM) - { - texDecoder = TextureDecoder_BC5_SNORM_uncompress::getInstance(); - } - if (texDecoder) - return texDecoder; - } - if (format == Latte::E_GX2SURFFMT::R4_G4_UNORM) texDecoder = TextureDecoder_R4_G4_UNORM_To_RGBA4::getInstance(); else if (format == Latte::E_GX2SURFFMT::R4_G4_B4_A4_UNORM) diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp index 51d0d206..571961f4 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRendererCore.cpp @@ -950,7 +950,7 @@ void OpenGLRenderer::draw_genericDrawHandler(uint32 baseVertex, uint32 baseInsta bool streamoutEnable = LatteGPUState.contextRegister[mmVGT_STRMOUT_EN] != 0; if (streamoutEnable) { - if (glBeginTransformFeedback == nullptr || LatteGPUState.glVendor == GLVENDOR_INTEL_NOLEGACY) + if (glBeginTransformFeedback == nullptr) { cemu_assert_debug(false); return; // transform feedback not supported diff --git a/src/Cafe/HW/Latte/Renderer/Renderer.h b/src/Cafe/HW/Latte/Renderer/Renderer.h index 2a9a1d1b..0b694bb9 100644 --- a/src/Cafe/HW/Latte/Renderer/Renderer.h +++ b/src/Cafe/HW/Latte/Renderer/Renderer.h @@ -21,8 +21,6 @@ enum class GfxVendor Generic, AMD, - IntelLegacy, - IntelNoLegacy, Intel, Nvidia, Apple, diff --git a/src/config/LaunchSettings.cpp b/src/config/LaunchSettings.cpp index fdd4cc65..b7a79a11 100644 --- a/src/config/LaunchSettings.cpp +++ b/src/config/LaunchSettings.cpp @@ -174,8 +174,6 @@ bool LaunchSettings::HandleCommandline(const std::vector& args) if (vm.count("nsight")) s_nsight_mode = vm["nsight"].as(); - if (vm.count("legacy")) - s_force_intel_legacy = vm["legacy"].as(); if(vm.count("force-interpreter")) s_force_interpreter = vm["force-interpreter"].as(); diff --git a/src/config/LaunchSettings.h b/src/config/LaunchSettings.h index f87dc609..be989e6a 100644 --- a/src/config/LaunchSettings.h +++ b/src/config/LaunchSettings.h @@ -24,7 +24,6 @@ public: static bool GDBStubEnabled() { return s_enable_gdbstub; } static bool NSightModeEnabled() { return s_nsight_mode; } - static bool ForceIntelLegacyEnabled() { return s_force_intel_legacy; } static bool ForceInterpreter() { return s_force_interpreter; }; @@ -44,7 +43,6 @@ private: inline static bool s_enable_gdbstub = false; inline static bool s_nsight_mode = false; - inline static bool s_force_intel_legacy = false; inline static bool s_force_interpreter = false; diff --git a/src/gui/guiWrapper.cpp b/src/gui/guiWrapper.cpp index 68f97590..ce043bab 100644 --- a/src/gui/guiWrapper.cpp +++ b/src/gui/guiWrapper.cpp @@ -93,10 +93,6 @@ void gui_updateWindowTitles(bool isIdle, bool isLoading, double fps) 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) From eaa82817dd235b5067002df76b06e66a550ac1d3 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Fri, 15 Mar 2024 23:06:48 +0100 Subject: [PATCH 046/299] Update thread names (#1120) --- src/Cafe/HW/Espresso/Debugger/GDBStub.cpp | 2 +- src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp | 2 +- src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp | 2 ++ .../HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp | 2 ++ src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp | 1 + src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp | 1 + src/Cafe/IOSU/ODM/iosu_odm.cpp | 2 ++ src/Cafe/IOSU/PDM/iosu_pdm.cpp | 2 ++ src/Cafe/IOSU/nn/iosu_nn_service.cpp | 1 + src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp | 2 +- src/Cafe/TitleList/SaveList.cpp | 2 ++ src/Cafe/TitleList/TitleList.cpp | 1 + src/Cemu/FileCache/FileCache.cpp | 2 +- src/gui/components/wxGameList.cpp | 1 + src/gui/guiWrapper.cpp | 2 +- src/input/InputManager.cpp | 2 +- src/input/api/DSU/DSUControllerProvider.cpp | 4 ++-- src/input/api/SDL/SDLControllerProvider.cpp | 2 +- src/input/api/Wiimote/WiimoteControllerProvider.cpp | 4 ++-- src/util/helpers/helpers.cpp | 4 +++- 20 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/Cafe/HW/Espresso/Debugger/GDBStub.cpp b/src/Cafe/HW/Espresso/Debugger/GDBStub.cpp index 6cddae01..e54fae1b 100644 --- a/src/Cafe/HW/Espresso/Debugger/GDBStub.cpp +++ b/src/Cafe/HW/Espresso/Debugger/GDBStub.cpp @@ -297,7 +297,7 @@ bool GDBServer::Initialize() void GDBServer::ThreadFunc() { - SetThreadName("GDBServer::ThreadFunc"); + SetThreadName("GDBServer"); while (!m_stopRequested) { diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp index f4d063fa..24e87bd1 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp @@ -294,7 +294,7 @@ std::atomic_bool s_recompilerThreadStopSignal{false}; void PPCRecompiler_thread() { - SetThreadName("PPCRecompiler_thread"); + SetThreadName("PPCRecompiler"); while (true) { if(s_recompilerThreadStopSignal) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp index 15ea6e89..50f2c2d6 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp @@ -8,6 +8,7 @@ #include #include +#include bool s_isLoadingShadersVk{ false }; class FileCache* s_spirvCache{nullptr}; @@ -155,6 +156,7 @@ public: void CompilerThreadFunc() { + SetThreadName("vkShaderComp"); while (m_threadsActive.load(std::memory_order::relaxed)) { s_compilationQueueCount.decrementWithWait(); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp index 2be9a2f4..123120d3 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp @@ -408,6 +408,7 @@ bool VulkanPipelineStableCache::DeserializePipeline(MemStreamReader& memReader, int VulkanPipelineStableCache::CompilerThread() { + SetThreadName("plCacheCompiler"); while (m_numCompilationThreads != 0) { std::vector pipelineData = m_compilationQueue.pop(); @@ -421,6 +422,7 @@ int VulkanPipelineStableCache::CompilerThread() void VulkanPipelineStableCache::WorkerThread() { + SetThreadName("plCacheWriter"); while (true) { CachedPipeline* job; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index d0305317..d62b61a6 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -1986,6 +1986,7 @@ void VulkanRenderer::WaitCommandBufferFinished(uint64 commandBufferId) void VulkanRenderer::PipelineCacheSaveThread(size_t cache_size) { + SetThreadName("vkDriverPlCache"); const auto dir = ActiveSettings::GetCachePath("shaderCache/driver/vk"); if (!fs::exists(dir)) { diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp index 320357f1..d510140b 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp @@ -190,6 +190,7 @@ std::queue g_compilePipelineRequests; void compilePipeline_thread(sint32 threadIndex) { + SetThreadName("compilePl"); #ifdef _WIN32 // one thread runs at normal priority while the others run at lower priority if(threadIndex != 0) diff --git a/src/Cafe/IOSU/ODM/iosu_odm.cpp b/src/Cafe/IOSU/ODM/iosu_odm.cpp index 3dc8e431..aae3c503 100644 --- a/src/Cafe/IOSU/ODM/iosu_odm.cpp +++ b/src/Cafe/IOSU/ODM/iosu_odm.cpp @@ -1,3 +1,4 @@ +#include #include "iosu_odm.h" #include "config/ActiveSettings.h" #include "Common/FileStream.h" @@ -79,6 +80,7 @@ namespace iosu void ODMServiceThread() { + SetThreadName("ODMService"); s_msgQueueId = IOS_CreateMessageQueue(_s_msgBuffer.GetPtr(), _s_msgBuffer.GetCount()); cemu_assert(!IOS_ResultIsError((IOS_ERROR)s_msgQueueId)); IOS_ERROR r = IOS_RegisterResourceManager(s_devicePath.c_str(), s_msgQueueId); diff --git a/src/Cafe/IOSU/PDM/iosu_pdm.cpp b/src/Cafe/IOSU/PDM/iosu_pdm.cpp index e54529a9..d94b1dbf 100644 --- a/src/Cafe/IOSU/PDM/iosu_pdm.cpp +++ b/src/Cafe/IOSU/PDM/iosu_pdm.cpp @@ -1,3 +1,4 @@ +#include #include "iosu_pdm.h" #include "Cafe/CafeSystem.h" #include "config/ActiveSettings.h" @@ -387,6 +388,7 @@ namespace iosu void TimeTrackingThread(uint64 titleId) { + SetThreadName("PlayDiaryThread"); PlayStatsEntry* playStatsEntry = PlayStats_BeginNewTracking(titleId); auto startTime = std::chrono::steady_clock::now(); diff --git a/src/Cafe/IOSU/nn/iosu_nn_service.cpp b/src/Cafe/IOSU/nn/iosu_nn_service.cpp index b3b2d4c9..1fb5c77a 100644 --- a/src/Cafe/IOSU/nn/iosu_nn_service.cpp +++ b/src/Cafe/IOSU/nn/iosu_nn_service.cpp @@ -155,6 +155,7 @@ namespace iosu void IPCService::ServiceThread() { + SetThreadName("IPCService"); m_msgQueueId = IOS_CreateMessageQueue(_m_msgBuffer.GetPtr(), _m_msgBuffer.GetCount()); cemu_assert(!IOS_ResultIsError((IOS_ERROR)m_msgQueueId)); IOS_ERROR r = IOS_RegisterResourceManager(m_devicePath.c_str(), m_msgQueueId); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp index 3701a4d7..8ce5de07 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp @@ -1168,7 +1168,7 @@ namespace coreinit void OSSchedulerCoreEmulationThread(void* _assignedCoreIndex) { - SetThreadName(fmt::format("OSSchedulerThread[core={}]", (uintptr_t)_assignedCoreIndex).c_str()); + SetThreadName(fmt::format("OSSched[core={}]", (uintptr_t)_assignedCoreIndex).c_str()); t_assignedCoreIndex = (sint32)(uintptr_t)_assignedCoreIndex; #if defined(ARCH_X86_64) _mm_setcsr(_mm_getcsr() | 0x8000); // flush denormals to zero diff --git a/src/Cafe/TitleList/SaveList.cpp b/src/Cafe/TitleList/SaveList.cpp index a86e8498..d0c0e3e2 100644 --- a/src/Cafe/TitleList/SaveList.cpp +++ b/src/Cafe/TitleList/SaveList.cpp @@ -1,5 +1,6 @@ #include "SaveList.h" #include +#include std::mutex sSLMutex; fs::path sSLMLCPath; @@ -44,6 +45,7 @@ void CafeSaveList::Refresh() void CafeSaveList::RefreshThreadWorker() { + SetThreadName("SaveListWorker"); // clear save list for (auto& itSaveInfo : sSLList) { diff --git a/src/Cafe/TitleList/TitleList.cpp b/src/Cafe/TitleList/TitleList.cpp index 1cc084b8..c288dd13 100644 --- a/src/Cafe/TitleList/TitleList.cpp +++ b/src/Cafe/TitleList/TitleList.cpp @@ -258,6 +258,7 @@ void CafeTitleList::AddTitleFromPath(fs::path path) bool CafeTitleList::RefreshWorkerThread() { + SetThreadName("TitleListWorker"); while (sTLRefreshRequests.load()) { sTLRefreshRequests.store(0); diff --git a/src/Cemu/FileCache/FileCache.cpp b/src/Cemu/FileCache/FileCache.cpp index aa7770a3..b284b66b 100644 --- a/src/Cemu/FileCache/FileCache.cpp +++ b/src/Cemu/FileCache/FileCache.cpp @@ -50,7 +50,7 @@ struct _FileCacheAsyncWriter private: void FileCacheThread() { - SetThreadName("fileCache_thread"); + SetThreadName("fileCache"); while (true) { std::unique_lock lock(m_fileCacheMutex); diff --git a/src/gui/components/wxGameList.cpp b/src/gui/components/wxGameList.cpp index 88934cd8..8e8f3c4d 100644 --- a/src/gui/components/wxGameList.cpp +++ b/src/gui/components/wxGameList.cpp @@ -1194,6 +1194,7 @@ void wxGameList::RemoveCache(const std::list& cachePaths, const std::s void wxGameList::AsyncWorkerThread() { + SetThreadName("GameListWorker"); while (m_async_worker_active) { m_async_task_count.decrementWithWait(); diff --git a/src/gui/guiWrapper.cpp b/src/gui/guiWrapper.cpp index ce043bab..d887e89a 100644 --- a/src/gui/guiWrapper.cpp +++ b/src/gui/guiWrapper.cpp @@ -37,7 +37,7 @@ void _wxLaunch() void gui_create() { - SetThreadName("MainThread"); + SetThreadName("cemu"); #if BOOST_OS_WINDOWS // 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 diff --git a/src/input/InputManager.cpp b/src/input/InputManager.cpp index 4e7848ce..d928e46c 100644 --- a/src/input/InputManager.cpp +++ b/src/input/InputManager.cpp @@ -934,7 +934,7 @@ std::optional InputManager::get_right_down_mouse_info(bool* is_pad) void InputManager::update_thread() { - SetThreadName("InputManager::update_thread"); + SetThreadName("Input_update"); while (!m_update_thread_shutdown.load(std::memory_order::relaxed)) { std::shared_lock lock(m_mutex); diff --git a/src/input/api/DSU/DSUControllerProvider.cpp b/src/input/api/DSU/DSUControllerProvider.cpp index 0fa93e25..37f92774 100644 --- a/src/input/api/DSU/DSUControllerProvider.cpp +++ b/src/input/api/DSU/DSUControllerProvider.cpp @@ -250,7 +250,7 @@ MotionSample DSUControllerProvider::get_motion_sample(uint8_t index) const void DSUControllerProvider::reader_thread() { - SetThreadName("DSUControllerProvider::reader_thread"); + SetThreadName("DSU-reader"); bool first_read = true; while (m_running.load(std::memory_order_relaxed)) { @@ -383,7 +383,7 @@ void DSUControllerProvider::reader_thread() void DSUControllerProvider::writer_thread() { - SetThreadName("DSUControllerProvider::writer_thread"); + SetThreadName("DSU-writer"); while (m_running.load(std::memory_order_relaxed)) { std::unique_lock lock(m_writer_mutex); diff --git a/src/input/api/SDL/SDLControllerProvider.cpp b/src/input/api/SDL/SDLControllerProvider.cpp index 9e0c09b5..9b21b306 100644 --- a/src/input/api/SDL/SDLControllerProvider.cpp +++ b/src/input/api/SDL/SDLControllerProvider.cpp @@ -124,7 +124,7 @@ MotionSample SDLControllerProvider::motion_sample(int diid) void SDLControllerProvider::event_thread() { - SetThreadName("SDLControllerProvider::event_thread"); + SetThreadName("SDL_events"); while (m_running.load(std::memory_order_relaxed)) { SDL_Event event{}; diff --git a/src/input/api/Wiimote/WiimoteControllerProvider.cpp b/src/input/api/Wiimote/WiimoteControllerProvider.cpp index 55f28c01..5aac3fe4 100644 --- a/src/input/api/Wiimote/WiimoteControllerProvider.cpp +++ b/src/input/api/Wiimote/WiimoteControllerProvider.cpp @@ -143,7 +143,7 @@ WiimoteControllerProvider::WiimoteState WiimoteControllerProvider::get_state(siz void WiimoteControllerProvider::reader_thread() { - SetThreadName("WiimoteControllerProvider::reader_thread"); + SetThreadName("Wiimote-reader"); std::chrono::steady_clock::time_point lastCheck = {}; while (m_running.load(std::memory_order_relaxed)) { @@ -878,7 +878,7 @@ void WiimoteControllerProvider::set_motion_plus(size_t index, bool state) void WiimoteControllerProvider::writer_thread() { - SetThreadName("WiimoteControllerProvider::writer_thread"); + SetThreadName("Wiimote-writer"); while (m_running.load(std::memory_order_relaxed)) { std::unique_lock writer_lock(m_writer_mutex); diff --git a/src/util/helpers/helpers.cpp b/src/util/helpers/helpers.cpp index b556db36..7e22e9fb 100644 --- a/src/util/helpers/helpers.cpp +++ b/src/util/helpers/helpers.cpp @@ -155,7 +155,9 @@ void SetThreadName(const char* name) #elif BOOST_OS_MACOS pthread_setname_np(name); #else - pthread_setname_np(pthread_self(), name); + if(std::strlen(name) > 15) + cemuLog_log(LogType::Force, "Truncating thread name {} because it was longer than 15 characters", name); + pthread_setname_np(pthread_self(), std::string{name}.substr(0,15).c_str()); #endif } From 42d14eec96c6e63ea93cfb7daa49655b7139c997 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Mon, 18 Mar 2024 09:18:02 +0100 Subject: [PATCH 047/299] Minor code improvements (#1124) --- .../LatteDecompilerEmitGLSL.cpp | 2 +- .../HW/Latte/Renderer/OpenGL/OpenGLRenderer.h | 2 -- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 10 +++++----- .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 3 +-- src/input/motion/MotionSample.h | 20 +++++++++---------- 5 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp index f3d2c7a8..e19535be 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp @@ -973,7 +973,7 @@ void _emitOperandInputCode(LatteDecompilerShaderContext* shaderContext, LatteDec } else { - cemuLog_log(LogType::Force, "Unsupported shader ALU operand sel 0x%x\n", aluInstruction->sourceOperand[operandIndex].sel); + cemuLog_log(LogType::Force, "Unsupported shader ALU operand sel {:#x}\n", aluInstruction->sourceOperand[operandIndex].sel); debugBreakpoint(); } diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h index 3a892191..313ea3c0 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h @@ -30,8 +30,6 @@ public: void Flush(bool waitIdle = false) override; void NotifyLatteCommandProcessorIdle() override; - void UpdateVSyncState(); - void EnableDebugMode() override; void SwapBuffers(bool swapTV = true, bool swapDRC = true) override; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index d62b61a6..02bb1e71 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -706,8 +706,8 @@ SwapchainInfoVk& VulkanRenderer::GetChainInfo(bool mainWindow) const void VulkanRenderer::StopUsingPadAndWait() { - m_destroyPadSwapchainNextAcquire = true; - m_padCloseReadySemaphore.wait(); + m_destroyPadSwapchainNextAcquire.test_and_set(); + m_destroyPadSwapchainNextAcquire.wait(true); } bool VulkanRenderer::IsPadWindowActive() @@ -2557,11 +2557,11 @@ bool VulkanRenderer::AcquireNextSwapchainImage(bool mainWindow) if(!IsSwapchainInfoValid(mainWindow)) return false; - if(!mainWindow && m_destroyPadSwapchainNextAcquire) + if(!mainWindow && m_destroyPadSwapchainNextAcquire.test()) { RecreateSwapchain(mainWindow, true); - m_destroyPadSwapchainNextAcquire = false; - m_padCloseReadySemaphore.notify(); + m_destroyPadSwapchainNextAcquire.clear(); + m_destroyPadSwapchainNextAcquire.notify_all(); return false; } diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index e0a4c75b..47097dfa 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -414,8 +414,7 @@ private: }m_state; std::unique_ptr m_mainSwapchainInfo{}, m_padSwapchainInfo{}; - Semaphore m_padCloseReadySemaphore; - bool m_destroyPadSwapchainNextAcquire = false; + std::atomic_flag m_destroyPadSwapchainNextAcquire{}; bool IsSwapchainInfoValid(bool mainWindow) const; VkRenderPass m_imguiRenderPass = VK_NULL_HANDLE; diff --git a/src/input/motion/MotionSample.h b/src/input/motion/MotionSample.h index bb47f784..0697711b 100644 --- a/src/input/motion/MotionSample.h +++ b/src/input/motion/MotionSample.h @@ -258,48 +258,48 @@ DRC flat on table, screen facing up. Top pointing away (away from person, pointi 0.03 0.99 -0.13 0.01 0.13 0.99 -Turned 45 to the right: +Turned 45° to the right: 0.71 -0.03 0.71 0.12 0.99 -0.08 -0.70 0.14 0.70 -Turned 45 to the right (top of GamePad pointing right now): +Turned 45° to the right (top of GamePad pointing right now): 0.08 -0.03 1.00 -> Z points towards person 0.15 0.99 0.01 -0.99 0.15 0.09 -> DRC Z-Axis now points towards X-minus -Turned 90 to the right (top of gamepad now pointing towards holder, away from monitor): +Turned 90° to the right (top of gamepad now pointing towards holder, away from monitor): -1.00 -0.01 0.06 0.00 0.99 0.15 -0.06 0.15 -0.99 -Turned 90 to the right (pointing left): +Turned 90° to the right (pointing left): -0.17 -0.01 -0.99 -0.13 0.99 0.02 0.98 0.13 -0.17 -After another 90 we end up in the initial position: +After another 90° we end up in the initial position: 0.99 -0.03 -0.11 0.01 0.99 -0.13 0.12 0.12 0.99 ------ -From initial position, lean the GamePad on its left side. 45 up. So the screen is pointing to the top left +From initial position, lean the GamePad on its left side. 45° up. So the screen is pointing to the top left 0.66 -0.75 -0.03 0.74 0.66 -0.11 0.10 0.05 0.99 -Further 45, GamePad now on its left, screen pointing left: +Further 45°, GamePad now on its left, screen pointing left: -0.03 -1.00 -0.00 0.99 -0.03 -0.15 0.15 -0.01 0.99 -From initial position, lean the GamePad on its right side. 45 up. So the screen is pointing to the top right +From initial position, lean the GamePad on its right side. 45° up. So the screen is pointing to the top right 0.75 0.65 -0.11 -0.65 0.76 0.07 0.12 0.02 0.99 -From initial position, tilt the GamePad up 90 (bottom side remains in touch with surface): +From initial position, tilt the GamePad up 90° (bottom side remains in touch with surface): 0.99 -0.05 -0.10 -0.10 0.01 -0.99 0.05 1.00 0.01 @@ -309,7 +309,7 @@ From initial position, stand the GamePad on its top side: 0.09 -0.01 1.00 -0.01 -1.00 -0.01 -Rotate GamePad 180 around x axis, so it now lies on its screen (top of GamePad pointing to holder): +Rotate GamePad 180° around x axis, so it now lies on its screen (top of GamePad pointing to holder): 0.99 -0.03 -0.15 -0.04 -1.00 -0.08 -0.15 0.09 -0.99 From 4d609f06b810a6bc686aa783a2b716e3cd280f0e Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Wed, 20 Mar 2024 10:22:48 +0100 Subject: [PATCH 048/299] InputSettings: Fix controller type counter to restore WPAD limit (#1118) --- src/gui/input/InputSettings2.cpp | 32 ++++---------------------------- src/gui/input/InputSettings2.h | 3 --- 2 files changed, 4 insertions(+), 31 deletions(-) diff --git a/src/gui/input/InputSettings2.cpp b/src/gui/input/InputSettings2.cpp index 58c168a3..72bf4f7d 100644 --- a/src/gui/input/InputSettings2.cpp +++ b/src/gui/input/InputSettings2.cpp @@ -295,32 +295,6 @@ wxWindow* InputSettings2::initialize_page(size_t index) return page; } -std::pair 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 InputSettings2::get_active_controller() const { auto& page_data = get_current_page_data(); @@ -771,14 +745,16 @@ void InputSettings2::on_emulated_controller_dropdown(wxCommandEvent& event) wxWindowUpdateLocker lock(emulated_controllers); bool is_gamepad_selected = false; + bool is_wpad_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)); + is_wpad_selected = !is_gamepad_selected && selected != 0; } - const auto [vpad_count, wpad_count] = get_emulated_controller_types(); + const auto [vpad_count, wpad_count] = InputManager::instance().get_controller_count(); emulated_controllers->Clear(); emulated_controllers->AppendString(_("Disabled")); @@ -786,7 +762,7 @@ void InputSettings2::on_emulated_controller_dropdown(wxCommandEvent& event) 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) + if (wpad_count < InputManager::kMaxWPADControllers || is_wpad_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))); diff --git a/src/gui/input/InputSettings2.h b/src/gui/input/InputSettings2.h index 01313653..1a3c8bb1 100644 --- a/src/gui/input/InputSettings2.h +++ b/src/gui/input/InputSettings2.h @@ -27,9 +27,6 @@ private: wxWindow* initialize_page(size_t index); - // count active controllers - std::pair get_emulated_controller_types() const; - // currently selected controller from active tab std::shared_ptr get_active_controller() const; From 17060752b6d5bc952446e26c4cd4b0d259653ced Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Sun, 24 Mar 2024 10:57:08 +0100 Subject: [PATCH 049/299] Vulkan: Several swapchain fixes and refactors (#1132) --- .../Latte/Renderer/Vulkan/SwapchainInfoVk.cpp | 91 +++++++++---------- .../Latte/Renderer/Vulkan/SwapchainInfoVk.h | 25 ++--- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 54 ++++++++--- .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 13 ++- 4 files changed, 99 insertions(+), 84 deletions(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp index 14b7a17c..b00f5490 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp @@ -1,15 +1,34 @@ #include "SwapchainInfoVk.h" #include "config/CemuConfig.h" +#include "gui/guiWrapper.h" #include "Cafe/HW/Latte/Core/Latte.h" #include "Cafe/HW/Latte/Core/LatteTiming.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" +#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" -void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDevice) +SwapchainInfoVk::SwapchainInfoVk(bool mainWindow, Vector2i size) : mainWindow(mainWindow), m_desiredExtent(size) { - m_physicalDevice = physicalDevice; - m_logicalDevice = logicalDevice; - const auto details = QuerySwapchainSupport(surface, physicalDevice); + auto& windowHandleInfo = mainWindow ? gui_getWindowInfo().canvas_main : gui_getWindowInfo().canvas_pad; + auto renderer = VulkanRenderer::GetInstance(); + m_instance = renderer->GetVkInstance(); + m_logicalDevice = renderer->GetLogicalDevice(); + m_physicalDevice = renderer->GetPhysicalDevice(); + + m_surface = renderer->CreateFramebufferSurface(m_instance, windowHandleInfo); +} + + +SwapchainInfoVk::~SwapchainInfoVk() +{ + Cleanup(); + if(m_surface != VK_NULL_HANDLE) + vkDestroySurfaceKHR(m_instance, m_surface, nullptr); +} + +void SwapchainInfoVk::Create() +{ + const auto details = QuerySwapchainSupport(m_surface, m_physicalDevice); m_surfaceFormat = ChooseSurfaceFormat(details.formats); m_actualExtent = ChooseSwapExtent(details.capabilities); @@ -20,28 +39,28 @@ void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDe if(image_count < 2) cemuLog_log(LogType::Force, "Vulkan: Swapchain image count less than 2 may cause problems"); - VkSwapchainCreateInfoKHR create_info = CreateSwapchainCreateInfo(surface, details, m_surfaceFormat, image_count, m_actualExtent); + VkSwapchainCreateInfoKHR create_info = CreateSwapchainCreateInfo(m_surface, details, m_surfaceFormat, image_count, m_actualExtent); create_info.oldSwapchain = nullptr; create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; - VkResult result = vkCreateSwapchainKHR(logicalDevice, &create_info, nullptr, &swapchain); + VkResult result = vkCreateSwapchainKHR(m_logicalDevice, &create_info, nullptr, &m_swapchain); if (result != VK_SUCCESS) UnrecoverableError("Error attempting to create a swapchain"); - result = vkGetSwapchainImagesKHR(logicalDevice, swapchain, &image_count, nullptr); + result = vkGetSwapchainImagesKHR(m_logicalDevice, m_swapchain, &image_count, nullptr); if (result != VK_SUCCESS) UnrecoverableError("Error attempting to retrieve the count of swapchain images"); m_swapchainImages.resize(image_count); - result = vkGetSwapchainImagesKHR(logicalDevice, swapchain, &image_count, m_swapchainImages.data()); + result = vkGetSwapchainImagesKHR(m_logicalDevice, m_swapchain, &image_count, m_swapchainImages.data()); if (result != VK_SUCCESS) UnrecoverableError("Error attempting to retrieve swapchain images"); // create default renderpass VkAttachmentDescription colorAttachment = {}; colorAttachment.format = m_surfaceFormat.format; colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; - colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; @@ -62,7 +81,7 @@ void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDe renderPassInfo.pAttachments = &colorAttachment; renderPassInfo.subpassCount = 1; renderPassInfo.pSubpasses = &subpass; - result = vkCreateRenderPass(logicalDevice, &renderPassInfo, nullptr, &m_swapchainRenderPass); + result = vkCreateRenderPass(m_logicalDevice, &renderPassInfo, nullptr, &m_swapchainRenderPass); if (result != VK_SUCCESS) UnrecoverableError("Failed to create renderpass for swapchain"); @@ -84,7 +103,7 @@ void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDe createInfo.subresourceRange.levelCount = 1; createInfo.subresourceRange.baseArrayLayer = 0; createInfo.subresourceRange.layerCount = 1; - result = vkCreateImageView(logicalDevice, &createInfo, nullptr, &m_swapchainImageViews[i]); + result = vkCreateImageView(m_logicalDevice, &createInfo, nullptr, &m_swapchainImageViews[i]); if (result != VK_SUCCESS) UnrecoverableError("Failed to create imageviews for swapchain"); } @@ -104,7 +123,7 @@ void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDe framebufferInfo.width = m_actualExtent.width; framebufferInfo.height = m_actualExtent.height; framebufferInfo.layers = 1; - result = vkCreateFramebuffer(logicalDevice, &framebufferInfo, nullptr, &m_swapchainFramebuffers[i]); + result = vkCreateFramebuffer(m_logicalDevice, &framebufferInfo, nullptr, &m_swapchainFramebuffers[i]); if (result != VK_SUCCESS) UnrecoverableError("Failed to create framebuffer for swapchain"); } @@ -114,7 +133,7 @@ void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDe VkSemaphoreCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; for (auto& semaphore : m_presentSemaphores){ - if (vkCreateSemaphore(logicalDevice, &info, nullptr, &semaphore) != VK_SUCCESS) + if (vkCreateSemaphore(m_logicalDevice, &info, nullptr, &semaphore) != VK_SUCCESS) UnrecoverableError("Failed to create semaphore for swapchain present"); } @@ -123,14 +142,14 @@ void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDe info = {}; info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; for (auto& semaphore : m_acquireSemaphores){ - if (vkCreateSemaphore(logicalDevice, &info, nullptr, &semaphore) != VK_SUCCESS) + if (vkCreateSemaphore(m_logicalDevice, &info, nullptr, &semaphore) != VK_SUCCESS) UnrecoverableError("Failed to create semaphore for swapchain acquire"); } VkFenceCreateInfo fenceInfo = {}; fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; - result = vkCreateFence(logicalDevice, &fenceInfo, nullptr, &m_imageAvailableFence); + result = vkCreateFence(m_logicalDevice, &fenceInfo, nullptr, &m_imageAvailableFence); if (result != VK_SUCCESS) UnrecoverableError("Failed to create fence for swapchain"); @@ -167,19 +186,20 @@ void SwapchainInfoVk::Cleanup() if (m_imageAvailableFence) { + WaitAvailableFence(); vkDestroyFence(m_logicalDevice, m_imageAvailableFence, nullptr); m_imageAvailableFence = nullptr; } - if (swapchain) + if (m_swapchain) { - vkDestroySwapchainKHR(m_logicalDevice, swapchain, nullptr); - swapchain = VK_NULL_HANDLE; + vkDestroySwapchainKHR(m_logicalDevice, m_swapchain, nullptr); + m_swapchain = VK_NULL_HANDLE; } } bool SwapchainInfoVk::IsValid() const { - return swapchain && !m_acquireSemaphores.empty(); + return m_swapchain && !m_acquireSemaphores.empty(); } void SwapchainInfoVk::WaitAvailableFence() @@ -207,7 +227,7 @@ bool SwapchainInfoVk::AcquireImage(uint64 timeout) ResetAvailableFence(); VkSemaphore acquireSemaphore = m_acquireSemaphores[m_acquireIndex]; - VkResult result = vkAcquireNextImageKHR(m_logicalDevice, swapchain, timeout, acquireSemaphore, m_imageAvailableFence, &swapchainImageIndex); + VkResult result = vkAcquireNextImageKHR(m_logicalDevice, m_swapchain, timeout, acquireSemaphore, m_imageAvailableFence, &swapchainImageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) m_shouldRecreate = true; if (result < 0) @@ -231,35 +251,6 @@ void SwapchainInfoVk::UnrecoverableError(const char* errMsg) throw std::runtime_error(errMsg); } -SwapchainInfoVk::QueueFamilyIndices SwapchainInfoVk::FindQueueFamilies(VkSurfaceKHR surface, VkPhysicalDevice device) -{ - uint32_t queueFamilyCount = 0; - vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); - - std::vector queueFamilies(queueFamilyCount); - vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); - - QueueFamilyIndices indices; - for (int i = 0; i < (int)queueFamilies.size(); ++i) - { - const auto& queueFamily = queueFamilies[i]; - if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) - indices.graphicsFamily = i; - - VkBool32 presentSupport = false; - const VkResult result = vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); - if (result != VK_SUCCESS) - throw std::runtime_error(fmt::format("Error while attempting to check if a surface supports presentation: {}", result)); - - if (queueFamily.queueCount > 0 && presentSupport) - indices.presentFamily = i; - - if (indices.IsComplete()) - break; - } - - return indices; -} SwapchainInfoVk::SwapchainSupportDetails SwapchainInfoVk::QuerySwapchainSupport(VkSurfaceKHR surface, const VkPhysicalDevice& device) { @@ -391,7 +382,7 @@ VkSwapchainCreateInfoKHR SwapchainInfoVk::CreateSwapchainCreateInfo(VkSurfaceKHR createInfo.imageArrayLayers = 1; createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; - const QueueFamilyIndices indices = FindQueueFamilies(surface, m_physicalDevice); + const VulkanRenderer::QueueFamilyIndices indices = VulkanRenderer::GetInstance()->FindQueueFamilies(surface, m_physicalDevice); m_swapchainQueueFamilyIndices = { (uint32)indices.graphicsFamily, (uint32)indices.presentFamily }; if (indices.graphicsFamily != indices.presentFamily) { diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.h b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.h index 26dbc7d1..0e8c2ade 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.h @@ -14,14 +14,6 @@ struct SwapchainInfoVk SYNC_AND_LIMIT = 3, // synchronize emulated vsync events to monitor vsync. But skip events if rate higher than virtual vsync period }; - struct QueueFamilyIndices - { - int32_t graphicsFamily = -1; - int32_t presentFamily = -1; - - bool IsComplete() const { return graphicsFamily >= 0 && presentFamily >= 0; } - }; - struct SwapchainSupportDetails { VkSurfaceCapabilitiesKHR capabilities; @@ -30,7 +22,7 @@ struct SwapchainInfoVk }; void Cleanup(); - void Create(VkPhysicalDevice physicalDevice, VkDevice logicalDevice); + void Create(); bool IsValid() const; @@ -45,8 +37,6 @@ struct SwapchainInfoVk static void UnrecoverableError(const char* errMsg); - // todo: move this function somewhere more sensible. Not directly swapchain related - static QueueFamilyIndices FindQueueFamilies(VkSurfaceKHR surface, VkPhysicalDevice device); static SwapchainSupportDetails QuerySwapchainSupport(VkSurfaceKHR surface, const VkPhysicalDevice& device); VkPresentModeKHR ChoosePresentMode(const std::vector& modes); @@ -61,14 +51,10 @@ struct SwapchainInfoVk return m_actualExtent; } - SwapchainInfoVk(VkSurfaceKHR surface, bool mainWindow) - : surface(surface), mainWindow(mainWindow) {} + SwapchainInfoVk(bool mainWindow, Vector2i size); SwapchainInfoVk(const SwapchainInfoVk&) = delete; SwapchainInfoVk(SwapchainInfoVk&&) noexcept = default; - ~SwapchainInfoVk() - { - Cleanup(); - } + ~SwapchainInfoVk(); bool mainWindow{}; @@ -77,11 +63,12 @@ struct SwapchainInfoVk VSync m_vsyncState = VSync::Immediate; bool hasDefinedSwapchainImage{}; // indicates if the swapchain image is in a defined state + VkInstance m_instance{}; VkPhysicalDevice m_physicalDevice{}; VkDevice m_logicalDevice{}; - VkSurfaceKHR surface{}; + VkSurfaceKHR m_surface{}; VkSurfaceFormatKHR m_surfaceFormat{}; - VkSwapchainKHR swapchain{}; + VkSwapchainKHR m_swapchain{}; Vector2i m_desiredExtent{}; uint32 swapchainImageIndex = (uint32)-1; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 02bb1e71..e0ebda2a 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -167,6 +167,7 @@ std::vector VulkanRenderer::GetDevices() result.emplace_back(physDeviceProps.properties.deviceName, physDeviceIDProps.deviceUUID); } } + vkDestroySurfaceKHR(instance, surface, nullptr); } catch (...) { @@ -441,7 +442,7 @@ VulkanRenderer::VulkanRenderer() } // create logical device - m_indices = SwapchainInfoVk::FindQueueFamilies(surface, m_physicalDevice); + m_indices = FindQueueFamilies(surface, m_physicalDevice); std::set uniqueQueueFamilies = { m_indices.graphicsFamily, m_indices.presentFamily }; std::vector queueCreateInfos = CreateQueueCreateInfos(uniqueQueueFamilies); VkPhysicalDeviceFeatures deviceFeatures = {}; @@ -510,7 +511,7 @@ VulkanRenderer::VulkanRenderer() PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT = reinterpret_cast(vkGetInstanceProcAddr(m_instance, "vkCreateDebugUtilsMessengerEXT")); VkDebugUtilsMessengerCreateInfoEXT debugCallback{}; - debugCallback.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT; + debugCallback.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; debugCallback.pNext = nullptr; debugCallback.flags = 0; debugCallback.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT; @@ -673,24 +674,19 @@ VulkanRenderer* VulkanRenderer::GetInstance() void VulkanRenderer::InitializeSurface(const Vector2i& size, bool mainWindow) { - auto& windowHandleInfo = mainWindow ? gui_getWindowInfo().canvas_main : gui_getWindowInfo().canvas_pad; - - const auto surface = CreateFramebufferSurface(m_instance, windowHandleInfo); if (mainWindow) { - m_mainSwapchainInfo = std::make_unique(surface, mainWindow); - m_mainSwapchainInfo->m_desiredExtent = size; - m_mainSwapchainInfo->Create(m_physicalDevice, m_logicalDevice); + m_mainSwapchainInfo = std::make_unique(mainWindow, size); + m_mainSwapchainInfo->Create(); // aquire first command buffer InitFirstCommandBuffer(); } else { - m_padSwapchainInfo = std::make_unique(surface, mainWindow); - m_padSwapchainInfo->m_desiredExtent = size; + m_padSwapchainInfo = std::make_unique(mainWindow, size); // todo: figure out a way to exclusively create swapchain on main LatteThread - m_padSwapchainInfo->Create(m_physicalDevice, m_logicalDevice); + m_padSwapchainInfo->Create(); } } @@ -1074,6 +1070,36 @@ RendererShader* VulkanRenderer::shader_create(RendererShader::ShaderType type, u return new RendererShaderVk(type, baseHash, auxHash, isGameShader, isGfxPackShader, source); } +VulkanRenderer::QueueFamilyIndices VulkanRenderer::FindQueueFamilies(VkSurfaceKHR surface, VkPhysicalDevice device) +{ + uint32_t queueFamilyCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); + + std::vector queueFamilies(queueFamilyCount); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); + + QueueFamilyIndices indices; + for (int i = 0; i < (int)queueFamilies.size(); ++i) + { + const auto& queueFamily = queueFamilies[i]; + if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) + indices.graphicsFamily = i; + + VkBool32 presentSupport = false; + const VkResult result = vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + if (result != VK_SUCCESS) + throw std::runtime_error(fmt::format("Error while attempting to check if a surface supports presentation: {}", result)); + + if (queueFamily.queueCount > 0 && presentSupport) + indices.presentFamily = i; + + if (indices.IsComplete()) + break; + } + + return indices; +} + bool VulkanRenderer::CheckDeviceExtensionSupport(const VkPhysicalDevice device, FeatureControl& info) { std::vector availableDeviceExtensions; @@ -1215,7 +1241,7 @@ std::vector VulkanRenderer::CheckInstanceExtensionSupport(FeatureCo bool VulkanRenderer::IsDeviceSuitable(VkSurfaceKHR surface, const VkPhysicalDevice& device) { - if (!SwapchainInfoVk::FindQueueFamilies(surface, device).IsComplete()) + if (!FindQueueFamilies(surface, device).IsComplete()) return false; // check API version (using Vulkan 1.0 way of querying properties) @@ -2605,7 +2631,7 @@ void VulkanRenderer::RecreateSwapchain(bool mainWindow, bool skipCreate) chainInfo.m_desiredExtent = size; if(!skipCreate) { - chainInfo.Create(m_physicalDevice, m_logicalDevice); + chainInfo.Create(); } if (mainWindow) @@ -2675,7 +2701,7 @@ void VulkanRenderer::SwapBuffer(bool mainWindow) VkPresentInfoKHR presentInfo = {}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.swapchainCount = 1; - presentInfo.pSwapchains = &chainInfo.swapchain; + presentInfo.pSwapchains = &chainInfo.m_swapchain; presentInfo.pImageIndices = &chainInfo.swapchainImageIndex; // wait on command buffer semaphore presentInfo.waitSemaphoreCount = 1; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index 47097dfa..2491d052 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -127,7 +127,6 @@ class VulkanRenderer : public Renderer friend class PipelineCompiler; using VSync = SwapchainInfoVk::VSync; - using QueueFamilyIndices = SwapchainInfoVk::QueueFamilyIndices; static const inline int UNIFORMVAR_RINGBUFFER_SIZE = 1024 * 1024 * 16; // 16MB @@ -421,6 +420,18 @@ private: VkDescriptorPool m_descriptorPool; + public: + struct QueueFamilyIndices + { + int32_t graphicsFamily = -1; + int32_t presentFamily = -1; + + bool IsComplete() const { return graphicsFamily >= 0 && presentFamily >= 0; } + }; + static QueueFamilyIndices FindQueueFamilies(VkSurfaceKHR surface, VkPhysicalDevice device); + + private: + struct FeatureControl { struct From 241915e1a6bfd92e4ffd0d6961a178335300e83f Mon Sep 17 00:00:00 2001 From: capitalistspz Date: Sun, 24 Mar 2024 10:11:18 +0000 Subject: [PATCH 050/299] Gamelist: Display title long names + improvements for shortcuts (#1126) - Windows icons are stored as .ico files to %LOCALAPPDATA%/Cemu/icons/ - Long title names chosen as some games (NSMBU + NSLU) add trailing dots for their shortnames - Long title names have their newlines replaced with spaces at parsing - Linux shortcut paths are saved with UTF-8 encoding - Game titles are copied and saved with UTF-8 encoding --- src/Cafe/TitleList/ParsedMetaXml.h | 7 +- src/Cafe/TitleList/TitleInfo.cpp | 4 +- src/gui/CemuApp.cpp | 7 +- src/gui/components/wxGameList.cpp | 272 ++++++++++++++++------------- 4 files changed, 168 insertions(+), 122 deletions(-) diff --git a/src/Cafe/TitleList/ParsedMetaXml.h b/src/Cafe/TitleList/ParsedMetaXml.h index 7537d603..f1aee37e 100644 --- a/src/Cafe/TitleList/ParsedMetaXml.h +++ b/src/Cafe/TitleList/ParsedMetaXml.h @@ -90,8 +90,11 @@ struct ParsedMetaXml else if (boost::starts_with(name, "longname_")) { const sint32 index = GetLanguageIndex(name.substr(std::size("longname_") - 1)); - if (index != -1) - parsedMetaXml->m_long_name[index] = child.text().as_string(); + if (index != -1){ + std::string longname = child.text().as_string(); + std::replace_if(longname.begin(), longname.end(), [](char c) { return c == '\r' || c == '\n';}, ' '); + parsedMetaXml->m_long_name[index] = longname; + } } else if (boost::starts_with(name, L"shortname_")) { diff --git a/src/Cafe/TitleList/TitleInfo.cpp b/src/Cafe/TitleList/TitleInfo.cpp index ff457575..d23e1d0a 100644 --- a/src/Cafe/TitleList/TitleInfo.cpp +++ b/src/Cafe/TitleList/TitleInfo.cpp @@ -637,9 +637,9 @@ std::string TitleInfo::GetMetaTitleName() const if (m_parsedMetaXml) { std::string titleNameCfgLanguage; - titleNameCfgLanguage = m_parsedMetaXml->GetShortName(GetConfig().console_language); + titleNameCfgLanguage = m_parsedMetaXml->GetLongName(GetConfig().console_language); if (titleNameCfgLanguage.empty()) //Get English Title - titleNameCfgLanguage = m_parsedMetaXml->GetShortName(CafeConsoleLanguage::EN); + titleNameCfgLanguage = m_parsedMetaXml->GetLongName(CafeConsoleLanguage::EN); if (titleNameCfgLanguage.empty()) //Unknown Title titleNameCfgLanguage = "Unknown Title"; return titleNameCfgLanguage; diff --git a/src/gui/CemuApp.cpp b/src/gui/CemuApp.cpp index fde4bcc0..7f11d4c6 100644 --- a/src/gui/CemuApp.cpp +++ b/src/gui/CemuApp.cpp @@ -59,7 +59,12 @@ bool CemuApp::OnInit() fs::path user_data_path, config_path, cache_path, data_path; auto standardPaths = wxStandardPaths::Get(); fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath())); - +#if BOOST_OS_LINUX + // GetExecutablePath returns the AppImage's temporary mount location + wxString appImagePath; + if (wxGetEnv(("APPIMAGE"), &appImagePath)) + exePath = wxHelper::MakeFSPath(appImagePath); +#endif // Try a portable path first, if it exists. user_data_path = config_path = cache_path = data_path = exePath.parent_path() / "portable"; #if BOOST_OS_MACOS diff --git a/src/gui/components/wxGameList.cpp b/src/gui/components/wxGameList.cpp index 8e8f3c4d..73bcd98d 100644 --- a/src/gui/components/wxGameList.cpp +++ b/src/gui/components/wxGameList.cpp @@ -19,7 +19,6 @@ #include #include - #include #include @@ -526,7 +525,6 @@ void wxGameList::OnKeyDown(wxListEvent& event) } } - enum ContextMenuEntries { kContextMenuRefreshGames = wxID_HIGHEST + 1, @@ -732,7 +730,7 @@ void wxGameList::OnContextMenuSelected(wxCommandEvent& event) { if (wxTheClipboard->Open()) { - wxTheClipboard->SetData(new wxTextDataObject(gameInfo.GetTitleName())); + wxTheClipboard->SetData(new wxTextDataObject(wxString::FromUTF8(gameInfo.GetTitleName()))); wxTheClipboard->Close(); } break; @@ -1276,129 +1274,169 @@ void wxGameList::DeleteCachedStrings() m_name_cache.clear(); } -#if BOOST_OS_LINUX || BOOST_OS_WINDOWS -void wxGameList::CreateShortcut(GameInfo2& gameInfo) { - const auto title_id = gameInfo.GetBaseTitleId(); - const auto title_name = gameInfo.GetTitleName(); - auto exe_path = ActiveSettings::GetExecutablePath(); - const char *flatpak_id = getenv("FLATPAK_ID"); - - // GetExecutablePath returns the AppImage's temporary mount location, instead of its actual path - wxString appimage_path; - if (wxGetEnv(("APPIMAGE"), &appimage_path)) { - exe_path = appimage_path.utf8_string(); - } - #if BOOST_OS_LINUX - const wxString desktop_entry_name = wxString::Format("%s.desktop", title_name); - wxFileDialog entry_dialog(this, _("Choose desktop entry location"), "~/.local/share/applications", desktop_entry_name, - "Desktop file (*.desktop)|*.desktop", wxFD_SAVE | wxFD_CHANGE_DIR | wxFD_OVERWRITE_PROMPT); -#elif BOOST_OS_WINDOWS - // Get '%APPDATA%\Microsoft\Windows\Start Menu\Programs' path - PWSTR user_shortcut_folder; - SHGetKnownFolderPath(FOLDERID_Programs, 0, NULL, &user_shortcut_folder); - const wxString shortcut_name = wxString::Format("%s.lnk", title_name); - wxFileDialog entry_dialog(this, _("Choose shortcut location"), _pathToUtf8(user_shortcut_folder), shortcut_name, - "Shortcut (*.lnk)|*.lnk", wxFD_SAVE | wxFD_CHANGE_DIR | wxFD_OVERWRITE_PROMPT); -#endif - const auto result = entry_dialog.ShowModal(); - if (result == wxID_CANCEL) - return; - const auto output_path = entry_dialog.GetPath(); +void wxGameList::CreateShortcut(GameInfo2& gameInfo) +{ + const auto titleId = gameInfo.GetBaseTitleId(); + const auto titleName = wxString::FromUTF8(gameInfo.GetTitleName()); + auto exePath = ActiveSettings::GetExecutablePath(); + const char* flatpakId = getenv("FLATPAK_ID"); -#if BOOST_OS_LINUX - std::optional icon_path; - // Obtain and convert icon - { - m_icon_cache_mtx.lock(); - const auto icon_iter = m_icon_cache.find(title_id); - const auto result_index = (icon_iter != m_icon_cache.cend()) ? std::optional(icon_iter->second.first) : std::nullopt; - m_icon_cache_mtx.unlock(); + const wxString desktopEntryName = wxString::Format("%s.desktop", titleName); + wxFileDialog entryDialog(this, _("Choose desktop entry location"), "~/.local/share/applications", desktopEntryName, + "Desktop file (*.desktop)|*.desktop", wxFD_SAVE | wxFD_CHANGE_DIR | wxFD_OVERWRITE_PROMPT); + const auto result = entryDialog.ShowModal(); + if (result == wxID_CANCEL) + return; + const auto output_path = entryDialog.GetPath(); - // In most cases it should find it - if (!result_index){ - wxMessageBox(_("Icon is yet to load, so will not be used by the shortcut"), _("Warning"), wxOK | wxCENTRE | wxICON_WARNING); - } - else { - const fs::path out_icon_dir = ActiveSettings::GetUserDataPath("icons"); + std::optional iconPath; + // Obtain and convert icon + [&]() + { + int iconIndex, smallIconIndex; - if (!fs::exists(out_icon_dir) && !fs::create_directories(out_icon_dir)){ - wxMessageBox(_("Cannot access the icon directory, the shortcut will have no icon"), _("Warning"), wxOK | wxCENTRE | wxICON_WARNING); - } - else { - icon_path = out_icon_dir / fmt::format("{:016x}.png", gameInfo.GetBaseTitleId()); + if (!QueryIconForTitle(titleId, iconIndex, smallIconIndex)) + { + cemuLog_log(LogType::Force, "Icon hasn't loaded"); + return; + } + const fs::path outIconDir = ActiveSettings::GetUserDataPath("icons"); - auto image = m_image_list->GetIcon(result_index.value()).ConvertToImage(); + if (!fs::exists(outIconDir) && !fs::create_directories(outIconDir)) + { + cemuLog_log(LogType::Force, "Failed to create icon directory"); + return; + } - wxFileOutputStream png_file(_pathToUtf8(icon_path.value())); - wxPNGHandler pngHandler; - if (!pngHandler.SaveFile(&image, png_file, false)) { - icon_path = std::nullopt; - wxMessageBox(_("The icon was unable to be saved, the shortcut will have no icon"), _("Warning"), wxOK | wxCENTRE | wxICON_WARNING); - } - } - } - } + iconPath = outIconDir / fmt::format("{:016x}.png", gameInfo.GetBaseTitleId()); + wxFileOutputStream pngFileStream(_pathToUtf8(iconPath.value())); - std::string desktop_exec_entry; - if (flatpak_id) - desktop_exec_entry = fmt::format("/usr/bin/flatpak run {0} --title-id {1:016x}", flatpak_id, title_id); - else - desktop_exec_entry = fmt::format("{0:?} --title-id {1:016x}", _pathToUtf8(exe_path), title_id); + auto image = m_image_list->GetIcon(iconIndex).ConvertToImage(); + wxPNGHandler pngHandler; + if (!pngHandler.SaveFile(&image, pngFileStream, false)) + { + iconPath = std::nullopt; + cemuLog_log(LogType::Force, "Icon failed to save"); + } + }(); - // 'Icon' accepts spaces in file name, does not accept quoted file paths - // 'Exec' does not accept non-escaped spaces, and can accept quoted file paths - auto desktop_entry_string = - fmt::format("[Desktop Entry]\n" - "Name={0}\n" - "Comment=Play {0} on Cemu\n" - "Exec={1}\n" - "Icon={2}\n" - "Terminal=false\n" - "Type=Application\n" - "Categories=Game;\n", - title_name, - desktop_exec_entry, - _pathToUtf8(icon_path.value_or(""))); + std::string desktopExecEntry = flatpakId ? fmt::format("/usr/bin/flatpak run {0} --title-id {1:016x}", flatpakId, titleId) + : fmt::format("{0:?} --title-id {1:016x}", _pathToUtf8(exePath), titleId); - if (flatpak_id) - desktop_entry_string += fmt::format("X-Flatpak={}\n", flatpak_id); + // 'Icon' accepts spaces in file name, does not accept quoted file paths + // 'Exec' does not accept non-escaped spaces, and can accept quoted file paths + auto desktopEntryString = fmt::format( + "[Desktop Entry]\n" + "Name={0}\n" + "Comment=Play {0} on Cemu\n" + "Exec={1}\n" + "Icon={2}\n" + "Terminal=false\n" + "Type=Application\n" + "Categories=Game;\n", + titleName.utf8_string(), + desktopExecEntry, + _pathToUtf8(iconPath.value_or(""))); - std::ofstream output_stream(output_path); - if (!output_stream.good()) - { - auto errorMsg = formatWxString(_("Failed to save desktop entry to {}"), output_path.utf8_string()); - wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); - return; - } - output_stream << desktop_entry_string; + if (flatpakId) + desktopEntryString += fmt::format("X-Flatpak={}\n", flatpakId); -#elif BOOST_OS_WINDOWS - IShellLinkW *shell_link; - HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLink, reinterpret_cast(&shell_link)); - if (SUCCEEDED(hres)) - { - const auto description = wxString::Format("Play %s on Cemu", title_name); - const auto args = wxString::Format("-t %016llx", title_id); - - shell_link->SetPath(exe_path.wstring().c_str()); - shell_link->SetDescription(description.wc_str()); - shell_link->SetArguments(args.wc_str()); - shell_link->SetWorkingDirectory(exe_path.parent_path().wstring().c_str()); - // Use icon from Cemu exe for now since we can't embed icons into the shortcut - // in the future we could convert and store icons in AppData or ProgramData - shell_link->SetIconLocation(exe_path.wstring().c_str(), 0); - - IPersistFile *shell_link_file; - // save the shortcut - hres = shell_link->QueryInterface(IID_IPersistFile, reinterpret_cast(&shell_link_file)); - if (SUCCEEDED(hres)) - { - hres = shell_link_file->Save(output_path.wc_str(), TRUE); - shell_link_file->Release(); - } - shell_link->Release(); - } -#endif + std::ofstream outputStream(output_path.utf8_string()); + if (!outputStream.good()) + { + auto errorMsg = formatWxString(_("Failed to save desktop entry to {}"), output_path.utf8_string()); + wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); + return; + } + outputStream << desktopEntryString; } -#endif +#elif BOOST_OS_WINDOWS +void wxGameList::CreateShortcut(GameInfo2& gameInfo) +{ + const auto titleId = gameInfo.GetBaseTitleId(); + const auto titleName = wxString::FromUTF8(gameInfo.GetTitleName()); + auto exePath = ActiveSettings::GetExecutablePath(); + + // Get '%APPDATA%\Microsoft\Windows\Start Menu\Programs' path + PWSTR userShortcutFolder; + SHGetKnownFolderPath(FOLDERID_Programs, 0, NULL, &userShortcutFolder); + const wxString shortcutName = wxString::Format("%s.lnk", titleName); + wxFileDialog shortcutDialog(this, _("Choose shortcut location"), _pathToUtf8(userShortcutFolder), shortcutName, + "Shortcut (*.lnk)|*.lnk", wxFD_SAVE | wxFD_CHANGE_DIR | wxFD_OVERWRITE_PROMPT); + + const auto result = shortcutDialog.ShowModal(); + if (result == wxID_CANCEL) + return; + const auto outputPath = shortcutDialog.GetPath(); + + std::optional icon_path = std::nullopt; + [&]() + { + int iconIdx; + int smallIconIdx; + if (!QueryIconForTitle(titleId, iconIdx, smallIconIdx)) + { + cemuLog_log(LogType::Force, "Icon hasn't loaded"); + return; + } + const auto icon = m_image_list->GetIcon(iconIdx); + PWSTR localAppData; + const auto hres = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &localAppData); + wxBitmap bitmap{}; + auto folder = fs::path(localAppData) / "Cemu" / "icons"; + if (!SUCCEEDED(hres) || (!fs::exists(folder) && !fs::create_directories(folder))) + { + cemuLog_log(LogType::Force, "Failed to create icon directory"); + return; + } + if (!bitmap.CopyFromIcon(icon)) + { + cemuLog_log(LogType::Force, "Failed to copy icon"); + return; + } + + icon_path = folder / fmt::format("{:016x}.ico", titleId); + auto stream = wxFileOutputStream(_pathToUtf8(*icon_path)); + auto image = bitmap.ConvertToImage(); + wxICOHandler icohandler{}; + if (!icohandler.SaveFile(&image, stream, false)) + { + icon_path = std::nullopt; + cemuLog_log(LogType::Force, "Icon failed to save"); + } + }(); + + IShellLinkW* shellLink; + HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLink, reinterpret_cast(&shellLink)); + if (SUCCEEDED(hres)) + { + const auto description = wxString::Format("Play %s on Cemu", titleName); + const auto args = wxString::Format("-t %016llx", titleId); + + shellLink->SetPath(exePath.wstring().c_str()); + shellLink->SetDescription(description.wc_str()); + shellLink->SetArguments(args.wc_str()); + shellLink->SetWorkingDirectory(exePath.parent_path().wstring().c_str()); + + if (icon_path) + shellLink->SetIconLocation(icon_path->wstring().c_str(), 0); + else + shellLink->SetIconLocation(exePath.wstring().c_str(), 0); + + IPersistFile* shellLinkFile; + // save the shortcut + hres = shellLink->QueryInterface(IID_IPersistFile, reinterpret_cast(&shellLinkFile)); + if (SUCCEEDED(hres)) + { + hres = shellLinkFile->Save(outputPath.wc_str(), TRUE); + shellLinkFile->Release(); + } + shellLink->Release(); + } + if (!SUCCEEDED(hres)) { + auto errorMsg = formatWxString(_("Failed to save shortcut to {}"), outputPath); + wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); + } +} +#endif \ No newline at end of file From 4d148b369660db629c225bf1ff0e354d025e7902 Mon Sep 17 00:00:00 2001 From: Francesco Saltori Date: Mon, 25 Mar 2024 21:34:40 +0100 Subject: [PATCH 051/299] Add supported locales to macOS plist (#1133) --- src/resource/MacOSXBundleInfo.plist.in | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/resource/MacOSXBundleInfo.plist.in b/src/resource/MacOSXBundleInfo.plist.in index 98064735..ccd1c922 100644 --- a/src/resource/MacOSXBundleInfo.plist.in +++ b/src/resource/MacOSXBundleInfo.plist.in @@ -30,6 +30,28 @@ ${MACOSX_BUNDLE_CATEGORY} LSMinimumSystemVersion ${MACOSX_MINIMUM_SYSTEM_VERSION} + CFBundleLocalizations + + ca + de + en + es + fr + he + hu + it + ja + ko + nb + nl + pl + pt + ru + sv + tr + uk + zh + CFBundleDocumentTypes From 4b7d2f88ae044a590dfa7faeadee1e03047492f0 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Mon, 25 Mar 2024 21:02:37 +0100 Subject: [PATCH 052/299] Latte: Enable colorbuffer optimization if gfx packs are aware The optimization for colorbuffer resolution introduced in PR #706 is now enabled. This optimization changes the resolution of certain framebuffer textures, which may conflict with the texture resolution rules set by some graphic packs. As a result, if a graphic pack that specifies texture resolution rules is in use, the optimization will automatically be turned off to prevent any issues. To circumvent this, graphic packs can now include the setting "colorbufferOptimizationAware = true" in their rules.txt. This setting indicates that the pack has been updated to handle the resolution changes introduced by the optimization. Cemu will allow the optimization to remain enabled if resolution packs have this flag set. --- src/Cafe/GraphicPack/GraphicPack2.cpp | 4 ++++ src/Cafe/GraphicPack/GraphicPack2.h | 3 +++ src/Cafe/HW/Latte/Core/Latte.h | 2 ++ src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp | 21 ++++++++++---------- src/Cafe/HW/Latte/Core/LatteTexture.cpp | 7 ++++--- src/Cafe/HW/Latte/Core/LatteThread.cpp | 17 ++++++++++++++++ 6 files changed, 41 insertions(+), 13 deletions(-) diff --git a/src/Cafe/GraphicPack/GraphicPack2.cpp b/src/Cafe/GraphicPack/GraphicPack2.cpp index b581316e..27d423b9 100644 --- a/src/Cafe/GraphicPack/GraphicPack2.cpp +++ b/src/Cafe/GraphicPack/GraphicPack2.cpp @@ -280,6 +280,10 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules) m_enabled = m_default_enabled; } + auto option_allowRendertargetSizeOptimization = rules.FindOption("colorbufferOptimizationAware"); + if (option_allowRendertargetSizeOptimization) + m_allowRendertargetSizeOptimization = boost::iequals(*option_allowRendertargetSizeOptimization, "true") || boost::iequals(*option_allowRendertargetSizeOptimization, "1"); + auto option_vendorFilter = rules.FindOption("vendorFilter"); if (option_vendorFilter) { diff --git a/src/Cafe/GraphicPack/GraphicPack2.h b/src/Cafe/GraphicPack/GraphicPack2.h index 6b07cce9..9b6a86d4 100644 --- a/src/Cafe/GraphicPack/GraphicPack2.h +++ b/src/Cafe/GraphicPack/GraphicPack2.h @@ -113,6 +113,7 @@ public: const std::string& GetVirtualPath() const { return m_virtualPath; } // returns the path in the gfx tree hierarchy const std::string& GetDescription() const { return m_description; } bool IsDefaultEnabled() const { return m_default_enabled; } + bool AllowRendertargetSizeOptimization() const { return m_allowRendertargetSizeOptimization; } void SetEnabled(bool state) { m_enabled = state; } @@ -217,6 +218,8 @@ private: bool m_default_enabled = false; + bool m_allowRendertargetSizeOptimization = false; // gfx pack supports framebuffers with non-padded sizes, which is an optional optimization introduced with Cemu 2.0-74 + // filter std::optional m_renderer_api; std::optional m_gfx_vendor; diff --git a/src/Cafe/HW/Latte/Core/Latte.h b/src/Cafe/HW/Latte/Core/Latte.h index d9419a6a..e8cb2be4 100644 --- a/src/Cafe/HW/Latte/Core/Latte.h +++ b/src/Cafe/HW/Latte/Core/Latte.h @@ -25,6 +25,8 @@ struct LatteGPUState_t // context control uint32 contextControl0; uint32 contextControl1; + // optional features + bool allowFramebufferSizeOptimization{false}; // allow using scissor box as size hint to determine non-padded rendertarget size // draw context struct { diff --git a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp index 30069712..f165e257 100644 --- a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp +++ b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp @@ -267,14 +267,15 @@ LatteTextureView* LatteMRT::GetColorAttachmentTexture(uint32 index, bool createN // colorbuffer width/height has to be padded to 8/32 alignment but the actual resolution might be smaller // use the scissor box as a clue to figure out the original resolution if possible -#if 0 - uint32 scissorBoxWidth = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_BR.get_BR_X(); - uint32 scissorBoxHeight = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_BR.get_BR_Y(); - if (((scissorBoxWidth + 7) & ~7) == colorBufferWidth) - colorBufferWidth = scissorBoxWidth; - if (((colorBufferHeight + 31) & ~31) == colorBufferHeight) - colorBufferHeight = scissorBoxHeight; -#endif + if(LatteGPUState.allowFramebufferSizeOptimization) + { + uint32 scissorBoxWidth = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_BR.get_BR_X(); + uint32 scissorBoxHeight = LatteGPUState.contextNew.PA_SC_GENERIC_SCISSOR_BR.get_BR_Y(); + if (((scissorBoxWidth + 7) & ~7) == colorBufferWidth) + colorBufferWidth = scissorBoxWidth; + if (((colorBufferHeight + 31) & ~31) == colorBufferHeight) + colorBufferHeight = scissorBoxHeight; + } // log resolution changes if the above heuristic takes effect // this is useful to find resolutions which need to be updated in gfx pack texture rules @@ -303,7 +304,7 @@ LatteTextureView* LatteMRT::GetColorAttachmentTexture(uint32 index, bool createN if (colorBufferView == nullptr) { // create color buffer view - colorBufferView = LatteTexture_CreateMapping(colorBufferPhysMem, 0, colorBufferWidth, colorBufferHeight, (viewFirstSlice + viewNumSlices), colorBufferPitch, colorBufferTileMode, colorBufferSwizzle>>8, viewFirstMip, 1, viewFirstSlice, viewNumSlices, (Latte::E_GX2SURFFMT)colorBufferFormat, (viewFirstSlice + viewNumSlices)>1? Latte::E_DIM::DIM_2D_ARRAY: Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, false); + colorBufferView = LatteTexture_CreateMapping(colorBufferPhysMem, 0, colorBufferWidth, colorBufferHeight, (viewFirstSlice + viewNumSlices), colorBufferPitch, colorBufferTileMode, colorBufferSwizzle>>8, viewFirstMip, 1, viewFirstSlice, viewNumSlices, (Latte::E_GX2SURFFMT)colorBufferFormat, (viewFirstSlice + viewNumSlices)>1? Latte::E_DIM::DIM_2D_ARRAY: Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, false, true); LatteGPUState.repeatTextureInitialization = true; checkForTextureChanges = false; } @@ -582,7 +583,7 @@ bool LatteMRT::UpdateCurrentFBO() if (!depthBufferView) { // create new depth buffer view and if it doesn't exist then also create the texture - depthBufferView = LatteTexture_CreateMapping(depthBufferPhysMem, 0, depthBufferWidth, depthBufferHeight, depthBufferViewFirstSlice+1, depthBufferPitch, depthBufferTileMode, depthBufferSwizzle, 0, 1, depthBufferViewFirstSlice, 1, depthBufferFormat, depthBufferViewFirstSlice > 0 ? Latte::E_DIM::DIM_2D_ARRAY : Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, true); + depthBufferView = LatteTexture_CreateMapping(depthBufferPhysMem, 0, depthBufferWidth, depthBufferHeight, depthBufferViewFirstSlice+1, depthBufferPitch, depthBufferTileMode, depthBufferSwizzle, 0, 1, depthBufferViewFirstSlice, 1, depthBufferFormat, depthBufferViewFirstSlice > 0 ? Latte::E_DIM::DIM_2D_ARRAY : Latte::E_DIM::DIM_2D, Latte::E_DIM::DIM_2D, true, true); LatteGPUState.repeatTextureInitialization = true; } else diff --git a/src/Cafe/HW/Latte/Core/LatteTexture.cpp b/src/Cafe/HW/Latte/Core/LatteTexture.cpp index d6f576d4..3754fb19 100644 --- a/src/Cafe/HW/Latte/Core/LatteTexture.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTexture.cpp @@ -1,5 +1,4 @@ #include "Cafe/HW/Latte/Core/Latte.h" -#include "Cafe/HW/Latte/Core/LatteDraw.h" #include "Cafe/HW/Latte/Core/LatteShader.h" #include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" #include "Cafe/HW/Latte/Core/LatteTexture.h" @@ -9,6 +8,8 @@ #include "Cafe/GraphicPack/GraphicPack2.h" +#include + struct TexMemOccupancyEntry { uint32 addrStart; @@ -963,7 +964,7 @@ void LatteTexture_RecreateTextureWithDifferentMipSliceCount(LatteTexture* textur } // create new texture representation -// if allowCreateNewDataTexture is true, a new texture will be created if necessary. If it is false, only existing textures may be used, except if a data-compatible version of the requested texture already exists and it's not view compatible +// if allowCreateNewDataTexture is true, a new texture will be created if necessary. If it is false, only existing textures may be used, except if a data-compatible version of the requested texture already exists and it's not view compatible (todo - we should differentiate between Latte compatible views and renderer compatible) // the returned view will map to the provided mip and slice range within the created texture, this is to match the behavior of lookupSliceEx LatteTextureView* LatteTexture_CreateMapping(MPTR physAddr, MPTR physMipAddr, sint32 width, sint32 height, sint32 depth, sint32 pitch, Latte::E_HWTILEMODE tileMode, uint32 swizzle, sint32 firstMip, sint32 numMip, sint32 firstSlice, sint32 numSlice, Latte::E_GX2SURFFMT format, Latte::E_DIM dimBase, Latte::E_DIM dimView, bool isDepth, bool allowCreateNewDataTexture) { @@ -980,7 +981,7 @@ LatteTextureView* LatteTexture_CreateMapping(MPTR physAddr, MPTR physMipAddr, si // todo, depth and numSlice are redundant sint32 sliceCount = firstSlice + numSlice; - std::vector list_overlappingTextures; + boost::container::small_vector list_overlappingTextures; for (sint32 sliceIndex = 0; sliceIndex < sliceCount; sliceIndex++) { sint32 mipIndex = 0; diff --git a/src/Cafe/HW/Latte/Core/LatteThread.cpp b/src/Cafe/HW/Latte/Core/LatteThread.cpp index bd312d93..a23bd5be 100644 --- a/src/Cafe/HW/Latte/Core/LatteThread.cpp +++ b/src/Cafe/HW/Latte/Core/LatteThread.cpp @@ -175,6 +175,23 @@ int Latte_ThreadEntry() // before doing anything with game specific shaders, we need to wait for graphic packs to finish loading GraphicPack2::WaitUntilReady(); + // if legacy packs are enabled we cannot use the colorbuffer resolution optimization + LatteGPUState.allowFramebufferSizeOptimization = true; + for(auto& pack : GraphicPack2::GetActiveGraphicPacks()) + { + if(pack->AllowRendertargetSizeOptimization()) + continue; + for(auto& rule : pack->GetTextureRules()) + { + if(rule.filter_settings.width >= 0 || rule.filter_settings.height >= 0 || rule.filter_settings.depth >= 0 || + rule.overwrite_settings.width >= 0 || rule.overwrite_settings.height >= 0 || rule.overwrite_settings.depth >= 0) + { + LatteGPUState.allowFramebufferSizeOptimization = false; + cemuLog_log(LogType::Force, "Graphic pack {} prevents rendertarget size optimization.", pack->GetName()); + break; + } + } + } // load disk shader cache LatteShaderCache_Load(); // init registers From fa4ad9b8c196c0821888c0d882154d10feee673b Mon Sep 17 00:00:00 2001 From: SSimco <37044560+SSimco@users.noreply.github.com> Date: Mon, 25 Mar 2024 23:30:39 +0200 Subject: [PATCH 053/299] Gamelist: Add option to hide the icon column (#604) --- src/config/CemuConfig.cpp | 3 +++ src/config/CemuConfig.h | 2 ++ src/gui/components/wxGameList.cpp | 15 +++++++++++++-- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/config/CemuConfig.cpp b/src/config/CemuConfig.cpp index 1801759a..e4be97a7 100644 --- a/src/config/CemuConfig.cpp +++ b/src/config/CemuConfig.cpp @@ -84,6 +84,8 @@ void CemuConfig::Load(XMLConfigParser& parser) game_list_style = gamelist.get("style", 0); game_list_column_order = gamelist.get("order", ""); + show_icon_column = parser.get("show_icon_column", true); + // return default width if value in config file out of range auto loadColumnSize = [&gamelist] (const char *name, uint32 defaultWidth) { @@ -385,6 +387,7 @@ void CemuConfig::Save(XMLConfigParser& parser) psize.set("x", pad_size.x); psize.set("y", pad_size.y); config.set("pad_maximized", pad_maximized); + config.set("show_icon_column" , show_icon_column); auto gamelist = config.set("GameList"); gamelist.set("style", game_list_style); diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index eb552fce..bcaf8467 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -418,6 +418,8 @@ struct CemuConfig ConfigValue did_show_graphic_pack_download{false}; ConfigValue did_show_macos_disclaimer{false}; + ConfigValue show_icon_column{ false }; + int game_list_style = 0; std::string game_list_column_order; struct diff --git a/src/gui/components/wxGameList.cpp b/src/gui/components/wxGameList.cpp index 73bcd98d..d7c9a4f8 100644 --- a/src/gui/components/wxGameList.cpp +++ b/src/gui/components/wxGameList.cpp @@ -88,7 +88,10 @@ wxGameList::wxGameList(wxWindow* parent, wxWindowID id) const auto& config = GetConfig(); InsertColumn(ColumnHiddenName, "", wxLIST_FORMAT_LEFT, 0); - InsertColumn(ColumnIcon, "", wxLIST_FORMAT_LEFT, kListIconWidth); + if(config.show_icon_column) + InsertColumn(ColumnIcon, _("Icon"), wxLIST_FORMAT_LEFT, kListIconWidth); + else + InsertColumn(ColumnIcon, _("Icon"), wxLIST_FORMAT_LEFT, 0); InsertColumn(ColumnName, _("Game"), wxLIST_FORMAT_LEFT, config.column_width.name); InsertColumn(ColumnVersion, _("Version"), wxLIST_FORMAT_RIGHT, config.column_width.version); InsertColumn(ColumnDLC, _("DLC"), wxLIST_FORMAT_RIGHT, config.column_width.dlc); @@ -794,6 +797,7 @@ void wxGameList::OnColumnRightClick(wxListEvent& event) ResetWidth = wxID_HIGHEST + 1, ResetOrder, + ShowIcon, ShowName, ShowVersion, ShowDlc, @@ -810,6 +814,7 @@ void wxGameList::OnColumnRightClick(wxListEvent& event) menu.Append(ResetOrder, _("Reset &order")) ; menu.AppendSeparator(); + menu.AppendCheckItem(ShowIcon, _("Show &icon"))->Check(GetColumnWidth(ColumnIcon) > 0); menu.AppendCheckItem(ShowName, _("Show &name"))->Check(GetColumnWidth(ColumnName) > 0); menu.AppendCheckItem(ShowVersion, _("Show &version"))->Check(GetColumnWidth(ColumnVersion) > 0); menu.AppendCheckItem(ShowDlc, _("Show &dlc"))->Check(GetColumnWidth(ColumnDLC) > 0); @@ -828,6 +833,9 @@ void wxGameList::OnColumnRightClick(wxListEvent& event) switch (event.GetId()) { + case ShowIcon: + config.show_icon_column = menu->IsChecked(ShowIcon); + break; case ShowName: config.column_width.name = menu->IsChecked(ShowName) ? DefaultColumnSize::name : 0; break; @@ -907,7 +915,10 @@ void wxGameList::ApplyGameListColumnWidths() { const auto& config = GetConfig(); wxWindowUpdateLocker lock(this); - SetColumnWidth(ColumnIcon, kListIconWidth); + if(config.show_icon_column) + SetColumnWidth(ColumnIcon, kListIconWidth); + else + SetColumnWidth(ColumnIcon, 0); SetColumnWidth(ColumnName, config.column_width.name); SetColumnWidth(ColumnVersion, config.column_width.version); SetColumnWidth(ColumnDLC, config.column_width.dlc); From 111e383d1b0c2a27001433781e2cba123f991048 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Tue, 26 Mar 2024 13:07:08 +0100 Subject: [PATCH 054/299] coreinit: Fix race condition that causes crash (#1138) --- src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp index 8ce5de07..809d7be4 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp @@ -1159,9 +1159,11 @@ namespace coreinit #include std::vector g_schedulerThreadIds; + std::mutex g_schedulerThreadIdsLock; std::vector& OSGetSchedulerThreadIds() { + std::lock_guard schedulerThreadIdsLockGuard(g_schedulerThreadIdsLock); return g_schedulerThreadIds; } #endif @@ -1183,7 +1185,10 @@ namespace coreinit } pid_t tid = gettid(); - g_schedulerThreadIds.emplace_back(tid); + { + std::lock_guard schedulerThreadIdsLockGuard(g_schedulerThreadIdsLock); + g_schedulerThreadIds.emplace_back(tid); + } #endif t_schedulerFiber = Fiber::PrepareCurrentThread(); @@ -1238,7 +1243,10 @@ namespace coreinit sSchedulerThreads.clear(); g_schedulerThreadHandles.clear(); #if BOOST_OS_LINUX - g_schedulerThreadIds.clear(); + { + std::lock_guard schedulerThreadIdsLockGuard(g_schedulerThreadIdsLock); + g_schedulerThreadIds.clear(); + } #endif // clean up all fibers for (auto& it : g_idleLoopFiber) From 4f3d4624f5c73d5f2634b09142f4fb7ea7fba623 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Tue, 26 Mar 2024 13:09:24 +0100 Subject: [PATCH 055/299] GraphicPacksWindow: Disable update button when a game is running (#1137) --- src/gui/DownloadGraphicPacksWindow.cpp | 16 ++++++---------- src/gui/GraphicPacksWindow2.cpp | 10 ++++++++++ src/gui/GraphicPacksWindow2.h | 1 + src/gui/MainWindow.cpp | 10 ++++++++++ src/gui/MainWindow.h | 4 +++- 5 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/gui/DownloadGraphicPacksWindow.cpp b/src/gui/DownloadGraphicPacksWindow.cpp index 8189b50a..03f102d2 100644 --- a/src/gui/DownloadGraphicPacksWindow.cpp +++ b/src/gui/DownloadGraphicPacksWindow.cpp @@ -110,14 +110,6 @@ void deleteDownloadedGraphicPacks() 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; @@ -326,8 +318,6 @@ DownloadGraphicPacksWindow::DownloadGraphicPacksWindow(wxWindow* parent) m_downloadState = std::make_unique(); - - m_thread = std::thread(&DownloadGraphicPacksWindow::UpdateThread, this); } DownloadGraphicPacksWindow::~DownloadGraphicPacksWindow() @@ -344,6 +334,12 @@ const std::string& DownloadGraphicPacksWindow::GetException() const int DownloadGraphicPacksWindow::ShowModal() { + if(CafeSystem::IsTitleRunning()) + { + wxMessageBox(_("Graphic packs cannot be updated while a game is running."), _("Graphic packs"), 5, this->GetParent()); + return wxID_CANCEL; + } + m_thread = std::thread(&DownloadGraphicPacksWindow::UpdateThread, this); wxDialog::ShowModal(); return m_threadState == ThreadCanceled ? wxID_CANCEL : wxID_OK; } diff --git a/src/gui/GraphicPacksWindow2.cpp b/src/gui/GraphicPacksWindow2.cpp index 78b344d5..29f4b865 100644 --- a/src/gui/GraphicPacksWindow2.cpp +++ b/src/gui/GraphicPacksWindow2.cpp @@ -319,6 +319,7 @@ GraphicPacksWindow2::GraphicPacksWindow2(wxWindow* parent, uint64_t title_id_fil SetSizer(main_sizer); + UpdateTitleRunning(CafeSystem::IsTitleRunning()); FillGraphicPackList(); } @@ -676,6 +677,15 @@ void GraphicPacksWindow2::OnInstalledGamesChanged(wxCommandEvent& event) event.Skip(); } +void GraphicPacksWindow2::UpdateTitleRunning(bool running) +{ + m_update_graphicPacks->Enable(!running); + if(running) + m_update_graphicPacks->SetToolTip(_("Graphic packs cannot be updated while a game is running.")); + else + m_update_graphicPacks->SetToolTip(nullptr); +} + void GraphicPacksWindow2::ReloadPack(const GraphicPackPtr& graphic_pack) const { if (graphic_pack->HasShaders() || graphic_pack->HasPatches() || graphic_pack->HasCustomVSyncFrequency()) diff --git a/src/gui/GraphicPacksWindow2.h b/src/gui/GraphicPacksWindow2.h index a068f2b6..a79c62c1 100644 --- a/src/gui/GraphicPacksWindow2.h +++ b/src/gui/GraphicPacksWindow2.h @@ -21,6 +21,7 @@ public: ~GraphicPacksWindow2(); static void RefreshGraphicPacks(); + void UpdateTitleRunning(bool running); private: std::string m_filter; diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 311ddfb7..023918bd 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -625,6 +625,7 @@ bool MainWindow::FileLoad(const fs::path launchPath, wxLaunchGameEvent::INITIATE CreateCanvas(); CafeSystem::LaunchForegroundTitle(); RecreateMenu(); + UpdateChildWindowTitleRunningState(); return true; } @@ -683,6 +684,7 @@ void MainWindow::OnFileMenu(wxCommandEvent& event) RecreateMenu(); CreateGameListAndStatusBar(); DoLayout(); + UpdateChildWindowTitleRunningState(); } } @@ -2320,6 +2322,14 @@ void MainWindow::RecreateMenu() SetMenuVisible(false); } +void MainWindow::UpdateChildWindowTitleRunningState() +{ + const bool running = CafeSystem::IsTitleRunning(); + + if(m_graphic_pack_window) + m_graphic_pack_window->UpdateTitleRunning(running); +} + void MainWindow::RestoreSettingsAfterGameExited() { RecreateMenu(); diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index 88d2a1d3..7191df12 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -21,6 +21,7 @@ class DebuggerWindow2; struct GameEntry; class DiscordPresence; class TitleManager; +class GraphicPacksWindow2; class wxLaunchGameEvent; wxDECLARE_EVENT(wxEVT_LAUNCH_GAME, wxLaunchGameEvent); @@ -146,6 +147,7 @@ public: private: void RecreateMenu(); + void UpdateChildWindowTitleRunningState(); static wxString GetInitialWindowTitle(); void ShowGettingStartedDialog(); @@ -163,7 +165,7 @@ private: MemorySearcherTool* m_toolWindow = nullptr; TitleManager* m_title_manager = nullptr; PadViewFrame* m_padView = nullptr; - wxWindow* m_graphic_pack_window = nullptr; + GraphicPacksWindow2* m_graphic_pack_window = nullptr; wxTimer* m_timer; wxPoint m_mouse_position{}; From 5230fcab374b6180043b0c1397c9d51e0c99b84a Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Wed, 27 Mar 2024 11:14:01 +0100 Subject: [PATCH 056/299] Debugger: Fix infinite loop in symbol storage (#1134) --- src/Cafe/HW/Espresso/Debugger/DebugSymbolStorage.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Cafe/HW/Espresso/Debugger/DebugSymbolStorage.h b/src/Cafe/HW/Espresso/Debugger/DebugSymbolStorage.h index aba6a9b5..0a46951d 100644 --- a/src/Cafe/HW/Espresso/Debugger/DebugSymbolStorage.h +++ b/src/Cafe/HW/Espresso/Debugger/DebugSymbolStorage.h @@ -45,12 +45,17 @@ public: static void ClearRange(MPTR address, uint32 length) { + if (length == 0) + return; s_lock.lock(); - while (length > 0) + for (;;) { auto itr = s_typeStorage.find(address); if (itr != s_typeStorage.end()) s_typeStorage.erase(itr); + + if (length <= 4) + break; address += 4; length -= 4; } @@ -60,4 +65,4 @@ public: private: static FSpinlock s_lock; static std::unordered_map s_typeStorage; -}; \ No newline at end of file +}; From b0b2c257626852f749109aba053c2efcb037b0b8 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:44:51 +0100 Subject: [PATCH 057/299] coreinit: Improve accuracy of OSSwitchCoroutine Fixes Injustice: Gods Among Us crashing during boot. --- .../OS/libs/coreinit/coreinit_Coroutine.cpp | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Coroutine.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Coroutine.cpp index b1f4abb7..49c46831 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Coroutine.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Coroutine.cpp @@ -3,9 +3,13 @@ #include "Cafe/HW/Espresso/PPCState.h" #include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" #include "Cafe/HW/MMU/MMU.h" +#include "Cafe/OS/RPL/rpl.h" namespace coreinit { + static_assert(sizeof(OSCoroutine) == 0x180); + + static uint32 s_PPCAddrOSSwitchCoroutineAfterOSLoadCoroutine = 0; void coreinitExport_OSInitCoroutine(PPCInterpreter_t* hCPU) { @@ -57,14 +61,30 @@ namespace coreinit void coreinitExport_OSSwitchCoroutine(PPCInterpreter_t* hCPU) { - OSCoroutine* coroutineCurrent = (OSCoroutine*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); - OSCoroutine* coroutineNext = (OSCoroutine*)memory_getPointerFromVirtualOffsetAllowNull(hCPU->gpr[4]); + // OSSwitchCoroutine is a wrapper for OSSaveCoroutine + OSLoadCoroutine but it has side effects that we need to care about: + // r31 is saved and restored via the stack in OSSwitchCoroutine + // r4 is stored in the r31 field of coroutineCurrent. Injustice: Gods Among Us reads the r31 field and expects it to match coroutineCurrent (0x027183D4 @ EU v16) + OSCoroutine* coroutineCurrent = MEMPTR(hCPU->gpr[3]); + OSCoroutine* coroutineNext = MEMPTR(hCPU->gpr[4]); + hCPU->gpr[1] -= 0x10; + memory_writeU32(hCPU->gpr[1]+0xC, hCPU->gpr[31]); + memory_writeU32(hCPU->gpr[1]+0x14, hCPU->spr.LR); + hCPU->spr.LR = s_PPCAddrOSSwitchCoroutineAfterOSLoadCoroutine; + hCPU->gpr[31] = hCPU->gpr[4]; coreinitCoroutine_OSSaveCoroutine(coroutineCurrent, hCPU); - if (coroutineNext != NULL) - { - coreinitCoroutine_OSLoadCoroutine(coroutineNext, hCPU); - } - osLib_returnFromFunction(hCPU, 0); + hCPU->gpr[3] = hCPU->gpr[31]; + hCPU->gpr[4] = 1; + coreinitCoroutine_OSLoadCoroutine(coroutineNext, hCPU); + hCPU->instructionPointer = hCPU->spr.LR; + } + + void coreinitExport_OSSwitchCoroutineAfterOSLoadCoroutine(PPCInterpreter_t* hCPU) + { + // resuming after OSSaveCoroutine + hCPU->gpr[31] = memory_readU32(hCPU->gpr[1]+0xC); + hCPU->spr.LR = memory_readU32(hCPU->gpr[1]+0x14); + hCPU->gpr[1] += 0x10; + hCPU->instructionPointer = hCPU->spr.LR; } void coreinitExport_OSSwitchFiberEx(PPCInterpreter_t* hCPU) @@ -96,5 +116,7 @@ namespace coreinit osLib_addFunction("coreinit", "OSInitCoroutine", coreinitExport_OSInitCoroutine); osLib_addFunction("coreinit", "OSSwitchCoroutine", coreinitExport_OSSwitchCoroutine); osLib_addFunction("coreinit", "OSSwitchFiberEx", coreinitExport_OSSwitchFiberEx); + + s_PPCAddrOSSwitchCoroutineAfterOSLoadCoroutine = RPLLoader_MakePPCCallable(coreinitExport_OSSwitchCoroutineAfterOSLoadCoroutine); } } \ No newline at end of file From 60adc382056d4272d38673e8045e11f2bca2338d Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 27 Mar 2024 15:59:13 +0100 Subject: [PATCH 058/299] Latte: Add support for more fence conditions MEM_OP_GREATER is required by Injustice: Gods Among Us --- .../HW/Latte/Core/LatteCommandProcessor.cpp | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp index c928f89f..167911b6 100644 --- a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp +++ b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp @@ -475,18 +475,45 @@ LatteCMDPtr LatteCP_itWaitRegMem(LatteCMDPtr cmd, uint32 nWords) { uint32 fenceMemValue = _swapEndianU32(*fencePtr); fenceMemValue &= fenceMask; - if (compareOp == GPU7_WAIT_MEM_OP_GEQUAL) + if (compareOp == GPU7_WAIT_MEM_OP_LESS) { - // greater or equal - if (fenceMemValue >= fenceValue) + if (fenceMemValue < fenceValue) + break; + } + else if (compareOp == GPU7_WAIT_MEM_OP_LEQUAL) + { + if (fenceMemValue <= fenceValue) break; } else if (compareOp == GPU7_WAIT_MEM_OP_EQUAL) { - // equal if (fenceMemValue == fenceValue) break; } + else if (compareOp == GPU7_WAIT_MEM_OP_NOTEQUAL) + { + if (fenceMemValue != fenceValue) + break; + } + else if (compareOp == GPU7_WAIT_MEM_OP_GEQUAL) + { + if (fenceMemValue >= fenceValue) + break; + } + else if (compareOp == GPU7_WAIT_MEM_OP_GREATER) + { + if (fenceMemValue > fenceValue) + break; + } + else if (compareOp == GPU7_WAIT_MEM_OP_ALWAYS) + { + break; + } + else if (compareOp == GPU7_WAIT_MEM_OP_NEVER) + { + cemuLog_logOnce(LogType::Force, "Latte: WAIT_MEM_OP_NEVER encountered"); + break; + } else assert_dbg(); if (!stalls) From fa8bab2f3978d1fedeffa4c578a876e4c624206b Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 27 Mar 2024 16:01:44 +0100 Subject: [PATCH 059/299] Latte: Add support for LOOP_START_NO_AL shader instruction This instruction is used by Injustice: Gods Among Us and Project Zero Also improved robustness of rendering to be less prone to crashing when a game tries to draw with broken shaders --- src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp | 2 +- src/Cafe/HW/Latte/Core/LatteShaderAssembly.h | 2 +- .../HW/Latte/LegacyShaderDecompiler/LatteDecompiler.cpp | 6 ++++-- .../LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp | 9 ++++++--- .../LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp | 3 ++- src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp | 4 ++-- 6 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp index f165e257..5b9fc349 100644 --- a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp +++ b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp @@ -340,7 +340,7 @@ uint8 LatteMRT::GetActiveColorBufferMask(const LatteDecompilerShader* pixelShade return 0; cemu_assert_debug(colorControlReg.get_DEGAMMA_ENABLE() == false); // not supported // combine color buffer mask with pixel output mask from pixel shader - colorBufferMask &= pixelShader->pixelColorOutputMask; + colorBufferMask &= (pixelShader ? pixelShader->pixelColorOutputMask : 0); // combine color buffer mask with color channel mask from mmCB_TARGET_MASK (disable render buffer if all colors are blocked) uint32 channelTargetMask = lcr.CB_TARGET_MASK.get_MASK(); for (uint32 i = 0; i < 8; i++) diff --git a/src/Cafe/HW/Latte/Core/LatteShaderAssembly.h b/src/Cafe/HW/Latte/Core/LatteShaderAssembly.h index df636689..d2314a53 100644 --- a/src/Cafe/HW/Latte/Core/LatteShaderAssembly.h +++ b/src/Cafe/HW/Latte/Core/LatteShaderAssembly.h @@ -12,7 +12,7 @@ #define GPU7_CF_INST_VTX (0x02) // used only in GS copy program? #define GPU7_CF_INST_LOOP_END (0x05) #define GPU7_CF_INST_LOOP_START_DX10 (0x06) -#define GPU7_CF_INST_LOOP_START_NO_AL (0x07) // (Seen in Project Zero) +#define GPU7_CF_INST_LOOP_START_NO_AL (0x07) // (Seen in Project Zero, Injustice: Gods Among Us) #define GPU7_CF_INST_LOOP_BREAK (0x09) #define GPU7_CF_INST_JUMP (0x0A) diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.cpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.cpp index cf88b901..c3f7c19e 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.cpp +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.cpp @@ -101,7 +101,8 @@ bool LatteDecompiler_ParseCFInstruction(LatteDecompilerShaderContext* shaderCont // ignored (we use ALU/IF/ELSE/PUSH/POP clauses to determine code flow) return true; } - else if (cf_inst23_7 == GPU7_CF_INST_LOOP_START_DX10 || cf_inst23_7 == GPU7_CF_INST_LOOP_END) + else if (cf_inst23_7 == GPU7_CF_INST_LOOP_START_DX10 || cf_inst23_7 == GPU7_CF_INST_LOOP_END || + cf_inst23_7 == GPU7_CF_INST_LOOP_START_NO_AL) { LatteDecompilerCFInstruction& cfInstruction = instructionList.emplace_back(); // set type and address @@ -966,7 +967,8 @@ void LatteDecompiler_ParseClauses(LatteDecompilerShaderContext* decompilerContex { // no sub-instructions } - else if (cfInstruction.type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction.type == GPU7_CF_INST_LOOP_END) + else if (cfInstruction.type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction.type == GPU7_CF_INST_LOOP_END || + cfInstruction.type == GPU7_CF_INST_LOOP_START_NO_AL) { // no sub-instructions } diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp index cf22f05d..19604e0c 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp @@ -441,7 +441,8 @@ void LatteDecompiler_analyzeSubroutine(LatteDecompilerShaderContext* shaderConte { shaderContext->analyzer.modifiesPixelActiveState = true; } - else if (cfInstruction.type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction.type == GPU7_CF_INST_LOOP_END) + else if (cfInstruction.type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction.type == GPU7_CF_INST_LOOP_END || + cfInstruction.type == GPU7_CF_INST_LOOP_START_NO_AL) { shaderContext->analyzer.modifiesPixelActiveState = true; } @@ -685,7 +686,8 @@ void LatteDecompiler_analyze(LatteDecompilerShaderContext* shaderContext, LatteD { shaderContext->analyzer.modifiesPixelActiveState = true; } - else if (cfInstruction.type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction.type == GPU7_CF_INST_LOOP_END) + else if (cfInstruction.type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction.type == GPU7_CF_INST_LOOP_END || + cfInstruction.type == GPU7_CF_INST_LOOP_START_NO_AL) { shaderContext->analyzer.modifiesPixelActiveState = true; shaderContext->analyzer.hasLoops = true; @@ -929,7 +931,8 @@ void LatteDecompiler_analyze(LatteDecompilerShaderContext* shaderContext, LatteD if (cfCurrentStackDepth < 0) debugBreakpoint(); } - else if (cfInstruction.type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction.type == GPU7_CF_INST_LOOP_END) + else if (cfInstruction.type == GPU7_CF_INST_LOOP_START_DX10 || cfInstruction.type == GPU7_CF_INST_LOOP_END || + cfInstruction.type == GPU7_CF_INST_LOOP_START_NO_AL) { // no effect on stack depth cfInstruction.activeStackDepth = cfCurrentStackDepth; diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp index e19535be..7a6605f8 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp @@ -3662,7 +3662,8 @@ void LatteDecompiler_emitClauseCode(LatteDecompilerShaderContext* shaderContext, { src->addFmt("{} = {} == true && {} == true;" _CRLF, _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth + 1 - cfInstruction->popCount), _getActiveMaskVarName(shaderContext, cfInstruction->activeStackDepth - cfInstruction->popCount), _getActiveMaskCVarName(shaderContext, cfInstruction->activeStackDepth - cfInstruction->popCount)); } - else if( cfInstruction->type == GPU7_CF_INST_LOOP_START_DX10 ) + else if( cfInstruction->type == GPU7_CF_INST_LOOP_START_DX10 || + cfInstruction->type == GPU7_CF_INST_LOOP_START_NO_AL) { // start of loop // if pixel is disabled, then skip loop diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp index d510140b..6500f7d3 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp @@ -1285,9 +1285,9 @@ void VulkanRenderer::draw_beginSequence() // update shader state LatteSHRC_UpdateActiveShaders(); - if (m_state.drawSequenceSkip) + if (LatteGPUState.activeShaderHasError) { - debug_printf("Skipping drawcalls due to shader error\n"); + cemuLog_logDebugOnce(LogType::Force, "Skipping drawcalls due to shader error"); m_state.drawSequenceSkip = true; cemu_assert_debug(false); return; From 3e467e220e4e407752381f264f405c50eaec1036 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 3 Apr 2024 01:45:50 +0200 Subject: [PATCH 060/299] Logging: Prevent crash for nullptr strings --- src/Cafe/OS/common/OSUtil.h | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Cafe/OS/common/OSUtil.h b/src/Cafe/OS/common/OSUtil.h index 6801f6af..84e38a6c 100644 --- a/src/Cafe/OS/common/OSUtil.h +++ b/src/Cafe/OS/common/OSUtil.h @@ -140,6 +140,17 @@ static std::tuple cafeExportBuildArgTuple(PPCInterpreter_t* hCPU, R(fn) return std::tuple{ cafeExportGetParamWrapper(hCPU, gprIndex, fprIndex)... }; } +template +T cafeExportGetFormatParamWrapper(PPCInterpreter_t* hCPU, int& gprIndex, int& fprIndex) +{ + T v; + cafeExportParamWrapper::getParamWrapper(hCPU, gprIndex, fprIndex, v); + // if T is char* or const char*, return "null" instead of nullptr since newer fmtlib would throw otherwise + if constexpr (std::is_same_v || std::is_same_v) + return v ? v : (T)"null"; + return v; +} + template using _CAFE_FORMAT_ARG = std::conditional_t, std::conditional_t || std::is_same_v, T, MEMPTR>, T>; @@ -150,7 +161,7 @@ static auto cafeExportBuildFormatTuple(PPCInterpreter_t* hCPU, R(fn)(Args...)) int gprIndex = 0; int fprIndex = 0; return std::tuple<_CAFE_FORMAT_ARG...>{ - cafeExportGetParamWrapper<_CAFE_FORMAT_ARG>(hCPU, gprIndex, fprIndex)... + cafeExportGetFormatParamWrapper<_CAFE_FORMAT_ARG>(hCPU, gprIndex, fprIndex)... }; } From 51072b510c74e078350f70f31386802f2e78159f Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 3 Apr 2024 01:45:05 +0200 Subject: [PATCH 061/299] nn_boss: Large rework with various improvements Lots of internal changes. On the surface this only fixes a crash in Mario & Sonic Rio 2016 (at least what I saw from my testing) but it may affect more games. Summary of changes: - Rewrite code to use newer cafeExportRegisterFunc - Simplify code by merging namespaces and structs of the same types - Correctly set ppc vtables for the virtual boss classes - Fix some wrong function definitions and implement a little bit more of the boss API (mainly constructors and destructors) --- src/Cafe/OS/libs/nn_boss/nn_boss.cpp | 2710 +++++++++++++------------- src/Cemu/Logging/CemuLogging.cpp | 1 + src/Cemu/Logging/CemuLogging.h | 1 + src/gui/MainWindow.cpp | 1 + 4 files changed, 1325 insertions(+), 1388 deletions(-) diff --git a/src/Cafe/OS/libs/nn_boss/nn_boss.cpp b/src/Cafe/OS/libs/nn_boss/nn_boss.cpp index c2d65a5f..f53a6d79 100644 --- a/src/Cafe/OS/libs/nn_boss/nn_boss.cpp +++ b/src/Cafe/OS/libs/nn_boss/nn_boss.cpp @@ -27,13 +27,244 @@ bossBufferVector->buffer = (uint8*)bossRequest; sint32 g_initCounter = 0; bool g_isInitialized = false; - void freeMem(void* mem) + struct VTableEntry { - if(mem) - coreinit::default_MEMFreeToDefaultHeap((uint8*)mem - 8); + uint16be offsetA{0}; + uint16be offsetB{0}; + MEMPTR ptr; + }; + static_assert(sizeof(VTableEntry) == 8); + + #define DTOR_WRAPPER(__TYPE) RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) { dtor(MEMPTR<__TYPE>(hCPU->gpr[3]), hCPU->gpr[4]); osLib_returnFromFunction(hCPU, 0); }) + + constexpr uint32 BOSS_MEM_MAGIC = 0xCAFE4321; + + template + MEMPTR boss_new() + { + uint32 objSize = sizeof(T); + uint32be* basePtr = (uint32be*)coreinit::_weak_MEMAllocFromDefaultHeapEx(objSize + 8, 0x8); + basePtr[0] = BOSS_MEM_MAGIC; + basePtr[1] = objSize; + return (T*)(basePtr+2); } - struct TaskSetting_t + void boss_delete(MEMPTR mem) + { + if(!mem) + return; + uint32be* basePtr = (uint32be*)mem.GetPtr() - 2; + if(basePtr[0] != BOSS_MEM_MAGIC) + { + cemuLog_log(LogType::Force, "nn_boss: Detected memory corruption"); + cemu_assert_suspicious(); + } + coreinit::_weak_MEMFreeToDefaultHeap(basePtr); + } + + Result Initialize() // Initialize__Q2_2nn4bossFv + { + coreinit::OSLockMutex(&g_mutex); + Result result = 0; + if(g_initCounter == 0) + { + g_isInitialized = true; + // IPC init here etc. + result = 0x200080; // init result + } + g_initCounter++; + coreinit::OSUnlockMutex(&g_mutex); + return NN_RESULT_IS_SUCCESS(result) ? 0 : result; + } + + uint32 IsInitialized() // IsInitialized__Q2_2nn4bossFv + { + return g_isInitialized; + } + + void Finalize() // Finalize__Q2_2nn4bossFv + { + coreinit::OSLockMutex(&g_mutex); + if(g_initCounter == 0) + cemuLog_log(LogType::Force, "nn_boss: Finalize() called without corresponding Initialize()"); + if(g_initCounter == 1) + { + g_isInitialized = false; + // IPC deinit here etc. + } + g_initCounter--; + coreinit::OSUnlockMutex(&g_mutex); + } + + uint32 GetBossState(PPCInterpreter_t* hCPU) + { + cemuLog_logDebug(LogType::Force, "nn_boss.GetBossState() - stub"); + return 7; + } + + struct TitleId + { + uint64be u64{}; + + static TitleId* ctor(TitleId* _thisptr, uint64 titleId) + { + if (!_thisptr) + _thisptr = boss_new(); + _thisptr->u64 = titleId; + return _thisptr; + } + + static TitleId* ctor(TitleId* _thisptr) + { + return ctor(_thisptr, 0); + } + + static bool IsValid(TitleId* _thisptr) + { + return _thisptr->u64 != 0; + } + + static TitleId* ctor1(TitleId* _thisptr, uint32 filler, uint64 titleId) + { + return ctor(_thisptr); + } + + static TitleId* ctor2(TitleId* _thisptr, uint32 filler, uint64 titleId) + { + cemuLog_logDebug(LogType::Force, "nn_boss_TitleId_ctor2(0x{:x})", MEMPTR(_thisptr).GetMPTR()); + if (!_thisptr) + { + // _thisptr = new Task_t + assert_dbg(); + } + + _thisptr->u64 = titleId; + return _thisptr; + } + + static TitleId* ctor3(TitleId* _thisptr, TitleId* titleId) + { + cemuLog_logDebug(LogType::Force, "nn_boss_TitleId_cctor(0x{:x})", MEMPTR(_thisptr).GetMPTR()); + if (!_thisptr) + _thisptr = boss_new(); + _thisptr->u64 = titleId->u64; + return _thisptr; + } + + static bool operator_ne(TitleId* _thisptr, TitleId* titleId) + { + cemuLog_logDebug(LogType::Force, "nn_boss_TitleId_operator_ne(0x{:x})", MEMPTR(_thisptr).GetMPTR()); + return _thisptr->u64 != titleId->u64; + } + }; + static_assert(sizeof(TitleId) == 8); + + struct TaskId + { + char id[0x8]{}; + + static TaskId* ctor(TaskId* _thisptr) + { + if(!_thisptr) + _thisptr = boss_new(); + _thisptr->id[0] = '\0'; + return _thisptr; + } + }; + static_assert(sizeof(TaskId) == 8); + + struct Title + { + uint32be accountId{}; // 0x00 + TitleId titleId{}; // 0x8 + MEMPTR vTablePtr{}; // 0x10 + + struct VTable + { + VTableEntry rtti; + VTableEntry dtor; + }; + static inline SysAllocator s_titleVTable; + + static Title* ctor(Title* _this) + { + if (!_this) + _this = boss_new(); + *_this = {}; + _this->vTablePtr = s_titleVTable; + return _this; + } + + static void dtor(Title* _this, uint32 options) + { + if (_this && (options & 1)) + boss_delete(_this); + } + + static void InitVTable() + { + s_titleVTable->rtti.ptr = nullptr; // todo + s_titleVTable->dtor.ptr = DTOR_WRAPPER(Title); + } + }; + static_assert(sizeof(Title) == 0x18); + + struct DirectoryName + { + char name[0x8]{}; + + static DirectoryName* ctor(DirectoryName* _thisptr) + { + if (!_thisptr) + _thisptr = boss_new<DirectoryName>(); + memset(_thisptr->name, 0x00, 0x8); + return _thisptr; + } + + static const char* operator_const_char(DirectoryName* _thisptr) + { + return _thisptr->name; + } + }; + static_assert(sizeof(DirectoryName) == 8); + + struct BossAccount // the actual class name is "Account" and while the boss namespace helps us separate this from Account(.h) we use an alternative name to avoid confusion + { + struct VTable + { + VTableEntry rtti; + VTableEntry dtor; + }; + static inline SysAllocator<VTable> s_VTable; + + uint32be accountId; + MEMPTR<void> vTablePtr; + + static BossAccount* ctor(BossAccount* _this, uint32 accountId) + { + if (!_this) + _this = boss_new<BossAccount>(); + _this->accountId = accountId; + _this->vTablePtr = s_VTable; + return _this; + } + + static void dtor(BossAccount* _this, uint32 options) + { + if(_this && options & 1) + boss_delete(_this); + } + + static void InitVTable() + { + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(BossAccount); + } + + }; + static_assert(sizeof(BossAccount) == 8); + + struct TaskSetting { static const uint32 kBossCode = 0x7C0; static const uint32 kBossCodeLen = 0x20; @@ -46,8 +277,6 @@ bossBufferVector->buffer = (uint8*)bossRequest; static const uint32 kNbdlFileName = 0x7F8; static const uint32 kFileNameLen = 0x20; - - static const uint32 kURL = 0x48; static const uint32 kURLLen = 0x100; @@ -57,245 +286,75 @@ bossBufferVector->buffer = (uint8*)bossRequest; static const uint32 kServiceToken = 0x590; static const uint32 kServiceTokenLen = 0x200; - uint8 settings[0x1000]; - uint32be uknExt_vTableProbably; // +0x1000 - }; - static_assert(sizeof(TaskSetting_t) == 0x1004); - static_assert(offsetof(TaskSetting_t, uknExt_vTableProbably) == 0x1000, "offsetof(TaskSetting_t, uknExt)"); + MEMPTR<void> vTablePtr; // +0x1000 - struct NetTaskSetting_t : TaskSetting_t + struct VTableTaskSetting + { + VTableEntry rtti; + VTableEntry dtor; + VTableEntry RegisterPreprocess; + VTableEntry unk1; + }; + static inline SysAllocator<VTableTaskSetting> s_VTable; + + static TaskSetting* ctor(TaskSetting* _thisptr) + { + if(!_thisptr) + _thisptr = boss_new<TaskSetting>(); + _thisptr->vTablePtr = s_VTable; + InitializeSetting(_thisptr); + return _thisptr; + } + + static void dtor(TaskSetting* _this, uint32 options) + { + cemuLog_logDebug(LogType::Force, "nn::boss::TaskSetting::dtor(0x{:08x}, 0x{:08x})", MEMPTR(_this).GetMPTR(), options); + if(options & 1) + boss_delete(_this); + } + + static bool IsPrivileged(TaskSetting* _thisptr) + { + const uint16 value = *(uint16be*)&_thisptr->settings[0x28]; + return value == 1 || value == 9 || value == 5; + } + + static void InitializeSetting(TaskSetting* _thisptr) + { + memset(_thisptr, 0x00, sizeof(TaskSetting::settings)); + *(uint32*)&_thisptr->settings[0x0C] = 0; + *(uint8*)&_thisptr->settings[0x2A] = 0x7D; // timeout? + *(uint32*)&_thisptr->settings[0x30] = 0x7080; + *(uint32*)&_thisptr->settings[0x8] = 0; + *(uint32*)&_thisptr->settings[0x38] = 0; + *(uint32*)&_thisptr->settings[0x3C] = 0x76A700; + *(uint32*)&_thisptr->settings[0] = 0x76A700; + } + + static void InitVTable() + { + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(TaskSetting); + s_VTable->RegisterPreprocess.ptr = nullptr; // todo + s_VTable->unk1.ptr = nullptr; // todo + } + }; + static_assert(sizeof(TaskSetting) == 0x1004); + static_assert(offsetof(TaskSetting, vTablePtr) == 0x1000); + + struct NetTaskSetting : TaskSetting { // 0x188 cert1 + 0x188 cert2 + 0x188 cert3 // 0x190 AddCaCert (3times) char cert[0x80]; // SetConnectionSetting // SetFirstLastModifiedTime - }; - static_assert(sizeof(NetTaskSetting_t) == 0x1004); - struct NbdlTaskSetting_t : NetTaskSetting_t - { - //char fileName[0x20]; // @0x7F8 - }; - static_assert(sizeof(NbdlTaskSetting_t) == 0x1004); + struct VTableNetTaskSetting : public VTableTaskSetting + { }; + static inline SysAllocator<VTableNetTaskSetting> s_VTable; - struct RawUlTaskSetting_t : NetTaskSetting_t - { - static const uint32 kType = 0x12340000; - uint32be ukRaw1; // 0x1004 - uint32be ukRaw2; // 0x1008 - uint32be ukRaw3; // 0x100C - uint8 rawSpace[0x200]; // 0x1010 - }; - static_assert(sizeof(RawUlTaskSetting_t) == 0x1210); - - struct PlayReportSetting_t : RawUlTaskSetting_t - { - static const uint32 kType = 0x12340001; - MEMPTR<void*> ukPlay1; // 0x1210 - uint32be ukPlay2; // 0x1214 - uint32be ukPlay3; // 0x1218 - uint32be ukPlay4; // 0x121C - }; - static_assert(sizeof(PlayReportSetting_t) == 0x1220); - - struct RawDlTaskSetting_t : NetTaskSetting_t - { - static const uint32 kType = 0x12340002; - //char fileName[0x20]; // 0x7F8 - }; - static_assert(sizeof(RawDlTaskSetting_t) == 0x1004); - - struct TitleId_t - { - uint64be u64{}; - }; - static_assert(sizeof(TitleId_t) == 8); - - struct TaskId_t - { - char id[0x8]{}; - }; - static_assert(sizeof(TaskId_t) == 8); - - struct Title_t - { - uint32be accountId{}; // 0x00 - TitleId_t titleId{}; // 0x8 - uint32be someValue = 0x12341000; // 0x10 - }; - static_assert(sizeof(Title_t) == 0x18); - - struct DirectoryName_t - { - char name[0x8]{}; - }; - static_assert(sizeof(DirectoryName_t) == 8); - - struct Account_t - { - uint32be accountId; - uint32be uk1; // global struct - }; - static_assert(sizeof(Account_t) == 8); - - struct Task_t - { - uint32be accountId; // 0x00 - uint32be uk2; // 0x04 - TaskId_t taskId; // 0x08 - TitleId_t titleId; // 0x10 - uint32be ext; // 0x18 - uint32be padding; // 0x1C - }; - static_assert(sizeof(Task_t) == 0x20, "sizeof(Task_t)"); - - namespace TaskId - { - TaskId_t* ctor(TaskId_t* thisptr) - { - if(!thisptr) - { - // thisptr = new TaskId_t - assert_dbg(); - } - - if(thisptr) - { - thisptr->id[0] = 0; - } - - return thisptr; - } - } - - namespace Account - { - Account_t* ctor(Account_t* thisptr, uint32 accountId) - { - if (!thisptr) - { - // thisptr = new TaskId_t - assert_dbg(); - } - - thisptr->accountId = accountId; - thisptr->uk1 = 0x12340010; - return thisptr; - } - } - - namespace TitleId - { - TitleId_t* ctor(TitleId_t* thisptr, uint64 titleId) - { - if (!thisptr) - { - // thisptr = new TaskId_t - assert_dbg(); - } - - if (thisptr) - { - thisptr->u64 = titleId; - } - - return thisptr; - } - - TitleId_t* ctor(TitleId_t* thisptr) - { - return ctor(thisptr, 0); - } - - bool IsValid(TitleId_t* thisptr) - { - return thisptr->u64 != 0; - } - - TitleId_t* ctor1(TitleId_t* thisptr, uint32 filler, uint64 titleId) - { - return ctor(thisptr); - } - - TitleId_t* ctor2(TitleId_t* thisptr, uint32 filler, uint64 titleId) - { - cemuLog_logDebug(LogType::Force, "nn_boss_TitleId_ctor2(0x{:x})", MEMPTR(thisptr).GetMPTR()); - if (!thisptr) - { - // thisptr = new Task_t - assert_dbg(); - } - - thisptr->u64 = titleId; - return thisptr; - } - - - TitleId_t* cctor(TitleId_t* thisptr, TitleId_t* titleId) - { - cemuLog_logDebug(LogType::Force, "nn_boss_TitleId_cctor(0x{:x})", MEMPTR(thisptr).GetMPTR()); - if (!thisptr) - { - // thisptr = new Task_t - assert_dbg(); - } - - thisptr->u64 = titleId->u64; - - return thisptr; - } - - bool operator_ne(TitleId_t* thisptr, TitleId_t* titleId) - { - cemuLog_logDebug(LogType::Force, "nn_boss_TitleId_operator_ne(0x{:x})", MEMPTR(thisptr).GetMPTR()); - return thisptr->u64 != titleId->u64; - } - } - - namespace TaskSetting - { - bool IsPrivilegedTaskSetting(TaskSetting_t* thisptr) - { - const uint16 value = *(uint16*)&thisptr->settings[0x28]; - return value == 1 || value == 9 || value == 5; - } - - void InitializeSetting(TaskSetting_t* thisptr) - { - memset(thisptr, 0x00, sizeof(TaskSetting_t::settings)); - *(uint32*)&thisptr->settings[0x0C] = 0; - *(uint8*)&thisptr->settings[0x2A] = 0x7D; // timeout? - *(uint32*)&thisptr->settings[0x30] = 0x7080; - *(uint32*)&thisptr->settings[0x8] = 0; - *(uint32*)&thisptr->settings[0x38] = 0; - *(uint32*)&thisptr->settings[0x3C] = 0x76A700; - *(uint32*)&thisptr->settings[0] = 0x76A700; - } - - TaskSetting_t* ctor(TaskSetting_t* thisptr) - { - if(!thisptr) - { - // thisptr = new TaskSetting_t - assert_dbg(); - } - - if (thisptr) - { - thisptr->uknExt_vTableProbably = 0; - InitializeSetting(thisptr); - } - - return thisptr; - } - - - } - - namespace NetTaskSetting - { - Result AddCaCert(NetTaskSetting_t* thisptr, const char* name) + static Result AddCaCert(NetTaskSetting* _thisptr, const char* name) { if(name == nullptr || strnlen(name, 0x80) == 0x80) { @@ -308,1505 +367,1380 @@ bossBufferVector->buffer = (uint8*)bossRequest; return 0xA0220D00; } - NetTaskSetting_t* ctor(NetTaskSetting_t* thisptr) + static NetTaskSetting* ctor(NetTaskSetting* _thisptr) { - if (!thisptr) - { - // thisptr = new NetTaskSetting_t - assert_dbg(); - } - - if (thisptr) - { - TaskSetting::ctor(thisptr); - *(uint32*)&thisptr->settings[0x18C] = 0x78; - thisptr->uknExt_vTableProbably = 0; - } - - return thisptr; + if (!_thisptr) + _thisptr = boss_new<NetTaskSetting>(); + TaskSetting::ctor(_thisptr); + *(uint32*)&_thisptr->settings[0x18C] = 0x78; + _thisptr->vTablePtr = s_VTable; + return _thisptr; } - Result SetServiceToken(NetTaskSetting_t* thisptr, const uint8* serviceToken) + static Result SetServiceToken(NetTaskSetting* _thisptr, const uint8* serviceToken) { - cemuLog_logDebug(LogType::Force, "nn_boss_NetTaskSetting_SetServiceToken(0x{:x}, 0x{:x})", MEMPTR(thisptr).GetMPTR(), MEMPTR(serviceToken).GetMPTR()); + cemuLog_logDebug(LogType::Force, "nn_boss_NetTaskSetting_SetServiceToken(0x{:x}, 0x{:x})", MEMPTR(_thisptr).GetMPTR(), MEMPTR(serviceToken).GetMPTR()); cemuLog_logDebug(LogType::Force, "\t->{}", fmt::ptr(serviceToken)); - memcpy(&thisptr->settings[TaskSetting_t::kServiceToken], serviceToken, TaskSetting_t::kServiceTokenLen); + memcpy(&_thisptr->settings[TaskSetting::kServiceToken], serviceToken, TaskSetting::kServiceTokenLen); return 0x200080; } - Result AddInternalCaCert(NetTaskSetting_t* thisptr, char certId) + static Result AddInternalCaCert(NetTaskSetting* _thisptr, char certId) { - cemuLog_logDebug(LogType::Force, "nn_boss_NetTaskSetting_AddInternalCaCert(0x{:x}, 0x{:x})", MEMPTR(thisptr).GetMPTR(), (int)certId); + cemuLog_logDebug(LogType::Force, "nn_boss_NetTaskSetting_AddInternalCaCert(0x{:x}, 0x{:x})", MEMPTR(_thisptr).GetMPTR(), (int)certId); - uint32 location = TaskSetting_t::kCACert; + uint32 location = TaskSetting::kCACert; for(int i = 0; i < 3; ++i) { - if(thisptr->settings[location] == 0) + if(_thisptr->settings[location] == 0) { - thisptr->settings[location] = (uint8)certId; + _thisptr->settings[location] = (uint8)certId; return 0x200080; } - location += TaskSetting_t::kCACert; + location += TaskSetting::kCACert; } - + cemuLog_logDebug(LogType::Force, "nn_boss_NetTaskSetting_AddInternalCaCert: can't store certificate"); return 0xA0220D00; } - void SetInternalClientCert(NetTaskSetting_t* thisptr, char certId) + static void SetInternalClientCert(NetTaskSetting* _thisptr, char certId) { - cemuLog_logDebug(LogType::Force, "nn_boss_NetTaskSetting_SetInternalClientCert(0x{:x}, 0x{:x})", MEMPTR(thisptr).GetMPTR(), (int)certId); - thisptr->settings[TaskSetting_t::kClientCert] = (uint8)certId; + cemuLog_logDebug(LogType::Force, "nn_boss_NetTaskSetting_SetInternalClientCert(0x{:x}, 0x{:x})", MEMPTR(_thisptr).GetMPTR(), (int)certId); + _thisptr->settings[TaskSetting::kClientCert] = (uint8)certId; } - } - namespace NbdlTaskSetting // : NetTaskSetting + static void InitVTable() + { + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(NetTaskSetting); + s_VTable->RegisterPreprocess.ptr = nullptr; // todo + s_VTable->unk1.ptr = nullptr; // todo + } + }; + static_assert(sizeof(NetTaskSetting) == 0x1004); + + struct NbdlTaskSetting : NetTaskSetting { - NbdlTaskSetting_t* ctor(NbdlTaskSetting_t* thisptr) + struct VTableNbdlTaskSetting : public VTableNetTaskSetting { - if (!thisptr) - { - // thisptr = new NbdlTaskSetting_t - assert_dbg(); - } + VTableEntry rttiNetTaskSetting; // unknown + }; + static_assert(sizeof(VTableNbdlTaskSetting) == 8*5); + static inline SysAllocator<VTableNbdlTaskSetting> s_VTable; - if (thisptr) - { - NetTaskSetting::ctor(thisptr); - thisptr->uknExt_vTableProbably = 0; - } - - return thisptr; + static NbdlTaskSetting* ctor(NbdlTaskSetting* _thisptr) + { + if (!_thisptr) + _thisptr = boss_new<NbdlTaskSetting>(); + NetTaskSetting::ctor(_thisptr); + _thisptr->vTablePtr = s_VTable; + return _thisptr; } - void export_ctor(PPCInterpreter_t* hCPU) + static Result Initialize(NbdlTaskSetting* _thisptr, const char* bossCode, uint64 directorySizeLimit, const char* directoryName) // Initialize__Q3_2nn4boss15NbdlTaskSettingFPCcLT1 { - ppcDefineParamMEMPTR(thisptr, NbdlTaskSetting_t, 0); - cemuLog_logDebug(LogType::Force, "nn_boss_NbdlTaskSetting_ctor"); - ctor(thisptr.GetPtr()); - osLib_returnFromFunction(hCPU, thisptr.GetMPTR()); - } - - void export_Initialize(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(thisptr, NbdlTaskSetting_t, 0); - ppcDefineParamMEMPTR(bossCode, const char, 1); - ppcDefineParamU64(directorySizeLimit, 2); - ppcDefineParamMEMPTR(directoryName, const char, 4); - cemuLog_logDebug(LogType::Force, "nn_boss_NbdlTaskSetting_Initialize(0x{:08x}, {}, 0x{:x}, 0x{:08x})", thisptr.GetMPTR(), bossCode.GetPtr(), directorySizeLimit, directoryName.GetMPTR()); - - if(!bossCode || strnlen(bossCode.GetPtr(), 0x20) == 0x20) - { - osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_BOSS, 0x3780)); - return; - } - - if (directoryName && strnlen(directoryName.GetPtr(), 0x8) == 0x8) - { - osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_BOSS, 0x3780)); - return; - } - - strncpy((char*)&thisptr->settings[TaskSetting_t::kBossCode], bossCode.GetPtr(), TaskSetting_t::kBossCodeLen); - - *(uint64be*)&thisptr->settings[TaskSetting_t::kDirectorySizeLimit] = directorySizeLimit; // uint64be - if(directoryName) - strncpy((char*)&thisptr->settings[TaskSetting_t::kDirectoryName], directoryName.GetPtr(), TaskSetting_t::kDirectoryNameLen); - - osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0x80)); - } - - Result SetFileName(NbdlTaskSetting_t* thisptr, const char* fileName) - { - cemuLog_logDebug(LogType::Force, "nn_boss_NbdlTaskSetting_t_SetFileName(0x{:08x}, {})", MEMPTR(thisptr).GetMPTR(), fileName ? fileName : "\"\""); - - if (!fileName || strnlen(fileName, TaskSetting_t::kFileNameLen) == TaskSetting_t::kFileNameLen) - { + if(!bossCode || strnlen(bossCode, TaskSetting::kBossCodeLen) == TaskSetting::kBossCodeLen) return BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_BOSS, 0x3780); - } - - strncpy((char*)&thisptr->settings[TaskSetting_t::kNbdlFileName], fileName, TaskSetting_t::kFileNameLen); + + if (directoryName && strnlen(directoryName, TaskSetting::kDirectoryNameLen) == TaskSetting::kDirectoryNameLen) + return BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_BOSS, 0x3780); + + strncpy((char*)&_thisptr->settings[TaskSetting::kBossCode], bossCode, TaskSetting::kBossCodeLen); + + *(uint64be*)&_thisptr->settings[TaskSetting::kDirectorySizeLimit] = directorySizeLimit; // uint64be + if(directoryName) + strncpy((char*)&_thisptr->settings[TaskSetting::kDirectoryName], directoryName, TaskSetting::kDirectoryNameLen); + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0x80); } - } - - namespace RawUlTaskSetting - { - RawUlTaskSetting_t* ctor(RawUlTaskSetting_t* thisptr) + static Result SetFileName(NbdlTaskSetting* _thisptr, const char* fileName) { - cemuLog_logDebug(LogType::Force, "nn_boss_RawUlTaskSetting_ctor(0x{:x}) TODO", MEMPTR(thisptr).GetMPTR()); - if (!thisptr) - { - // thisptr = new RawUlTaskSetting_t - assert_dbg(); - } + cemuLog_logDebug(LogType::Force, "nn_boss_NbdlTaskSetting_t_SetFileName(0x{:08x}, {})", MEMPTR(_thisptr).GetMPTR(), fileName ? fileName : "\"\""); + if (!fileName || strnlen(fileName, TaskSetting::kFileNameLen) == TaskSetting::kFileNameLen) + return BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_BOSS, 0x3780); - if (thisptr) - { - NetTaskSetting::ctor(thisptr); - thisptr->uknExt_vTableProbably = RawUlTaskSetting_t::kType; - thisptr->ukRaw1 = 0; - thisptr->ukRaw2 = 0; - thisptr->ukRaw3 = 0; - memset(thisptr->rawSpace, 0x00, 0x200); - } - - return thisptr; - } - } - - namespace RawDlTaskSetting - { - RawDlTaskSetting_t* ctor(RawDlTaskSetting_t* thisptr) - { - cemuLog_logDebug(LogType::Force, "nn_boss_RawDlTaskSetting_ctor(0x{:x}) TODO", MEMPTR(thisptr).GetMPTR()); - if (!thisptr) - { - // thisptr = new RawDlTaskSetting_t - assert_dbg(); - } - - if (thisptr) - { - NetTaskSetting::ctor(thisptr); - thisptr->uknExt_vTableProbably = RawDlTaskSetting_t::kType; - } - - return thisptr; + strncpy((char*)&_thisptr->settings[TaskSetting::kNbdlFileName], fileName, TaskSetting::kFileNameLen); + // also sets byte at +0x817 to zero? + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0x80); } - Result Initialize(RawDlTaskSetting_t* thisptr, const char* url, bool newArrival, bool led, const char* fileName, const char* directoryName) + static void InitVTable() { - cemuLog_logDebug(LogType::Force, "nn_boss_RawDlTaskSetting_Initialize(0x{:x}, 0x{:x}, {}, {}, 0x{:x}, 0x{:x})", MEMPTR(thisptr).GetMPTR(), MEMPTR(url).GetMPTR(), newArrival, led, MEMPTR(fileName).GetMPTR(), MEMPTR(directoryName).GetMPTR()); + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(NbdlTaskSetting); + s_VTable->RegisterPreprocess.ptr = nullptr; // todo + s_VTable->unk1.ptr = nullptr; // todo + s_VTable->rttiNetTaskSetting.ptr = nullptr; // todo + } + }; + static_assert(sizeof(NbdlTaskSetting) == 0x1004); + + struct RawUlTaskSetting : NetTaskSetting + { + uint32be ukRaw1; // 0x1004 + uint32be ukRaw2; // 0x1008 + uint32be ukRaw3; // 0x100C + uint8 rawSpace[0x200]; // 0x1010 + + struct VTableRawUlTaskSetting : public VTableNetTaskSetting + { + VTableEntry rttiNetTaskSetting; // unknown + }; + static_assert(sizeof(VTableRawUlTaskSetting) == 8*5); + static inline SysAllocator<VTableRawUlTaskSetting> s_VTable; + + static RawUlTaskSetting* ctor(RawUlTaskSetting* _thisptr) + { + if (!_thisptr) + _thisptr = boss_new<RawUlTaskSetting>(); + NetTaskSetting::ctor(_thisptr); + _thisptr->vTablePtr = s_VTable; + _thisptr->ukRaw1 = 0; + _thisptr->ukRaw2 = 0; + _thisptr->ukRaw3 = 0; + memset(_thisptr->rawSpace, 0x00, 0x200); + return _thisptr; + } + + static void dtor(RawUlTaskSetting* _this, uint32 options) + { + cemuLog_logDebug(LogType::Force, "nn::boss::RawUlTaskSetting::dtor() is todo"); + } + + static void InitVTable() + { + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(RawUlTaskSetting); + s_VTable->RegisterPreprocess.ptr = nullptr; // todo + s_VTable->unk1.ptr = nullptr; // todo + s_VTable->rttiNetTaskSetting.ptr = nullptr; // todo + } + }; + static_assert(sizeof(RawUlTaskSetting) == 0x1210); + + struct RawDlTaskSetting : NetTaskSetting + { + struct VTableRawDlTaskSetting : public VTableNetTaskSetting + { + VTableEntry rttiNetTaskSetting; // unknown + }; + static_assert(sizeof(VTableRawDlTaskSetting) == 8*5); + static inline SysAllocator<VTableRawDlTaskSetting> s_VTable; + + static RawDlTaskSetting* ctor(RawDlTaskSetting* _thisptr) + { + cemuLog_logDebug(LogType::Force, "nn_boss_RawDlTaskSetting_ctor(0x{:x}) TODO", MEMPTR(_thisptr).GetMPTR()); + if (!_thisptr) + _thisptr = boss_new<RawDlTaskSetting>(); + NetTaskSetting::ctor(_thisptr); + _thisptr->vTablePtr = s_VTable; + return _thisptr; + } + + static Result Initialize(RawDlTaskSetting* _thisptr, const char* url, bool newArrival, bool led, const char* fileName, const char* directoryName) + { + cemuLog_logDebug(LogType::Force, "nn_boss_RawDlTaskSetting_Initialize(0x{:x}, 0x{:x}, {}, {}, 0x{:x}, 0x{:x})", MEMPTR(_thisptr).GetMPTR(), MEMPTR(url).GetMPTR(), newArrival, led, MEMPTR(fileName).GetMPTR(), MEMPTR(directoryName).GetMPTR()); if (!url) { return 0xC0203780; } - if (strnlen(url, TaskSetting_t::kURLLen) == TaskSetting_t::kURLLen) + if (strnlen(url, TaskSetting::kURLLen) == TaskSetting::kURLLen) { return 0xC0203780; } cemuLog_logDebug(LogType::Force, "\t-> url: {}", url); - if (fileName && strnlen(fileName, TaskSetting_t::kFileNameLen) == TaskSetting_t::kFileNameLen) + if (fileName && strnlen(fileName, TaskSetting::kFileNameLen) == TaskSetting::kFileNameLen) { return 0xC0203780; } - if (directoryName && strnlen(directoryName, TaskSetting_t::kDirectoryNameLen) == TaskSetting_t::kDirectoryNameLen) + if (directoryName && strnlen(directoryName, TaskSetting::kDirectoryNameLen) == TaskSetting::kDirectoryNameLen) { return 0xC0203780; } - strncpy((char*)thisptr + TaskSetting_t::kURL, url, TaskSetting_t::kURLLen); - thisptr->settings[0x147] = '\0'; + strncpy((char*)_thisptr + TaskSetting::kURL, url, TaskSetting::kURLLen); + _thisptr->settings[0x147] = '\0'; if (fileName) - strncpy((char*)thisptr + 0x7D0, fileName, TaskSetting_t::kFileNameLen); + strncpy((char*)_thisptr + 0x7D0, fileName, TaskSetting::kFileNameLen); else - strncpy((char*)thisptr + 0x7D0, "rawcontent.dat", TaskSetting_t::kFileNameLen); - thisptr->settings[0x7EF] = '\0'; + strncpy((char*)_thisptr + 0x7D0, "rawcontent.dat", TaskSetting::kFileNameLen); + _thisptr->settings[0x7EF] = '\0'; - cemuLog_logDebug(LogType::Force, "\t-> filename: {}", (char*)thisptr + 0x7D0); + cemuLog_logDebug(LogType::Force, "\t-> filename: {}", (char*)_thisptr + 0x7D0); if (directoryName) { - strncpy((char*)thisptr + 0x7C8, directoryName, TaskSetting_t::kDirectoryNameLen); - thisptr->settings[0x7CF] = '\0'; - cemuLog_logDebug(LogType::Force, "\t-> directoryName: {}", (char*)thisptr + 0x7C8); + strncpy((char*)_thisptr + 0x7C8, directoryName, TaskSetting::kDirectoryNameLen); + _thisptr->settings[0x7CF] = '\0'; + cemuLog_logDebug(LogType::Force, "\t-> directoryName: {}", (char*)_thisptr + 0x7C8); } - thisptr->settings[0x7C0] = newArrival; - thisptr->settings[0x7C1] = led; - *(uint16be*)&thisptr->settings[0x28] = 0x3; + _thisptr->settings[0x7C0] = newArrival; + _thisptr->settings[0x7C1] = led; + *(uint16be*)&_thisptr->settings[0x28] = 0x3; return 0x200080; } - } - namespace PlayReportSetting // : NetTaskSetting - { - void export_ctor(PPCInterpreter_t* hCPU) + static void InitVTable() { - ppcDefineParamMEMPTR(thisptr, PlayReportSetting_t, 0); - cemuLog_logDebug(LogType::Force, "nn_boss_PlayReportSetting_ctor TODO"); - if (!thisptr) - { - assert_dbg(); - } + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(RawDlTaskSetting); + s_VTable->RegisterPreprocess.ptr = nullptr; // todo + s_VTable->unk1.ptr = nullptr; // todo + s_VTable->rttiNetTaskSetting.ptr = nullptr; // todo + } + }; + static_assert(sizeof(RawDlTaskSetting) == 0x1004); - if (thisptr) - { - RawUlTaskSetting::ctor(thisptr.GetPtr()); - thisptr->uknExt_vTableProbably = PlayReportSetting_t::kType; - thisptr->ukPlay1 = nullptr; - thisptr->ukPlay2 = 0; - thisptr->ukPlay3 = 0; - thisptr->ukPlay4 = 0; - } + struct PlayReportSetting : RawUlTaskSetting + { + MEMPTR<uint8> ukn1210_ptr; // 0x1210 + uint32be ukn1214_size; // 0x1214 + uint32be ukPlay3; // 0x1218 + uint32be ukPlay4; // 0x121C - osLib_returnFromFunction(hCPU, thisptr.GetMPTR()); + struct VTablePlayReportSetting : public VTableRawUlTaskSetting + {}; + static_assert(sizeof(VTablePlayReportSetting) == 8*5); + static inline SysAllocator<VTablePlayReportSetting> s_VTable; + + static PlayReportSetting* ctor(PlayReportSetting* _this) + { + if(!_this) + _this = boss_new<PlayReportSetting>(); + RawUlTaskSetting::ctor(_this); + _this->vTablePtr = s_VTable; + _this->ukn1210_ptr = nullptr; + _this->ukn1214_size = 0; + _this->ukPlay3 = 0; + _this->ukPlay4 = 0; + return _this; } - void export_Initialize(PPCInterpreter_t* hCPU) + static void dtor(PlayReportSetting* _this, uint32 options) { - ppcDefineParamMEMPTR(thisptr, PlayReportSetting_t, 0); - ppcDefineParamMEMPTR(ptr, void*, 1); - ppcDefineParamU32(value, 2); - ppcDefineParamMEMPTR(directoryName, const char, 4); - //cemuLog_logDebug(LogType::Force, "nn_boss_PlayReportSetting_Initialize(0x{:08x}, {}, 0x{:x}, 0x{:08x})", thisptr.GetMPTR(), ptr.GetPtr(), directorySizeLimit, directoryName.GetMPTR()); + RawUlTaskSetting::dtor(_this, 0); + if(options&1) + boss_delete(_this->ukn1210_ptr.GetPtr()); + } - if(!ptr || value == 0 || value > 0x19000) + static void Initialize(PlayReportSetting* _this, uint8* ptr, uint32 size) + { + if(!ptr || size == 0 || size > 0x19000) { - cemuLog_logDebug(LogType::Force, "nn_boss_PlayReportSetting_Initialize: invalid parameter"); - osLib_returnFromFunction(hCPU, 0); + cemuLog_logDebug(LogType::Force, "nn::boss::PlayReportSetting::Initialize: invalid parameter"); + return; } - *ptr.GetPtr<uint8>() = 0; + *ptr = 0; - *(uint16be*)&thisptr->settings[0x28] = 6; - *(uint16be*)&thisptr->settings[0x2B] |= 0x3; - *(uint16be*)&thisptr->settings[0x2C] |= 0xA; - *(uint32be*)&thisptr->settings[0x7C0] |= 2; - - thisptr->ukPlay1 = ptr; - thisptr->ukPlay2 = value; - thisptr->ukPlay3 = 0; - thisptr->ukPlay4 = 0; + *(uint16be*)&_this->settings[0x28] = 6; + *(uint16be*)&_this->settings[0x2B] |= 0x3; + *(uint16be*)&_this->settings[0x2C] |= 0xA; + *(uint32be*)&_this->settings[0x7C0] |= 2; + + _this->ukn1210_ptr = ptr; + _this->ukn1214_size = size; + _this->ukPlay3 = 0; + _this->ukPlay4 = 0; // TODO - osLib_returnFromFunction(hCPU, 0); } - void export_Set(PPCInterpreter_t* hCPU) + static bool Set(PlayReportSetting* _this, const char* keyname, uint32 value) { - ppcDefineParamMEMPTR(thisptr, PlayReportSetting_t, 0); - ppcDefineParamMEMPTR(key, const char, 1); - ppcDefineParamU32(value, 2); - // TODO - cemuLog_logDebug(LogType::Force, "nn_boss_PlayReportSetting_Set(0x{:08x}, {}, 0x{:x}) TODO", thisptr.GetMPTR(), key.GetPtr(), value); - - osLib_returnFromFunction(hCPU, 1); + return true; } - - } - - namespace Title - { - Title_t* ctor(Title_t* thisptr) + static void InitVTable() { - cemuLog_logDebug(LogType::Force, "nn_boss_Title_ctor(0x{:x})", MEMPTR(thisptr).GetMPTR()); - if (!thisptr) - { - // thisptr = new Task_t - assert_dbg(); - } - - *thisptr = {}; - - return thisptr; + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(PlayReportSetting); + s_VTable->RegisterPreprocess.ptr = nullptr; // todo + s_VTable->unk1.ptr = nullptr; // todo + s_VTable->rttiNetTaskSetting.ptr = nullptr; // todo } - } + }; + static_assert(sizeof(PlayReportSetting) == 0x1220); - namespace DirectoryName + struct Task { - DirectoryName_t* ctor(DirectoryName_t* thisptr) + struct VTableTask { - cemuLog_logDebug(LogType::Force, "nn_boss_DirectoryName_ctor(0x{:x})", MEMPTR(thisptr).GetMPTR()); - if (!thisptr) - { - // thisptr = new Task_t - assert_dbg(); - } + VTableEntry rtti; + VTableEntry dtor; + }; + static inline SysAllocator<VTableTask> s_vTable; - memset(thisptr->name, 0x00, 0x8); + uint32be accountId; // 0x00 + uint32be uk2; // 0x04 + TaskId taskId; // 0x08 + TitleId titleId; // 0x10 + MEMPTR<VTableTask> vTablePtr; // 0x18 + uint32be padding; // 0x1C - return thisptr; - } - - const char* operator_const_char(DirectoryName_t* thisptr) - { - cemuLog_logDebug(LogType::Force, "nn_boss_DirectoryName_operator_const_char(0x{:x})", MEMPTR(thisptr).GetMPTR()); - return thisptr->name; - } - } - - namespace Task - { - - Result Initialize(Task_t* thisptr, const char* taskId, uint32 accountId) + static Result Initialize1(Task* _thisptr, const char* taskId, uint32 accountId) // Initialize__Q3_2nn4boss4TaskFPCcUi { if(!taskId || strnlen(taskId, 0x8) == 8) { return BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_BOSS, 0x3780); } - - thisptr->accountId = accountId; - strncpy(thisptr->taskId.id, taskId, 0x08); + _thisptr->accountId = accountId; + strncpy(_thisptr->taskId.id, taskId, 0x08); return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_BOSS, 0x80); } - Result Initialize(Task_t* thisptr, uint8 slot, const char* taskId) + static Result Initialize2(Task* _thisptr, uint8 slot, const char* taskId) // Initialize__Q3_2nn4boss4TaskFUcPCc { const uint32 accountId = slot == 0 ? 0 : act::GetPersistentIdEx(slot); - return Initialize(thisptr, taskId, accountId); + return Initialize1(_thisptr, taskId, accountId); } - Result Initialize(Task_t* thisptr, const char* taskId) + static Result Initialize3(Task* _thisptr, const char* taskId) // Initialize__Q3_2nn4boss4TaskFPCc { - return Initialize(thisptr, taskId, 0); + return Initialize1(_thisptr, taskId, 0); } - void export_Initialize3(PPCInterpreter_t* hCPU) + static Task* ctor2(Task* _thisptr, const char* taskId, uint32 accountId) // __ct__Q3_2nn4boss4TaskFPCcUi { - ppcDefineParamMEMPTR(thisptr, Task_t, 0); - ppcDefineParamMEMPTR(taskId, const char, 1); - ppcDefineParamU32(accountId, 2); - cemuLog_logDebug(LogType::Force, "nn_boss_Task_Initialize3(0x{:08x}, {}, 0x{:x})", thisptr.GetMPTR(), taskId.GetPtr(), accountId); - const Result result = Initialize(thisptr.GetPtr(), taskId.GetPtr(), accountId); - osLib_returnFromFunction(hCPU, result); + if (!_thisptr) + _thisptr = boss_new<Task>(); + _thisptr->accountId = 0; + _thisptr->vTablePtr = s_vTable; + TaskId::ctor(&_thisptr->taskId); + TitleId::ctor(&_thisptr->titleId, 0); + auto r = Initialize1(_thisptr, taskId, accountId); + cemu_assert_debug(NN_RESULT_IS_SUCCESS(r)); + return _thisptr; } - void export_Initialize2(PPCInterpreter_t* hCPU) + static Task* ctor1(Task* _thisptr, uint8 slot, const char* taskId) // __ct__Q3_2nn4boss4TaskFUcPCc { - ppcDefineParamMEMPTR(thisptr, Task_t, 0); - ppcDefineParamU8(slotId, 1); - ppcDefineParamMEMPTR(taskId, const char, 2); - cemuLog_logDebug(LogType::Force, "nn_boss_Task_Initialize2(0x{:08x}, {}, {})", thisptr.GetMPTR(), slotId, taskId.GetPtr()); - const Result result = Initialize(thisptr.GetPtr(), slotId, taskId.GetPtr()); - osLib_returnFromFunction(hCPU, result); - } - - void export_Initialize1(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(thisptr, Task_t, 0); - ppcDefineParamMEMPTR(taskId, const char, 1); - cemuLog_logDebug(LogType::Force, "nn_boss_Task_Initialize1(0x{:08x}, {})", thisptr.GetMPTR(), taskId.GetPtr()); - const Result result = Initialize(thisptr.GetPtr(), taskId.GetPtr()); - osLib_returnFromFunction(hCPU, result); + if (!_thisptr) + _thisptr = boss_new<Task>(); + _thisptr->accountId = 0; + _thisptr->vTablePtr = s_vTable; + TaskId::ctor(&_thisptr->taskId); + TitleId::ctor(&_thisptr->titleId, 0); + auto r = Initialize2(_thisptr, slot, taskId); + cemu_assert_debug(NN_RESULT_IS_SUCCESS(r)); + return _thisptr; } - - Task_t* ctor(Task_t* thisptr, const char* taskId, uint32 accountId) + static Task* ctor3(Task* _thisptr, const char* taskId) // __ct__Q3_2nn4boss4TaskFPCc { - if (!thisptr) - { - // thisptr = new Task_t - assert_dbg(); - } - - if (thisptr) - { - thisptr->accountId = 0; - thisptr->ext = 0; // dword_10002174 - TaskId::ctor(&thisptr->taskId); - TitleId::ctor(&thisptr->titleId, 0); - cemu_assert_debug(NN_RESULT_IS_SUCCESS(Initialize(thisptr, taskId, accountId))); - } - - return thisptr; + if (!_thisptr) + _thisptr = boss_new<Task>(); + _thisptr->accountId = 0; + _thisptr->vTablePtr = s_vTable; + TaskId::ctor(&_thisptr->taskId); + TitleId::ctor(&_thisptr->titleId, 0); + auto r = Initialize3(_thisptr, taskId); + cemu_assert_debug(NN_RESULT_IS_SUCCESS(r)); + return _thisptr; } - Task_t* ctor(Task_t* thisptr, uint8 slot, const char* taskId) + static Task* ctor4(Task* _thisptr) // __ct__Q3_2nn4boss4TaskFv { - if (!thisptr) - { - // thisptr = new Task_t - assert_dbg(); - } - - if (thisptr) - { - thisptr->accountId = 0; - thisptr->ext = 0; // dword_10002174 - TaskId::ctor(&thisptr->taskId); - TitleId::ctor(&thisptr->titleId, 0); - cemu_assert_debug(NN_RESULT_IS_SUCCESS(Initialize(thisptr, slot, taskId))); - } - - return thisptr; + if (!_thisptr) + _thisptr = boss_new<Task>(); + _thisptr->accountId = 0; + _thisptr->vTablePtr = s_vTable; + TaskId::ctor(&_thisptr->taskId); + TitleId::ctor(&_thisptr->titleId, 0); + memset(&_thisptr->taskId, 0x00, sizeof(TaskId)); + return _thisptr; } - Task_t* ctor(Task_t* thisptr, const char* taskId) + static void dtor(Task* _this, uint32 options) // __dt__Q3_2nn4boss4TaskFv { - if (!thisptr) - { - // thisptr = new Task_t - assert_dbg(); - } - - if (thisptr) - { - thisptr->accountId = 0; - thisptr->ext = 0; // dword_10002174 - TaskId::ctor(&thisptr->taskId); - TitleId::ctor(&thisptr->titleId, 0); - cemu_assert_debug(NN_RESULT_IS_SUCCESS(Initialize(thisptr, taskId))); - } - - return thisptr; + cemuLog_logDebug(LogType::Force, "nn::boss::Task::dtor(0x{:08x}, 0x{:08x})", MEMPTR(_this).GetMPTR(), options); + // todo - Task::Finalize + if(options & 1) + boss_delete(_this); } - Task_t* ctor(Task_t* thisptr) + static Result Run(Task* _thisptr, bool isForegroundRun) { - if (!thisptr) - { - // thisptr = new Task_t - assert_dbg(); - } - - if (thisptr) - { - thisptr->accountId = 0; - thisptr->ext = 0; // dword_10002174 - TaskId::ctor(&thisptr->taskId); - TitleId::ctor(&thisptr->titleId, 0); - memset(&thisptr->taskId, 0x00, sizeof(TaskId_t)); - } - - return thisptr; - } - - void export_ctor(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(thisptr, Task_t, 0); - cemuLog_logDebug(LogType::Force, "nn_boss_Task_ctor(0x{:08x})", thisptr.GetMPTR()); - ctor(thisptr.GetPtr()); - osLib_returnFromFunction(hCPU, thisptr.GetMPTR()); - } - - void export_Run(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(thisptr, Task_t, 0); - ppcDefineParamU8(isForegroundRun, 1); - cemuLog_logDebug(LogType::Force, "nn_boss_Task_Run(0x{:08x}, {})", thisptr.GetMPTR(), isForegroundRun); if (isForegroundRun != 0) { - //peterBreak(); cemuLog_logDebug(LogType::Force, "export_Run foreground run"); } bossPrepareRequest(); bossRequest->requestCode = IOSU_NN_BOSS_TASK_RUN; - bossRequest->accountId = thisptr->accountId; - bossRequest->taskId = thisptr->taskId.id; - bossRequest->titleId = thisptr->titleId.u64; + bossRequest->accountId = _thisptr->accountId; + bossRequest->taskId = _thisptr->taskId.id; + bossRequest->titleId = _thisptr->titleId.u64; bossRequest->bool_parameter = isForegroundRun != 0; __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - osLib_returnFromFunction(hCPU, 0); + return 0; } - void export_StartScheduling(PPCInterpreter_t* hCPU) + static Result StartScheduling(Task* _thisptr, uint8 executeImmediately) { - ppcDefineParamMEMPTR(thisptr, Task_t, 0); - ppcDefineParamU8(executeImmediately, 1); - cemuLog_logDebug(LogType::Force, "nn_boss_Task_StartScheduling(0x{:08x}, {})", thisptr.GetMPTR(), executeImmediately); - bossPrepareRequest(); bossRequest->requestCode = IOSU_NN_BOSS_TASK_START_SCHEDULING; - bossRequest->accountId = thisptr->accountId; - bossRequest->taskId = thisptr->taskId.id; - bossRequest->titleId = thisptr->titleId.u64; + bossRequest->accountId = _thisptr->accountId; + bossRequest->taskId = _thisptr->taskId.id; + bossRequest->titleId = _thisptr->titleId.u64; bossRequest->bool_parameter = executeImmediately != 0; __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - osLib_returnFromFunction(hCPU, 0); + return 0; } - void export_StopScheduling(PPCInterpreter_t* hCPU) + static Result StopScheduling(Task* _thisptr) { - ppcDefineParamMEMPTR(thisptr, Task_t, 0); - cemuLog_logDebug(LogType::Force, "nn_boss_Task_StopScheduling(0x{:08x})", thisptr.GetMPTR()); - bossPrepareRequest(); bossRequest->requestCode = IOSU_NN_BOSS_TASK_STOP_SCHEDULING; - bossRequest->accountId = thisptr->accountId; - bossRequest->taskId = thisptr->taskId.id; - bossRequest->titleId = thisptr->titleId.u64; + bossRequest->accountId = _thisptr->accountId; + bossRequest->taskId = _thisptr->taskId.id; + bossRequest->titleId = _thisptr->titleId.u64; __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - osLib_returnFromFunction(hCPU, 0); + return 0; } - void export_IsRegistered(PPCInterpreter_t* hCPU) + static Result IsRegistered(Task* _thisptr) { - ppcDefineParamMEMPTR(thisptr, Task_t, 0); - bossPrepareRequest(); bossRequest->requestCode = IOSU_NN_BOSS_TASK_IS_REGISTERED; - bossRequest->accountId = thisptr->accountId; - bossRequest->titleId = thisptr->titleId.u64; - bossRequest->taskId = thisptr->taskId.id; + bossRequest->accountId = _thisptr->accountId; + bossRequest->titleId = _thisptr->titleId.u64; + bossRequest->taskId = _thisptr->taskId.id; __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - cemuLog_logDebug(LogType::Force, "nn_boss_Task_IsRegistered(0x{:08x}) -> {}", thisptr.GetMPTR(), bossRequest->returnCode); - - osLib_returnFromFunction(hCPU, bossRequest->returnCode); + return bossRequest->returnCode; } - void export_Wait(PPCInterpreter_t* hCPU) + static Result Wait(Task* _thisptr, uint32 timeout, uint32 waitState) // Wait__Q3_2nn4boss4TaskFUiQ3_2nn4boss13TaskWaitState { - // Wait__Q3_2nn4boss4TaskFUiQ3_2nn4boss13TaskWaitState - ppcDefineParamMEMPTR(thisptr, Task_t, 0); - ppcDefineParamU32(timeout, 1); - ppcDefineParamU32(waitState, 2); - cemuLog_logDebug(LogType::Force, "nn_boss_Task_Wait(0x{:08x}, 0x{:x}, {})", thisptr.GetMPTR(), timeout, waitState); - bossPrepareRequest(); bossRequest->requestCode = IOSU_NN_BOSS_TASK_WAIT; - bossRequest->titleId = thisptr->titleId.u64; - bossRequest->taskId = thisptr->taskId.id; + bossRequest->titleId = _thisptr->titleId.u64; + bossRequest->taskId = _thisptr->taskId.id; bossRequest->timeout = timeout; bossRequest->waitState = waitState; __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - osLib_returnFromFunction(hCPU, bossRequest->returnCode); - - //osLib_returnFromFunction(hCPU, 1); // 0 -> timeout, 1 -> wait condition met + return bossRequest->returnCode; } - void export_RegisterForImmediateRun(PPCInterpreter_t* hCPU) + static Result RegisterForImmediateRun(Task* _thisptr, TaskSetting* settings) // RegisterForImmediateRun__Q3_2nn4boss4TaskFRCQ3_2nn4boss11TaskSetting { - // RegisterForImmediateRun__Q3_2nn4boss4TaskFRCQ3_2nn4boss11TaskSetting - ppcDefineParamMEMPTR(thisptr, Task_t, 0); - ppcDefineParamMEMPTR(settings, TaskSetting_t, 1); - cemuLog_logDebug(LogType::Force, "nn_boss_Task_RegisterForImmediateRun(0x{:08x}, 0x{:08x})", thisptr.GetMPTR(), settings.GetMPTR()); - bossPrepareRequest(); bossRequest->requestCode = IOSU_NN_BOSS_TASK_REGISTER; - bossRequest->accountId = thisptr->accountId; - bossRequest->taskId = thisptr->taskId.id; - bossRequest->settings = settings.GetPtr(); + bossRequest->accountId = _thisptr->accountId; + bossRequest->taskId = _thisptr->taskId.id; + bossRequest->settings = settings; bossRequest->uk1 = 0xC00; - if (TaskSetting::IsPrivilegedTaskSetting(settings.GetPtr())) - bossRequest->titleId = thisptr->titleId.u64; + if (TaskSetting::IsPrivileged(settings)) + bossRequest->titleId = _thisptr->titleId.u64; - const sint32 result = __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - osLib_returnFromFunction(hCPU, result); + Result result = __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); + return result; } - void export_Unregister(PPCInterpreter_t* hCPU) + static Result Unregister(Task* _thisptr) { - ppcDefineParamMEMPTR(thisptr, Task_t, 0); - cemuLog_logDebug(LogType::Force, "nn_boss_Task_Unregister(0x{:08x})", thisptr.GetMPTR()); - bossPrepareRequest(); bossRequest->requestCode = IOSU_NN_BOSS_TASK_UNREGISTER; - bossRequest->accountId = thisptr->accountId; - bossRequest->taskId = thisptr->taskId.id; - bossRequest->titleId = thisptr->titleId.u64; + bossRequest->accountId = _thisptr->accountId; + bossRequest->taskId = _thisptr->taskId.id; + bossRequest->titleId = _thisptr->titleId.u64; const sint32 result = __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - osLib_returnFromFunction(hCPU, result); + return result; } - void export_Register(PPCInterpreter_t* hCPU) + static Result Register(Task* _thisptr, TaskSetting* settings) { - ppcDefineParamMEMPTR(thisptr, Task_t, 0); - ppcDefineParamMEMPTR(settings, TaskSetting_t, 1); - cemuLog_logDebug(LogType::Force, "nn_boss_Task_Register(0x{:08x}, 0x{:08x})", thisptr.GetMPTR(), settings.GetMPTR()); - - if (hCPU->gpr[4] == 0) + if (!settings) { - cemuLog_logDebug(LogType::Force, "nn_boss_Task_Register - crash workaround (fix me)"); - osLib_returnFromFunction(hCPU, 0); - return; + cemuLog_logDebug(LogType::Force, "nn_boss_Task_Register - crash workaround (fix me)"); // settings should never be zero + return 0; } bossPrepareRequest(); bossRequest->requestCode = IOSU_NN_BOSS_TASK_REGISTER_FOR_IMMEDIATE_RUN; - bossRequest->accountId = thisptr->accountId; - bossRequest->taskId = thisptr->taskId.id; - bossRequest->settings = settings.GetPtr(); + bossRequest->accountId = _thisptr->accountId; + bossRequest->taskId = _thisptr->taskId.id; + bossRequest->settings = settings; bossRequest->uk1 = 0xC00; - if(TaskSetting::IsPrivilegedTaskSetting(settings.GetPtr())) - bossRequest->titleId = thisptr->titleId.u64; + if(TaskSetting::IsPrivileged(settings)) + bossRequest->titleId = _thisptr->titleId.u64; __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - osLib_returnFromFunction(hCPU, bossRequest->returnCode); + return bossRequest->returnCode; } - - - void export_GetTurnState(PPCInterpreter_t* hCPU) + + static uint32 GetTurnState(Task* _this, uint32be* executionCountOut) { - ppcDefineParamMEMPTR(thisptr, Task_t, 0); - ppcDefineParamMEMPTR(execCount, uint32be, 1); - bossPrepareRequest(); bossRequest->requestCode = IOSU_NN_BOSS_TASK_GET_TURN_STATE; - bossRequest->accountId = thisptr->accountId; - bossRequest->taskId = thisptr->taskId.id; - bossRequest->titleId = thisptr->titleId.u64; + bossRequest->accountId = _this->accountId; + bossRequest->taskId = _this->taskId.id; + bossRequest->titleId = _this->titleId.u64; __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - if (execCount) - *execCount = bossRequest->u32.exec_count; + if (executionCountOut) + *executionCountOut = bossRequest->u32.exec_count; - cemuLog_logDebug(LogType::Force, "nn_boss_Task_GetTurnState(0x{:08x}, 0x{:08x}) -> {}", thisptr.GetMPTR(), execCount.GetMPTR(), bossRequest->u32.result); - - osLib_returnFromFunction(hCPU, bossRequest->u32.result); - //osLib_returnFromFunction(hCPU, 7); // 7 -> finished? 0x11 -> Error (Splatoon doesn't like it when we return 0x11 for Nbdl tasks) RETURN FINISHED + return bossRequest->u32.result; + // 7 -> finished? 0x11 -> Error (Splatoon doesn't like it when we return 0x11 for Nbdl tasks) } - void export_GetContentLength(PPCInterpreter_t* hCPU) + static uint64 GetContentLength(Task* _this, uint32be* executionCountOut) { - ppcDefineParamMEMPTR(thisptr, Task_t, 0); - ppcDefineParamMEMPTR(execCount, uint32be, 1); - bossPrepareRequest(); bossRequest->requestCode = IOSU_NN_BOSS_TASK_GET_CONTENT_LENGTH; - bossRequest->accountId = thisptr->accountId; - bossRequest->taskId = thisptr->taskId.id; - bossRequest->titleId = thisptr->titleId.u64; + bossRequest->accountId = _this->accountId; + bossRequest->taskId = _this->taskId.id; + bossRequest->titleId = _this->titleId.u64; __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - if (execCount) - *execCount = bossRequest->u64.exec_count; + if (executionCountOut) + *executionCountOut = bossRequest->u64.exec_count; - cemuLog_logDebug(LogType::Force, "nn_boss_Task_GetContentLength(0x{:08x}, 0x{:08x}) -> 0x{:x}", thisptr.GetMPTR(), execCount.GetMPTR(), bossRequest->u64.result); - - osLib_returnFromFunction64(hCPU, bossRequest->u64.result); + return bossRequest->u64.result; } - void export_GetProcessedLength(PPCInterpreter_t* hCPU) + static uint64 GetProcessedLength(Task* _this, uint32be* executionCountOut) { - ppcDefineParamMEMPTR(thisptr, Task_t, 0); - ppcDefineParamMEMPTR(execCount, uint32be, 1); - bossPrepareRequest(); bossRequest->requestCode = IOSU_NN_BOSS_TASK_GET_PROCESSED_LENGTH; - bossRequest->accountId = thisptr->accountId; - bossRequest->taskId = thisptr->taskId.id; - bossRequest->titleId = thisptr->titleId.u64; + bossRequest->accountId = _this->accountId; + bossRequest->taskId = _this->taskId.id; + bossRequest->titleId = _this->titleId.u64; __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - if (execCount) - *execCount = bossRequest->u64.exec_count; - - cemuLog_logDebug(LogType::Force, "nn_boss_Task_GetProcessedLength(0x{:08x}, 0x{:08x}) -> 0x{:x}", thisptr.GetMPTR(), execCount.GetMPTR(), bossRequest->u64.result); - - osLib_returnFromFunction64(hCPU, bossRequest->u64.result); + if (executionCountOut) + *executionCountOut = bossRequest->u64.exec_count; + return bossRequest->u64.result; } - void export_GetHttpStatusCode(PPCInterpreter_t* hCPU) + static uint32 GetHttpStatusCode(Task* _this, uint32be* executionCountOut) { - ppcDefineParamMEMPTR(thisptr, Task_t, 0); - ppcDefineParamMEMPTR(execCount, uint32be, 1); - bossPrepareRequest(); bossRequest->requestCode = IOSU_NN_BOSS_TASK_GET_HTTP_STATUS_CODE; - bossRequest->accountId = thisptr->accountId; - bossRequest->taskId = thisptr->taskId.id; - bossRequest->titleId = thisptr->titleId.u64; + bossRequest->accountId = _this->accountId; + bossRequest->taskId = _this->taskId.id; + bossRequest->titleId = _this->titleId.u64; __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - if (execCount) - *execCount = bossRequest->u32.exec_count; + if (executionCountOut) + *executionCountOut = bossRequest->u32.exec_count; - cemuLog_logDebug(LogType::Force, "nn_boss_Task_GetHttpStatusCode(0x{:08x}, 0x{:08x}) -> {}", thisptr.GetMPTR(), execCount.GetMPTR(), bossRequest->u32.result); - - osLib_returnFromFunction(hCPU, bossRequest->u32.result); + return bossRequest->u32.result; } - } - struct PrivilegedTask_t : Task_t - { - + static void InitVTable() + { + s_vTable->rtti.ptr = nullptr; // todo + s_vTable->dtor.ptr = RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) { Task::dtor(MEMPTR<Task>(hCPU->gpr[3]), hCPU->gpr[4]); osLib_returnFromFunction(hCPU, 0); }); + } }; - static_assert(sizeof(PrivilegedTask_t) == 0x20); - - struct AlmightyTask_t : PrivilegedTask_t + + static_assert(sizeof(Task) == 0x20); + + struct PrivilegedTask : Task { - + struct VTablePrivilegedTask : public VTableTask + { + VTableEntry rttiTask; + }; + static_assert(sizeof(VTablePrivilegedTask) == 8*3); + static inline SysAllocator<VTablePrivilegedTask> s_VTable; + + static PrivilegedTask* ctor(PrivilegedTask* _thisptr) + { + if (!_thisptr) + _thisptr = boss_new<PrivilegedTask>(); + Task::ctor4(_thisptr); + _thisptr->vTablePtr = s_VTable; + return _thisptr; + } + + static void dtor(PrivilegedTask* _this, uint32 options) + { + if(!_this) + return; + Task::dtor(_this, 0); + if(options & 1) + boss_delete(_this); + } + + static void InitVTable() + { + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(PrivilegedTask); + s_VTable->rttiTask.ptr = nullptr; // todo + } }; - static_assert(sizeof(AlmightyTask_t) == 0x20); - - namespace PrivilegedTask - { - PrivilegedTask_t* ctor(PrivilegedTask_t*thisptr) - { - if (!thisptr) - assert_dbg(); // new - - Task::ctor(thisptr); - thisptr->ext = 0x10003a50; - return thisptr; - } - } + static_assert(sizeof(PrivilegedTask) == 0x20); - namespace AlmightyTask + struct AlmightyTask : PrivilegedTask { - AlmightyTask_t* ctor(AlmightyTask_t* thisptr) - { - if (!thisptr) - assert_dbg(); // new + struct VTableAlmightyTask : public VTablePrivilegedTask + {}; + static_assert(sizeof(VTableAlmightyTask) == 8*3); + static inline SysAllocator<VTableAlmightyTask> s_VTable; - PrivilegedTask::ctor(thisptr); - thisptr->ext = 0x10002a0c; - return thisptr; - } - void dtor(AlmightyTask_t* thisptr) + static AlmightyTask* ctor(AlmightyTask* _thisptr) { - if (thisptr) - freeMem(thisptr); + if (!_thisptr) + _thisptr = boss_new<AlmightyTask>(); + PrivilegedTask::ctor(_thisptr); + _thisptr->vTablePtr = s_VTable; + return _thisptr; } - - uint32 Initialize(AlmightyTask_t* thisptr, TitleId_t* titleId, const char* taskId, uint32 accountId) + + static void dtor(AlmightyTask* _thisptr, uint32 options) { - if (!thisptr) + if (!_thisptr) + return; + PrivilegedTask::dtor(_thisptr, 0); + if(options&1) + boss_delete(_thisptr); + } + + static uint32 Initialize(AlmightyTask* _thisptr, TitleId* titleId, const char* taskId, uint32 accountId) + { + if (!_thisptr) return 0xc0203780; - thisptr->accountId = accountId; - thisptr->titleId.u64 = titleId->u64; - strncpy(thisptr->taskId.id, taskId, 8); - thisptr->taskId.id[7] = 0x00; - + _thisptr->accountId = accountId; + _thisptr->titleId.u64 = titleId->u64; + strncpy(_thisptr->taskId.id, taskId, 8); + _thisptr->taskId.id[7] = 0x00; + return 0x200080; } - } - Result InitializeImpl() - { - // if( Initialize(IpcClientCafe*) ) ... - g_isInitialized = true; - return 0; - } - - void export_IsInitialized(PPCInterpreter_t* hCPU) - { - osLib_returnFromFunction(hCPU, (uint32)g_isInitialized); - } - - Result Initialize() - { - Result result; - coreinit::OSLockMutex(&g_mutex); - - if(g_initCounter != 0 || NN_RESULT_IS_SUCCESS((result=InitializeImpl()))) + static void InitVTable() { - g_initCounter++; - result = 0; + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(AlmightyTask); + s_VTable->rttiTask.ptr = nullptr; // todo + } + }; + static_assert(sizeof(AlmightyTask) == 0x20); + + struct DataName + { + char name[32]; + + static DataName* ctor(DataName* _this) // __ct__Q3_2nn4boss8DataNameFv + { + if(!_this) + _this = boss_new<DataName>(); + memset(_this->name, 0, sizeof(name)); + return _this; } - coreinit::OSUnlockMutex(&g_mutex); - return result; - } + static const char* operator_const_char(DataName* _this) // __opPCc__Q3_2nn4boss8DataNameCFv + { + return _this->name; + } + }; + static_assert(sizeof(DataName) == 0x20); - void export_Initialize(PPCInterpreter_t* hCPU) + struct BossStorageFadEntry { - cemuLog_logDebug(LogType::Force, "nn_boss_Initialize()"); - osLib_returnFromFunction(hCPU, Initialize()); - } - - void export_GetBossState(PPCInterpreter_t* hCPU) - { - cemuLog_logDebug(LogType::Force, "nn_boss.GetBossState() - stub"); - osLib_returnFromFunction(hCPU, 7); - } - - enum StorageKind - { - kStorageKind_NBDL, - kStorageKind_RawDl, + char name[32]; + uint32be fileNameId; + uint32 ukn24; + uint32 ukn28; + uint32 ukn2C; + uint32 ukn30; + uint32be timestampRelated; // guessed }; - namespace Storage +#define FAD_ENTRY_MAX_COUNT 512 + + struct Storage { - struct bossStorage_t + struct VTableStorage { - /* +0x00 */ uint32be accountId; - /* +0x04 */ uint32be storageKind; - /* +0x08 */ uint8 ukn08Array[3]; - /* +0x0B */ char storageName[8]; - uint8 ukn13; - uint8 ukn14; - uint8 ukn15; - uint8 ukn16; - uint8 ukn17; - /* +0x18 */ - nn::boss::TitleId_t titleId; - uint32be ukn20; // pointer to some global struct - uint32be ukn24; + VTableEntry rtti; + VTableEntry dtor; }; - - static_assert(sizeof(bossStorage_t) == 0x28); - static_assert(offsetof(bossStorage_t, storageKind) == 0x04); - static_assert(offsetof(bossStorage_t, ukn08Array) == 0x08); - static_assert(offsetof(bossStorage_t, storageName) == 0x0B); - static_assert(offsetof(bossStorage_t, titleId) == 0x18); + static inline SysAllocator<VTableStorage> s_vTable; - bossStorage_t* ctor(bossStorage_t* thisptr) + enum StorageKind { - cemuLog_logDebug(LogType::Force, "nn_boss_Storage_ctor(0x{:x})", MEMPTR(thisptr).GetMPTR()); - if (!thisptr) - { - // thisptr = new RawDlTaskSetting_t - assert_dbg(); - } + kStorageKind_NBDL, + kStorageKind_RawDl, + }; - if (thisptr) - { - thisptr->titleId.u64 = 0; - thisptr->ukn20 = 0x10000a64; - } + /* +0x00 */ uint32be accountId; + /* +0x04 */ uint32be storageKind; + /* +0x08 */ uint8 ukn08Array[3]; + /* +0x0B */ char storageName[8]; + uint8 ukn13; + uint8 ukn14; + uint8 ukn15; + uint8 ukn16; + uint8 ukn17; + /* +0x18 */ nn::boss::TitleId titleId; + /* +0x20 */ MEMPTR<VTableStorage> vTablePtr; + /* +0x24 */ uint32be ukn24; - return thisptr; + static nn::boss::Storage* ctor1(nn::boss::Storage* _this) // __ct__Q3_2nn4boss7StorageFv + { + if(!_this) + _this = boss_new<nn::boss::Storage>(); + _this->vTablePtr = s_vTable; + _this->titleId.u64 = 0; + return _this; } - void nnBossStorage_prepareTitleId(bossStorage_t* storage) + static void dtor(nn::boss::Storage* _this, uint32 options) // __dt__Q3_2nn4boss7StorageFv + { + cemuLog_logDebug(LogType::Force, "nn::boss::Storage::dtor(0x{:08x}, 0x{:08x})", MEMPTR(_this).GetMPTR(), options); + Finalize(_this); + if(options & 1) + boss_delete(_this); + } + + static void nnBossStorage_prepareTitleId(Storage* storage) { if (storage->titleId.u64 != 0) return; storage->titleId.u64 = CafeSystem::GetForegroundTitleId(); } - Result Initialize(bossStorage_t* thisptr, const char* dirName, uint32 accountId, StorageKind type) + static Result Initialize(Storage* _thisptr, const char* dirName, uint32 accountId, StorageKind type) { if (!dirName) return 0xC0203780; cemuLog_logDebug(LogType::Force, "boss::Storage::Initialize({}, 0x{:08x}, {})", dirName, accountId, type); - thisptr->storageKind = type; - thisptr->titleId.u64 = 0; + _thisptr->storageKind = type; + _thisptr->titleId.u64 = 0; - memset(thisptr->storageName, 0, 0x8); - strncpy(thisptr->storageName, dirName, 0x8); - thisptr->storageName[7] = '\0'; + memset(_thisptr->storageName, 0, 0x8); + strncpy(_thisptr->storageName, dirName, 0x8); + _thisptr->storageName[7] = '\0'; - thisptr->accountId = accountId; + _thisptr->accountId = accountId; - nnBossStorage_prepareTitleId(thisptr); // usually not done like this + nnBossStorage_prepareTitleId(_thisptr); // usually not done like this return 0x200080; } - Result Initialize2(bossStorage_t* thisptr, const char* dirName, StorageKind type) + static Result Initialize2(Storage* _thisptr, const char* dirName, StorageKind type) { - return Initialize(thisptr, dirName, 0, type); + return Initialize(_thisptr, dirName, 0, type); } - } - using Storage_t = Storage::bossStorage_t; - struct AlmightyStorage_t : Storage_t - { - }; - static_assert(sizeof(AlmightyStorage_t) == 0x28); - - namespace AlmightyStorage - { - AlmightyStorage_t* ctor(AlmightyStorage_t* thisptr) + static void Finalize(Storage* _this) { - cemuLog_logDebug(LogType::Force, "nn_boss_AlmightyStorage_ctor(0x{:x})", MEMPTR(thisptr).GetMPTR()); - if (!thisptr) + memset(_this, 0, sizeof(Storage)); // todo - not all fields might be cleared + } + + static Result GetDataList(nn::boss::Storage* storage, DataName* dataList, sint32 maxEntries, uint32be* outputEntryCount, uint32 startIndex) // GetDataList__Q3_2nn4boss7StorageCFPQ3_2nn4boss8DataNameUiPUiT2 + { + // initialize titleId of storage if not already done + nnBossStorage_prepareTitleId(storage); + + cemu_assert_debug(startIndex == 0); // non-zero index is todo + + // load fad.db + BossStorageFadEntry* fadTable = nnBossStorageFad_getTable(storage); + if (fadTable) { - // thisptr = new RawDlTaskSetting_t - assert_dbg(); + sint32 validEntryCount = 0; + for (sint32 i = 0; i < FAD_ENTRY_MAX_COUNT; i++) + { + if( fadTable[i].name[0] == '\0' ) + continue; + memcpy(dataList[validEntryCount].name, fadTable[i].name, 0x20); + validEntryCount++; + if (validEntryCount >= maxEntries) + break; + } + *outputEntryCount = validEntryCount; + free(fadTable); } - - if (thisptr) + else { - Storage::ctor(thisptr); - thisptr->ukn20 = 0x100028a4; + // could not load fad table + *outputEntryCount = 0; } - - return thisptr; + return 0; // todo } - uint32 Initialize(AlmightyStorage_t* thisptr, TitleId_t* titleId, const char* storageName, uint32 accountId, StorageKind storageKind) + static bool Exist(nn::boss::Storage* storage) { - cemuLog_logDebug(LogType::Force, "nn_boss_AlmightyStorage_Initialize(0x{:x})", MEMPTR(thisptr).GetMPTR()); - if (!thisptr) - return 0xc0203780; - - thisptr->accountId = accountId; - thisptr->storageKind = storageKind; - thisptr->titleId.u64 = titleId->u64; - - strncpy(thisptr->storageName, storageName, 8); - thisptr->storageName[0x7] = 0x00; - - return 0x200080; - } - } -} -} - -// Storage - -struct bossDataName_t -{ - char name[32]; -}; - -static_assert(sizeof(bossDataName_t) == 0x20); - -struct bossStorageFadEntry_t -{ - char name[32]; - uint32be fileNameId; - uint32 ukn24; - uint32 ukn28; - uint32 ukn2C; - uint32 ukn30; - uint32be timestampRelated; // guessed -}; - -// __ct__Q3_2nn4boss8DataNameFv -void nnBossDataNameExport_ct(PPCInterpreter_t* hCPU) -{ - ppcDefineParamStructPtr(dataName, bossDataName_t, 0); - memset(dataName, 0, sizeof(bossDataName_t)); - osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(dataName)); -} - -// __opPCc__Q3_2nn4boss8DataNameCFv -void nnBossDataNameExport_opPCc(PPCInterpreter_t* hCPU) -{ - ppcDefineParamStructPtr(dataName, bossDataName_t, 0); - osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(dataName->name)); -} - -void nnBossStorageExport_ct(PPCInterpreter_t* hCPU) -{ - ppcDefineParamStructPtr(storage, nn::boss::Storage::bossStorage_t, 0); - cemuLog_logDebug(LogType::Force, "Constructor for boss storage called"); - // todo - memset(storage, 0, sizeof(nn::boss::Storage::bossStorage_t)); - osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(storage)); -} - -void nnBossStorageExport_exist(PPCInterpreter_t* hCPU) -{ - ppcDefineParamStructPtr(storage, nn::boss::Storage::bossStorage_t, 0); - cemuLog_logDebug(LogType::Force, "nn_boss.Storage_Exist(...) TODO"); - - // todo - osLib_returnFromFunction(hCPU, 1); -} - -#define FAD_ENTRY_MAX_COUNT 512 - -FSCVirtualFile* nnBossStorageFile_open(nn::boss::Storage::bossStorage_t* storage, uint32 fileNameId) -{ - char storageFilePath[1024]; - sprintf(storageFilePath, "/cemuBossStorage/%08x/%08x/user/common/data/%s/%08x", (uint32)(storage->titleId.u64 >> 32), (uint32)(storage->titleId.u64), storage->storageName, fileNameId); - sint32 fscStatus; - FSCVirtualFile* fscStorageFile = fsc_open(storageFilePath, FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION | FSC_ACCESS_FLAG::WRITE_PERMISSION, &fscStatus); - return fscStorageFile; -} - -bossStorageFadEntry_t* nnBossStorageFad_getTable(nn::boss::Storage::bossStorage_t* storage) -{ - const auto accountId = ActiveSettings::GetPersistentId(); - char fadPath[1024]; - sprintf(fadPath, "/cemuBossStorage/%08x/%08x/user/common/%08x/%s/fad.db", (uint32)(storage->titleId.u64 >> 32), (uint32)(storage->titleId.u64), accountId, storage->storageName); - - sint32 fscStatus; - FSCVirtualFile* fscFadFile = fsc_open(fadPath, FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); - if (!fscFadFile) - { - return nullptr; - } - // skip first 8 bytes - fsc_setFileSeek(fscFadFile, 8); - // read entries - bossStorageFadEntry_t* fadTable = (bossStorageFadEntry_t*)malloc(sizeof(bossStorageFadEntry_t)*FAD_ENTRY_MAX_COUNT); - memset(fadTable, 0, sizeof(bossStorageFadEntry_t)*FAD_ENTRY_MAX_COUNT); - fsc_readFile(fscFadFile, fadTable, sizeof(bossStorageFadEntry_t)*FAD_ENTRY_MAX_COUNT); - fsc_close(fscFadFile); - return fadTable; -} - -// Find index of entry by name. Returns -1 if not found -sint32 nnBossStorageFad_getIndexByName(bossStorageFadEntry_t* fadTable, char* name) -{ - for (sint32 i = 0; i < FAD_ENTRY_MAX_COUNT; i++) - { - if (fadTable[i].name[0] == '\0') - continue; - if (strncmp(name, fadTable[i].name, 0x20) == 0) - { - return i; - } - } - return -1; -} - -bool nnBossStorageFad_getEntryByName(nn::boss::Storage::bossStorage_t* storage, char* name, bossStorageFadEntry_t* fadEntry) -{ - bossStorageFadEntry_t* fadTable = nnBossStorageFad_getTable(storage); - if (fadTable) - { - sint32 entryIndex = nnBossStorageFad_getIndexByName(fadTable, name); - if (entryIndex >= 0) - { - memcpy(fadEntry, fadTable + entryIndex, sizeof(bossStorageFadEntry_t)); - free(fadTable); + cemuLog_logDebug(LogType::Force, "nn_boss::Storage::Exist() TODO"); return true; } - free(fadTable); - } - return false; -} -void nnBossStorageExport_getDataList(PPCInterpreter_t* hCPU) -{ - ppcDefineParamStructPtr(storage, nn::boss::Storage::bossStorage_t, 0); - ppcDefineParamStructPtr(dataList, bossDataName_t, 1); - ppcDefineParamS32(maxEntries, 2); - ppcDefineParamU32BEPtr(outputEntryCount, 3); - cemuLog_logDebug(LogType::Force, "boss storage getDataList()"); + /* FAD access */ - // initialize titleId of storage if not already done - nnBossStorage_prepareTitleId(storage); - - // load fad.db - bossStorageFadEntry_t* fadTable = nnBossStorageFad_getTable(storage); - if (fadTable) - { - sint32 validEntryCount = 0; - for (sint32 i = 0; i < FAD_ENTRY_MAX_COUNT; i++) + static FSCVirtualFile* nnBossStorageFile_open(nn::boss::Storage* storage, uint32 fileNameId) { - if( fadTable[i].name[0] == '\0' ) - continue; - memcpy(dataList[validEntryCount].name, fadTable[i].name, 0x20); - validEntryCount++; - if (validEntryCount >= maxEntries) - break; + char storageFilePath[1024]; + sprintf(storageFilePath, "/cemuBossStorage/%08x/%08x/user/common/data/%s/%08x", (uint32)(storage->titleId.u64 >> 32), (uint32)(storage->titleId.u64), storage->storageName, fileNameId); + sint32 fscStatus; + FSCVirtualFile* fscStorageFile = fsc_open(storageFilePath, FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION | FSC_ACCESS_FLAG::WRITE_PERMISSION, &fscStatus); + return fscStorageFile; } - *outputEntryCount = validEntryCount; - free(fadTable); - } - else - { - // could not load fad table - *outputEntryCount = 0; - } - osLib_returnFromFunction(hCPU, 0); // error code -} -// NsData - -typedef struct -{ - /* +0x00 */ char name[0x20]; - /* +0x20 */ nn::boss::Storage::bossStorage_t storage; - /* +0x48 */ uint64 readIndex; - /* +0x50 */ uint32 ukn50; // some pointer to a global struct - /* +0x54 */ uint32 ukn54; -}nsData_t; - -void nnBossNsDataExport_ct(PPCInterpreter_t* hCPU) -{ - cemuLog_logDebug(LogType::Force, "nnBossNsDataExport_ct"); - ppcDefineParamStructPtr(nsData, nsData_t, 0); - if (!nsData) - assert_dbg(); - - - nsData->ukn50 = 0x10000530; - - memset(nsData->name, 0, 0x20); - - nsData->storage.ukn20 = 0x10000798; - nsData->storage.titleId.u64 = 0; - - nsData->readIndex = 0; - - osLib_returnFromFunction(hCPU, memory_getVirtualOffsetFromPointer(nsData)); -} - -void nnBossNsDataExport_initialize(PPCInterpreter_t* hCPU) -{ - ppcDefineParamStructPtr(nsData, nsData_t, 0); - ppcDefineParamStructPtr(storage, nn::boss::Storage::bossStorage_t, 1); - ppcDefineParamStr(dataName, 2); - - if(dataName == nullptr) - { - if (storage->storageKind != 1) + static BossStorageFadEntry* nnBossStorageFad_getTable(nn::boss::Storage* storage) { - osLib_returnFromFunction(hCPU, 0xC0203780); - return; + const auto accountId = ActiveSettings::GetPersistentId(); + char fadPath[1024]; + sprintf(fadPath, "/cemuBossStorage/%08x/%08x/user/common/%08x/%s/fad.db", (uint32)(storage->titleId.u64 >> 32), (uint32)(storage->titleId.u64), accountId, storage->storageName); + + sint32 fscStatus; + FSCVirtualFile* fscFadFile = fsc_open(fadPath, FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); + if (!fscFadFile) + { + return nullptr; + } + // skip first 8 bytes + fsc_setFileSeek(fscFadFile, 8); + // read entries + BossStorageFadEntry* fadTable = (BossStorageFadEntry*)malloc(sizeof(BossStorageFadEntry)*FAD_ENTRY_MAX_COUNT); + memset(fadTable, 0, sizeof(BossStorageFadEntry)*FAD_ENTRY_MAX_COUNT); + fsc_readFile(fscFadFile, fadTable, sizeof(BossStorageFadEntry)*FAD_ENTRY_MAX_COUNT); + fsc_close(fscFadFile); + return fadTable; } - } - - nsData->storage.accountId = storage->accountId; - nsData->storage.storageKind = storage->storageKind; - memcpy(nsData->storage.ukn08Array, storage->ukn08Array, 3); - memcpy(nsData->storage.storageName, storage->storageName, 8); - - nsData->storage.titleId.u64 = storage->titleId.u64; - - nsData->storage = *storage; - - if (dataName != nullptr || storage->storageKind != 1) - strncpy(nsData->name, dataName, 0x20); - else - strncpy(nsData->name, "rawcontent.dat", 0x20); - nsData->name[0x1F] = '\0'; - - nsData->readIndex = 0; - - cemuLog_logDebug(LogType::Force, "nnBossNsDataExport_initialize: {}", nsData->name); - - osLib_returnFromFunction(hCPU, 0x200080); -} - -std::string nnBossNsDataExport_GetPath(nsData_t* nsData) -{ - uint32 accountId = nsData->storage.accountId; - if (accountId == 0) - accountId = iosuAct_getAccountIdOfCurrentAccount(); - - uint64 title_id = nsData->storage.titleId.u64; - if (title_id == 0) - title_id = CafeSystem::GetForegroundTitleId(); - - fs::path path = fmt::format("cemuBossStorage/{:08x}/{:08x}/user/{:08x}", (uint32)(title_id >> 32), (uint32)(title_id & 0xFFFFFFFF), accountId); - path /= nsData->storage.storageName; - path /= nsData->name; - return path.string(); -} - -void nnBossNsDataExport_DeleteRealFileWithHistory(PPCInterpreter_t* hCPU) -{ - ppcDefineParamStructPtr(nsData, nsData_t, 0); - cemuLog_logDebug(LogType::Force, "nn_boss.NsData_DeleteRealFileWithHistory(...)"); - - if (nsData->storage.storageKind == nn::boss::kStorageKind_NBDL) - { - // todo - cemuLog_log(LogType::Force, "BOSS NBDL: Unsupported delete"); - } - else - { - sint32 fscStatus = FSC_STATUS_OK; - std::string filePath = nnBossNsDataExport_GetPath(nsData).c_str(); - fsc_remove((char*)filePath.c_str(), &fscStatus); - if (fscStatus != 0) - cemuLog_log(LogType::Force, "Unhandeled FSC status in BOSS DeleteRealFileWithHistory()"); - } - osLib_returnFromFunction(hCPU, 0); -} - -void nnBossNsDataExport_Exist(PPCInterpreter_t* hCPU) -{ - cemuLog_logDebug(LogType::Force, "nn_boss.NsData_Exist(...)"); - ppcDefineParamStructPtr(nsData, nsData_t, 0); - - bool fileExists = false; - if(nsData->storage.storageKind == nn::boss::kStorageKind_NBDL) - { - // check if name is present in fad table - bossStorageFadEntry_t* fadTable = nnBossStorageFad_getTable(&nsData->storage); - if (fadTable) + // Find index of entry by name. Returns -1 if not found + static sint32 nnBossStorageFad_getIndexByName(BossStorageFadEntry* fadTable, char* name) { - fileExists = nnBossStorageFad_getIndexByName(fadTable, nsData->name) >= 0; - cemuLog_logDebug(LogType::Force, "\t({}) -> {}", nsData->name, fileExists); - free(fadTable); + for (sint32 i = 0; i < FAD_ENTRY_MAX_COUNT; i++) + { + if (fadTable[i].name[0] == '\0') + continue; + if (strncmp(name, fadTable[i].name, 0x20) == 0) + { + return i; + } + } + return -1; } - } - else - { - sint32 fscStatus; - auto fscStorageFile = fsc_open((char*)nnBossNsDataExport_GetPath(nsData).c_str(), FSC_ACCESS_FLAG::OPEN_FILE, &fscStatus); - if (fscStorageFile != nullptr) + + static bool nnBossStorageFad_getEntryByName(nn::boss::Storage* storage, char* name, BossStorageFadEntry* fadEntry) { - fileExists = true; + BossStorageFadEntry* fadTable = nnBossStorageFad_getTable(storage); + if (fadTable) + { + sint32 entryIndex = nnBossStorageFad_getIndexByName(fadTable, name); + if (entryIndex >= 0) + { + memcpy(fadEntry, fadTable + entryIndex, sizeof(BossStorageFadEntry)); + free(fadTable); + return true; + } + free(fadTable); + } + return false; + } + + static void InitVTable() + { + s_vTable->rtti.ptr = nullptr; // todo + s_vTable->dtor.ptr = DTOR_WRAPPER(Storage); + } + }; + + static_assert(sizeof(Storage) == 0x28); + static_assert(offsetof(Storage, storageKind) == 0x04); + static_assert(offsetof(Storage, ukn08Array) == 0x08); + static_assert(offsetof(Storage, storageName) == 0x0B); + static_assert(offsetof(Storage, titleId) == 0x18); + + struct AlmightyStorage : Storage + { + struct VTableAlmightyStorage : public VTableStorage + { + VTableEntry rttiStorage; + }; + static_assert(sizeof(VTableAlmightyStorage) == 8*3); + static inline SysAllocator<VTableAlmightyStorage> s_VTable; + + static AlmightyStorage* ctor(AlmightyStorage* _thisptr) + { + cemuLog_logDebug(LogType::Force, "nn_boss_AlmightyStorage_ctor(0x{:x})", MEMPTR(_thisptr).GetMPTR()); + if (!_thisptr) + _thisptr = boss_new<AlmightyStorage>(); + Storage::ctor1(_thisptr); + _thisptr->vTablePtr = s_VTable; + return _thisptr; + } + + static uint32 Initialize(AlmightyStorage* _thisptr, TitleId* titleId, const char* storageName, uint32 accountId, StorageKind storageKind) + { + cemuLog_logDebug(LogType::Force, "nn_boss_AlmightyStorage_Initialize(0x{:x})", MEMPTR(_thisptr).GetMPTR()); + if (!_thisptr) + return 0xc0203780; + + _thisptr->accountId = accountId; + _thisptr->storageKind = storageKind; + _thisptr->titleId.u64 = titleId->u64; + + strncpy(_thisptr->storageName, storageName, 8); + _thisptr->storageName[0x7] = 0x00; + + return 0x200080; + } + + static void InitVTable() + { + s_VTable->rtti.ptr = nullptr; // todo + s_VTable->dtor.ptr = DTOR_WRAPPER(AlmightyStorage); + s_VTable->rttiStorage.ptr = nullptr; // todo + } + }; + static_assert(sizeof(AlmightyStorage) == 0x28); + + // NsData + + struct NsData + { + struct VTableNsData + { + VTableEntry rtti; + VTableEntry dtor; + }; + static inline SysAllocator<VTableNsData> s_vTable; + + /* +0x00 */ char name[0x20]; // DataName ? + /* +0x20 */ nn::boss::Storage storage; + /* +0x48 */ uint64 readIndex; + /* +0x50 */ MEMPTR<void> vTablePtr; + /* +0x54 */ uint32 ukn54; + + static NsData* ctor(NsData* _this) + { + if (!_this) + _this = boss_new<NsData>(); + _this->vTablePtr = s_vTable; + memset(_this->name, 0, sizeof(_this->name)); + _this->storage.ctor1(&_this->storage); + _this->readIndex = 0; + return _this; + } + + static void dtor(NsData* _this, uint32 options) // __dt__Q3_2nn4boss6NsDataFv + { + _this->storage.dtor(&_this->storage, 0); + // todo + if(options & 1) + boss_delete(_this); + } + + static Result Initialize(NsData* _this, nn::boss::Storage* storage, const char* dataName) + { + if(dataName == nullptr) + { + if (storage->storageKind != 1) + { + return 0xC0203780; + } + } + + _this->storage.accountId = storage->accountId; + _this->storage.storageKind = storage->storageKind; + + memcpy(_this->storage.ukn08Array, storage->ukn08Array, 3); + memcpy(_this->storage.storageName, storage->storageName, 8); + + _this->storage.titleId.u64 = storage->titleId.u64; + + _this->storage = *storage; + + if (dataName != nullptr || storage->storageKind != 1) + strncpy(_this->name, dataName, 0x20); + else + strncpy(_this->name, "rawcontent.dat", 0x20); + _this->name[0x1F] = '\0'; + + _this->readIndex = 0; + + cemuLog_logDebug(LogType::Force, "initialize: {}", _this->name); + + return 0x200080; + } + + static std::string _GetPath(NsData* nsData) + { + uint32 accountId = nsData->storage.accountId; + if (accountId == 0) + accountId = iosuAct_getAccountIdOfCurrentAccount(); + + uint64 title_id = nsData->storage.titleId.u64; + if (title_id == 0) + title_id = CafeSystem::GetForegroundTitleId(); + + fs::path path = fmt::format("cemuBossStorage/{:08x}/{:08x}/user/{:08x}", (uint32)(title_id >> 32), (uint32)(title_id & 0xFFFFFFFF), accountId); + path /= nsData->storage.storageName; + path /= nsData->name; + return path.string(); + } + + static Result DeleteRealFileWithHistory(NsData* nsData) + { + if (nsData->storage.storageKind == nn::boss::Storage::kStorageKind_NBDL) + { + // todo + cemuLog_log(LogType::Force, "BOSS NBDL: Unsupported delete"); + } + else + { + sint32 fscStatus = FSC_STATUS_OK; + std::string filePath = _GetPath(nsData).c_str(); + fsc_remove((char*)filePath.c_str(), &fscStatus); + if (fscStatus != 0) + cemuLog_log(LogType::Force, "Unhandeled FSC status in BOSS DeleteRealFileWithHistory()"); + } + return 0; + } + + static uint32 Exist(NsData* nsData) + { + bool fileExists = false; + if(nsData->storage.storageKind == nn::boss::Storage::kStorageKind_NBDL) + { + // check if name is present in fad table + BossStorageFadEntry* fadTable = nn::boss::Storage::nnBossStorageFad_getTable(&nsData->storage); + if (fadTable) + { + fileExists = nn::boss::Storage::nnBossStorageFad_getIndexByName(fadTable, nsData->name) >= 0; + cemuLog_logDebug(LogType::Force, "\t({}) -> {}", nsData->name, fileExists); + free(fadTable); + } + } + else + { + sint32 fscStatus; + auto fscStorageFile = fsc_open(_GetPath(nsData).c_str(), FSC_ACCESS_FLAG::OPEN_FILE, &fscStatus); + if (fscStorageFile != nullptr) + { + fileExists = true; + fsc_close(fscStorageFile); + } + } + return fileExists?1:0; + } + + static uint64 GetSize(NsData* nsData) + { + FSCVirtualFile* fscStorageFile = nullptr; + if (nsData->storage.storageKind == nn::boss::Storage::kStorageKind_NBDL) + { + BossStorageFadEntry fadEntry; + if (nn::boss::Storage::nnBossStorageFad_getEntryByName(&nsData->storage, nsData->name, &fadEntry) == false) + { + cemuLog_log(LogType::Force, "BOSS storage cant find file {}", nsData->name); + return 0; + } + // open file + fscStorageFile = nn::boss::Storage::nnBossStorageFile_open(&nsData->storage, fadEntry.fileNameId); + } + else + { + sint32 fscStatus; + fscStorageFile = fsc_open(_GetPath(nsData).c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); + } + + if (fscStorageFile == nullptr) + { + cemuLog_log(LogType::Force, "BOSS storage cant open file alias {}", nsData->name); + return 0; + } + + // get size + const sint32 fileSize = fsc_getFileSize(fscStorageFile); + // close file fsc_close(fscStorageFile); + return fileSize; } - } - osLib_returnFromFunction(hCPU, fileExists?1:0); -} - -void nnBossNsDataExport_getSize(PPCInterpreter_t* hCPU) -{ - ppcDefineParamStructPtr(nsData, nsData_t, 0); - - FSCVirtualFile* fscStorageFile = nullptr; - if (nsData->storage.storageKind == nn::boss::kStorageKind_NBDL) - { - bossStorageFadEntry_t fadEntry; - if (nnBossStorageFad_getEntryByName(&nsData->storage, nsData->name, &fadEntry) == false) + static uint64 GetCreatedTime(NsData* nsData) { - cemuLog_log(LogType::Force, "BOSS storage cant find file {}", nsData->name); - osLib_returnFromFunction(hCPU, 0); - return; + cemuLog_logDebug(LogType::Force, "nn_boss.NsData_GetCreatedTime() not implemented. Returning 0"); + uint64 createdTime = 0; + return createdTime; } - // open file - fscStorageFile = nnBossStorageFile_open(&nsData->storage, fadEntry.fileNameId); - } - else - { - sint32 fscStatus; - fscStorageFile = fsc_open((char*)nnBossNsDataExport_GetPath(nsData).c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); - } - if (fscStorageFile == nullptr) - { - cemuLog_log(LogType::Force, "BOSS storage cant open file alias {}", nsData->name); - osLib_returnFromFunction(hCPU, 0); - return; - } - - // get size - const sint32 fileSize = fsc_getFileSize(fscStorageFile); - // close file - fsc_close(fscStorageFile); - osLib_returnFromFunction64(hCPU, fileSize); -} - -uint64 nnBossNsData_GetCreatedTime(nsData_t* nsData) -{ - cemuLog_logDebug(LogType::Force, "nn_boss.NsData_GetCreatedTime() not implemented. Returning 0"); - uint64 createdTime = 0; - return createdTime; -} - -uint32 nnBossNsData_read(nsData_t* nsData, uint64* sizeOutBE, void* buffer, sint32 length) -{ - FSCVirtualFile* fscStorageFile = nullptr; - if (nsData->storage.storageKind == nn::boss::kStorageKind_NBDL) - { - bossStorageFadEntry_t fadEntry; - if (nnBossStorageFad_getEntryByName(&nsData->storage, nsData->name, &fadEntry) == false) + static uint32 nnBossNsData_read(NsData* nsData, uint64be* sizeOutBE, void* buffer, sint32 length) { - cemuLog_log(LogType::Force, "BOSS storage cant find file {} for reading", nsData->name); - return 0x80000000; // todo - proper error code + FSCVirtualFile* fscStorageFile = nullptr; + if (nsData->storage.storageKind == nn::boss::Storage::kStorageKind_NBDL) + { + BossStorageFadEntry fadEntry; + if (nn::boss::Storage::nnBossStorageFad_getEntryByName(&nsData->storage, nsData->name, &fadEntry) == false) + { + cemuLog_log(LogType::Force, "BOSS storage cant find file {} for reading", nsData->name); + return 0x80000000; // todo - proper error code + } + // open file + fscStorageFile = nn::boss::Storage::nnBossStorageFile_open(&nsData->storage, fadEntry.fileNameId); + } + else + { + sint32 fscStatus; + fscStorageFile = fsc_open(_GetPath(nsData).c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); + } + + if (!fscStorageFile) + { + cemuLog_log(LogType::Force, "BOSS storage cant open file alias {} for reading", nsData->name); + return 0x80000000; // todo - proper error code + } + // get size + sint32 fileSize = fsc_getFileSize(fscStorageFile); + // verify read is within bounds + sint32 readEndOffset = (sint32)_swapEndianU64(nsData->readIndex) + length; + sint32 readBytes = length; + if (readEndOffset > fileSize) + { + readBytes = fileSize - (sint32)_swapEndianU64(nsData->readIndex); + cemu_assert_debug(readBytes != 0); + } + // read + fsc_setFileSeek(fscStorageFile, (uint32)_swapEndianU64(nsData->readIndex)); + fsc_readFile(fscStorageFile, buffer, readBytes); + nsData->readIndex = _swapEndianU64((sint32)_swapEndianU64(nsData->readIndex) + readBytes); + + // close file + fsc_close(fscStorageFile); + if (sizeOutBE) + *sizeOutBE = readBytes; + return 0; } - // open file - fscStorageFile = nnBossStorageFile_open(&nsData->storage, fadEntry.fileNameId); - } - else - { - sint32 fscStatus; - fscStorageFile = fsc_open((char*)nnBossNsDataExport_GetPath(nsData).c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); - } - - if (!fscStorageFile) - { - cemuLog_log(LogType::Force, "BOSS storage cant open file alias {} for reading", nsData->name); - return 0x80000000; // todo - proper error code - } - // get size - sint32 fileSize = fsc_getFileSize(fscStorageFile); - // verify read is within bounds - sint32 readEndOffset = (sint32)_swapEndianU64(nsData->readIndex) + length; - sint32 readBytes = length; - if (readEndOffset > fileSize) - { - readBytes = fileSize - (sint32)_swapEndianU64(nsData->readIndex); - cemu_assert_debug(readBytes != 0); - } - // read - fsc_setFileSeek(fscStorageFile, (uint32)_swapEndianU64(nsData->readIndex)); - fsc_readFile(fscStorageFile, buffer, readBytes); - nsData->readIndex = _swapEndianU64((sint32)_swapEndianU64(nsData->readIndex) + readBytes); - - // close file - fsc_close(fscStorageFile); - if (sizeOutBE) - *sizeOutBE = _swapEndianU64(readBytes); - return 0; -} #define NSDATA_SEEK_MODE_BEGINNING (0) -uint32 nnBossNsData_seek(nsData_t* nsData, uint64 seek, uint32 mode) -{ - FSCVirtualFile* fscStorageFile = nullptr; - if (nsData->storage.storageKind == nn::boss::kStorageKind_NBDL) - { - bossStorageFadEntry_t fadEntry; - if (nnBossStorageFad_getEntryByName(&nsData->storage, nsData->name, &fadEntry) == false) + static uint32 nnBossNsData_seek(NsData* nsData, uint64 seek, uint32 mode) { - cemuLog_log(LogType::Force, "BOSS storage cant find file {} for reading", nsData->name); - return 0x80000000; // todo - proper error code + FSCVirtualFile* fscStorageFile = nullptr; + if (nsData->storage.storageKind == nn::boss::Storage::kStorageKind_NBDL) + { + BossStorageFadEntry fadEntry; + if (nn::boss::Storage::nnBossStorageFad_getEntryByName(&nsData->storage, nsData->name, &fadEntry) == false) + { + cemuLog_log(LogType::Force, "BOSS storage cant find file {} for reading", nsData->name); + return 0x80000000; // todo - proper error code + } + // open file + fscStorageFile = nn::boss::Storage::nnBossStorageFile_open(&nsData->storage, fadEntry.fileNameId); + } + else + { + sint32 fscStatus; + fscStorageFile = fsc_open(_GetPath(nsData).c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); + } + + if (fscStorageFile == nullptr) + { + cemuLog_log(LogType::Force, "BOSS storage cant open file alias {} for reading", nsData->name); + return 0x80000000; // todo - proper error code + } + // get size + sint32 fileSize = fsc_getFileSize(fscStorageFile); + // handle seek + if (mode == NSDATA_SEEK_MODE_BEGINNING) + { + seek = std::min(seek, (uint64)fileSize); + nsData->readIndex = _swapEndianU64((uint64)seek); + } + else + { + cemu_assert_unimplemented(); + } + fsc_close(fscStorageFile); + return 0; } - // open file - fscStorageFile = nnBossStorageFile_open(&nsData->storage, fadEntry.fileNameId); - } - else - { - sint32 fscStatus; - fscStorageFile = fsc_open((char*)nnBossNsDataExport_GetPath(nsData).c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); - } - if (fscStorageFile == nullptr) - { - cemuLog_log(LogType::Force, "BOSS storage cant open file alias {} for reading", nsData->name); - return 0x80000000; // todo - proper error code - } - // get size - sint32 fileSize = fsc_getFileSize(fscStorageFile); - // handle seek - if (mode == NSDATA_SEEK_MODE_BEGINNING) - { - seek = std::min(seek, (uint64)fileSize); - nsData->readIndex = _swapEndianU64((uint64)seek); - } - else - { - cemu_assert_unimplemented(); - } - fsc_close(fscStorageFile); - return 0; + static sint32 Read(NsData* nsData, uint8* buffer, sint32 length) + { + cemuLog_logDebug(LogType::Force, "nsData read (filename {})", nsData->name); + return nnBossNsData_read(nsData, nullptr, buffer, length); + } + + static sint32 ReadWithSizeOut(NsData* nsData, uint64be* sizeOut, uint8* buffer, sint32 length) + { + uint32 r = nnBossNsData_read(nsData, sizeOut, buffer, length); + cemuLog_logDebug(LogType::Force, "nsData readWithSizeOut (filename {} length 0x{:x}) Result: {} Sizeout: {:x}", nsData->name, length, r, _swapEndianU64(*sizeOut)); + return r; + } + + static Result Seek(NsData* nsData, uint64 seekPos, uint32 mode) + { + uint32 r = nnBossNsData_seek(nsData, seekPos, mode); + cemuLog_logDebug(LogType::Force, "nsData seek (filename {} seek 0x{:x}) Result: {}", nsData->name, (uint32)seekPos, r); + return r; + } + + static void InitVTable() + { + s_vTable->rtti.ptr = nullptr; // todo + s_vTable->dtor.ptr = DTOR_WRAPPER(NsData); + } + }; + static_assert(sizeof(NsData) == 0x58); + +} } - -void nnBossNsDataExport_read(PPCInterpreter_t* hCPU) -{ - ppcDefineParamStructPtr(nsData, nsData_t, 0); - ppcDefineParamStr(buffer, 1); - ppcDefineParamS32(length, 2); - - cemuLog_logDebug(LogType::Force, "nsData read (filename {})", nsData->name); - - uint32 r = nnBossNsData_read(nsData, nullptr, buffer, length); - - osLib_returnFromFunction(hCPU, r); -} - -void nnBossNsDataExport_readWithSizeOut(PPCInterpreter_t* hCPU) -{ - ppcDefineParamStructPtr(nsData, nsData_t, 0); - ppcDefineParamTypePtr(sizeOut, uint64, 1); - ppcDefineParamStr(buffer, 2); - ppcDefineParamS32(length, 3); - - uint32 r = nnBossNsData_read(nsData, sizeOut, buffer, length); - cemuLog_logDebug(LogType::Force, "nsData readWithSizeOut (filename {} length 0x{:x}) Result: {} Sizeout: {:x}", nsData->name, length, r, _swapEndianU64(*sizeOut)); - - osLib_returnFromFunction(hCPU, r); -} - -void nnBossNsDataExport_seek(PPCInterpreter_t* hCPU) -{ - ppcDefineParamStructPtr(nsData, nsData_t, 0); - ppcDefineParamU64(seekPos, 2); - ppcDefineParamU32(mode, 4); - - uint32 r = nnBossNsData_seek(nsData, seekPos, mode); - - cemuLog_logDebug(LogType::Force, "nsData seek (filename {} seek 0x{:x}) Result: {}", nsData->name, (uint32)seekPos, r); - - osLib_returnFromFunction(hCPU, r); -} - void nnBoss_load() { OSInitMutexEx(&nn::boss::g_mutex, nullptr); - osLib_addFunction("nn_boss", "Initialize__Q2_2nn4bossFv", nn::boss::export_Initialize); - osLib_addFunction("nn_boss", "GetBossState__Q2_2nn4bossFv", nn::boss::export_GetBossState); - + nn::boss::g_initCounter = 0; + nn::boss::g_isInitialized = false; + + cafeExportRegisterFunc(nn::boss::GetBossState, "nn_boss", "GetBossState__Q2_2nn4bossFv", LogType::NN_BOSS); + + // boss lib + cafeExportRegisterFunc(nn::boss::Initialize, "nn_boss", "Initialize__Q2_2nn4bossFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::IsInitialized, "nn_boss", "IsInitialized__Q2_2nn4bossFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Finalize, "nn_boss", "Finalize__Q2_2nn4bossFv", LogType::NN_BOSS); + // task - osLib_addFunction("nn_boss", "__ct__Q3_2nn4boss4TaskFv", nn::boss::Task::export_ctor); - osLib_addFunction("nn_boss", "Run__Q3_2nn4boss4TaskFb", nn::boss::Task::export_Run); - osLib_addFunction("nn_boss", "Wait__Q3_2nn4boss4TaskFUiQ3_2nn4boss13TaskWaitState", nn::boss::Task::export_Wait); - osLib_addFunction("nn_boss", "GetTurnState__Q3_2nn4boss4TaskCFPUi", nn::boss::Task::export_GetTurnState); - osLib_addFunction("nn_boss", "GetHttpStatusCode__Q3_2nn4boss4TaskCFPUi", nn::boss::Task::export_GetHttpStatusCode); - osLib_addFunction("nn_boss", "GetContentLength__Q3_2nn4boss4TaskCFPUi", nn::boss::Task::export_GetContentLength); - osLib_addFunction("nn_boss", "GetProcessedLength__Q3_2nn4boss4TaskCFPUi", nn::boss::Task::export_GetProcessedLength); - osLib_addFunction("nn_boss", "Register__Q3_2nn4boss4TaskFRQ3_2nn4boss11TaskSetting", nn::boss::Task::export_Register); - osLib_addFunction("nn_boss", "Unregister__Q3_2nn4boss4TaskFv", nn::boss::Task::export_Unregister); - osLib_addFunction("nn_boss", "Initialize__Q3_2nn4boss4TaskFPCc", nn::boss::Task::export_Initialize1); - osLib_addFunction("nn_boss", "Initialize__Q3_2nn4boss4TaskFUcPCc", nn::boss::Task::export_Initialize2); - osLib_addFunction("nn_boss", "Initialize__Q3_2nn4boss4TaskFPCcUi", nn::boss::Task::export_Initialize3); - osLib_addFunction("nn_boss", "IsRegistered__Q3_2nn4boss4TaskCFv", nn::boss::Task::export_IsRegistered); - osLib_addFunction("nn_boss", "RegisterForImmediateRun__Q3_2nn4boss4TaskFRCQ3_2nn4boss11TaskSetting", nn::boss::Task::export_RegisterForImmediateRun); - osLib_addFunction("nn_boss", "StartScheduling__Q3_2nn4boss4TaskFb", nn::boss::Task::export_StartScheduling); - osLib_addFunction("nn_boss", "StopScheduling__Q3_2nn4boss4TaskFv", nn::boss::Task::export_StopScheduling); + nn::boss::Task::InitVTable(); + cafeExportRegisterFunc(nn::boss::Task::ctor1, "nn_boss", "__ct__Q3_2nn4boss4TaskFUcPCc", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::ctor2, "nn_boss", "__ct__Q3_2nn4boss4TaskFPCcUi", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::ctor3, "nn_boss", "__ct__Q3_2nn4boss4TaskFPCc", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::ctor4, "nn_boss", "__ct__Q3_2nn4boss4TaskFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::dtor, "nn_boss", "__dt__Q3_2nn4boss4TaskFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::Initialize1, "nn_boss", "Initialize__Q3_2nn4boss4TaskFPCcUi", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::Initialize2, "nn_boss", "Initialize__Q3_2nn4boss4TaskFUcPCc", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::Initialize3, "nn_boss", "Initialize__Q3_2nn4boss4TaskFPCc", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::Run, "nn_boss", "Run__Q3_2nn4boss4TaskFb", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::Wait, "nn_boss", "Wait__Q3_2nn4boss4TaskFUiQ3_2nn4boss13TaskWaitState", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::GetTurnState, "nn_boss", "GetTurnState__Q3_2nn4boss4TaskCFPUi", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::GetHttpStatusCode, "nn_boss", "GetHttpStatusCode__Q3_2nn4boss4TaskCFPUi", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::GetContentLength, "nn_boss", "GetContentLength__Q3_2nn4boss4TaskCFPUi", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::GetProcessedLength, "nn_boss", "GetProcessedLength__Q3_2nn4boss4TaskCFPUi", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::Register, "nn_boss", "Register__Q3_2nn4boss4TaskFRQ3_2nn4boss11TaskSetting", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::Unregister, "nn_boss", "Unregister__Q3_2nn4boss4TaskFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::IsRegistered, "nn_boss", "IsRegistered__Q3_2nn4boss4TaskCFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::RegisterForImmediateRun, "nn_boss", "RegisterForImmediateRun__Q3_2nn4boss4TaskFRCQ3_2nn4boss11TaskSetting", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::StartScheduling, "nn_boss", "StartScheduling__Q3_2nn4boss4TaskFb", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Task::StopScheduling, "nn_boss", "StopScheduling__Q3_2nn4boss4TaskFv", LogType::NN_BOSS); - // Nbdl task setting - osLib_addFunction("nn_boss", "__ct__Q3_2nn4boss15NbdlTaskSettingFv", nn::boss::NbdlTaskSetting::export_ctor); - osLib_addFunction("nn_boss", "Initialize__Q3_2nn4boss15NbdlTaskSettingFPCcLT1", nn::boss::NbdlTaskSetting::export_Initialize); - //osLib_addFunction("nn_boss", "SetFileName__Q3_2nn4boss15NbdlTaskSettingFPCc", nn::boss::NbdlTaskSetting::export_SetFileName); - cafeExportRegisterFunc(nn::boss::NbdlTaskSetting::SetFileName, "nn_boss", "SetFileName__Q3_2nn4boss15NbdlTaskSettingFPCc", LogType::Placeholder); + // TaskSetting + nn::boss::TaskSetting::InitVTable(); + cafeExportRegisterFunc(nn::boss::TaskSetting::ctor, "nn_boss", "__ct__Q3_2nn4boss11TaskSettingFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::TaskSetting::dtor, "nn_boss", "__dt__Q3_2nn4boss11TaskSettingFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::TaskSetting::IsPrivileged, "nn_boss", "Initialize__Q3_2nn4boss11TaskSettingFPCcUi", LogType::NN_BOSS); + + // NbdlTaskSetting + nn::boss::NbdlTaskSetting::InitVTable(); + cafeExportRegisterFunc(nn::boss::NbdlTaskSetting::ctor, "nn_boss", "__ct__Q3_2nn4boss15NbdlTaskSettingFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NbdlTaskSetting::dtor, "nn_boss", "__dt__Q3_2nn4boss15NbdlTaskSettingFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NbdlTaskSetting::Initialize, "nn_boss", "Initialize__Q3_2nn4boss15NbdlTaskSettingFPCcLT1", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NbdlTaskSetting::SetFileName, "nn_boss", "SetFileName__Q3_2nn4boss15NbdlTaskSettingFPCc", LogType::NN_BOSS); - // play task setting - osLib_addFunction("nn_boss", "__ct__Q3_2nn4boss17PlayReportSettingFv", nn::boss::PlayReportSetting::export_ctor); - osLib_addFunction("nn_boss", "Set__Q3_2nn4boss17PlayReportSettingFPCcUi", nn::boss::PlayReportSetting::export_Set); - //osLib_addFunction("nn_boss", "Set__Q3_2nn4boss17PlayReportSettingFUiT1", nn::boss::PlayReportSetting::export_Set); - osLib_addFunction("nn_boss", "Initialize__Q3_2nn4boss17PlayReportSettingFPvUi", nn::boss::PlayReportSetting::export_Initialize); + // PlayReportSetting + nn::boss::PlayReportSetting::InitVTable(); + cafeExportRegisterFunc(nn::boss::PlayReportSetting::ctor, "nn_boss", "__ct__Q3_2nn4boss17PlayReportSettingFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::PlayReportSetting::dtor, "nn_boss", "__dt__Q3_2nn4boss17PlayReportSettingFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::PlayReportSetting::Initialize, "nn_boss", "Initialize__Q3_2nn4boss17PlayReportSettingFPvUi", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::PlayReportSetting::Set, "nn_boss", "Set__Q3_2nn4boss17PlayReportSettingFPCcUi", LogType::NN_BOSS); - cafeExportRegisterFunc(nn::boss::RawDlTaskSetting::ctor, "nn_boss", "__ct__Q3_2nn4boss16RawDlTaskSettingFv", LogType::Placeholder); - cafeExportRegisterFunc(nn::boss::RawDlTaskSetting::Initialize, "nn_boss", "Initialize__Q3_2nn4boss16RawDlTaskSettingFPCcbT2N21", LogType::Placeholder); + // RawDlTaskSetting + nn::boss::RawDlTaskSetting::InitVTable(); + cafeExportRegisterFunc(nn::boss::RawDlTaskSetting::ctor, "nn_boss", "__ct__Q3_2nn4boss16RawDlTaskSettingFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::RawDlTaskSetting::dtor, "nn_boss", "__dt__Q3_2nn4boss16RawDlTaskSettingFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::RawDlTaskSetting::Initialize, "nn_boss", "Initialize__Q3_2nn4boss16RawDlTaskSettingFPCcbT2N21", LogType::NN_BOSS); - cafeExportRegisterFunc(nn::boss::NetTaskSetting::SetServiceToken, "nn_boss", "SetServiceToken__Q3_2nn4boss14NetTaskSettingFPCUc", LogType::Placeholder); - cafeExportRegisterFunc(nn::boss::NetTaskSetting::AddInternalCaCert, "nn_boss", "AddInternalCaCert__Q3_2nn4boss14NetTaskSettingFSc", LogType::Placeholder); - cafeExportRegisterFunc(nn::boss::NetTaskSetting::SetInternalClientCert, "nn_boss", "SetInternalClientCert__Q3_2nn4boss14NetTaskSettingFSc", LogType::Placeholder); + // NetTaskSetting + nn::boss::NetTaskSetting::InitVTable(); + cafeExportRegisterFunc(nn::boss::NetTaskSetting::ctor, "nn_boss", "__ct__Q3_2nn4boss14NetTaskSettingFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NetTaskSetting::dtor, "nn_boss", "__dt__Q3_2nn4boss14NetTaskSettingFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NetTaskSetting::SetServiceToken, "nn_boss", "SetServiceToken__Q3_2nn4boss14NetTaskSettingFPCUc", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NetTaskSetting::AddInternalCaCert, "nn_boss", "AddInternalCaCert__Q3_2nn4boss14NetTaskSettingFSc", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NetTaskSetting::SetInternalClientCert, "nn_boss", "SetInternalClientCert__Q3_2nn4boss14NetTaskSettingFSc", LogType::NN_BOSS); // Title - cafeExportRegisterFunc(nn::boss::Title::ctor, "nn_boss", "__ct__Q3_2nn4boss5TitleFv", LogType::Placeholder); + nn::boss::Title::InitVTable(); + cafeExportRegisterFunc(nn::boss::Title::ctor, "nn_boss", "__ct__Q3_2nn4boss5TitleFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Title::dtor, "nn_boss", "__dt__Q3_2nn4boss5TitleFv", LogType::NN_BOSS); // cafeExportMakeWrapper<nn::boss::Title::SetNewArrivalFlagOff>("nn_boss", "SetNewArrivalFlagOff__Q3_2nn4boss5TitleFv"); SMM bookmarks // TitleId - cafeExportRegisterFunc(nn::boss::TitleId::ctor1, "nn_boss", "__ct__Q3_2nn4boss7TitleIDFv", LogType::Placeholder); - cafeExportRegisterFunc(nn::boss::TitleId::ctor2, "nn_boss", "__ct__Q3_2nn4boss7TitleIDFUL", LogType::Placeholder); - cafeExportRegisterFunc(nn::boss::TitleId::cctor, "nn_boss", "__ct__Q3_2nn4boss7TitleIDFRCQ3_2nn4boss7TitleID", LogType::Placeholder); - cafeExportRegisterFunc(nn::boss::TitleId::operator_ne, "nn_boss", "__ne__Q3_2nn4boss7TitleIDCFRCQ3_2nn4boss7TitleID", LogType::Placeholder); + cafeExportRegisterFunc(nn::boss::TitleId::ctor1, "nn_boss", "__ct__Q3_2nn4boss7TitleIDFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::TitleId::ctor2, "nn_boss", "__ct__Q3_2nn4boss7TitleIDFUL", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::TitleId::ctor3, "nn_boss", "__ct__Q3_2nn4boss7TitleIDFRCQ3_2nn4boss7TitleID", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::TitleId::operator_ne, "nn_boss", "__ne__Q3_2nn4boss7TitleIDCFRCQ3_2nn4boss7TitleID", LogType::NN_BOSS); // DataName - osLib_addFunction("nn_boss", "__ct__Q3_2nn4boss8DataNameFv", nnBossDataNameExport_ct); - osLib_addFunction("nn_boss", "__opPCc__Q3_2nn4boss8DataNameCFv", nnBossDataNameExport_opPCc); + cafeExportRegisterFunc(nn::boss::DataName::ctor, "nn_boss", "__ct__Q3_2nn4boss8DataNameFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::DataName::operator_const_char, "nn_boss", "__opPCc__Q3_2nn4boss8DataNameCFv", LogType::NN_BOSS); // DirectoryName - cafeExportRegisterFunc(nn::boss::DirectoryName::ctor, "nn_boss", "__ct__Q3_2nn4boss13DirectoryNameFv", LogType::Placeholder); - cafeExportRegisterFunc(nn::boss::DirectoryName::operator_const_char, "nn_boss", "__opPCc__Q3_2nn4boss13DirectoryNameCFv", LogType::Placeholder); + cafeExportRegisterFunc(nn::boss::DirectoryName::ctor, "nn_boss", "__ct__Q3_2nn4boss13DirectoryNameFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::DirectoryName::operator_const_char, "nn_boss", "__opPCc__Q3_2nn4boss13DirectoryNameCFv", LogType::NN_BOSS); // Account - cafeExportRegisterFunc(nn::boss::Account::ctor, "nn_boss", "__ct__Q3_2nn4boss7AccountFUi", LogType::Placeholder); - - - // storage - osLib_addFunction("nn_boss", "__ct__Q3_2nn4boss7StorageFv", nnBossStorageExport_ct); - //osLib_addFunction("nn_boss", "Initialize__Q3_2nn4boss7StorageFPCcQ3_2nn4boss11StorageKind", nnBossStorageExport_initialize); - osLib_addFunction("nn_boss", "Exist__Q3_2nn4boss7StorageCFv", nnBossStorageExport_exist); - osLib_addFunction("nn_boss", "GetDataList__Q3_2nn4boss7StorageCFPQ3_2nn4boss8DataNameUiPUiT2", nnBossStorageExport_getDataList); - osLib_addFunction("nn_boss", "GetDataList__Q3_2nn4boss7StorageCFPQ3_2nn4boss8DataNameUiPUiT2", nnBossStorageExport_getDataList); - cafeExportRegisterFunc(nn::boss::Storage::Initialize, "nn_boss", "Initialize__Q3_2nn4boss7StorageFPCcUiQ3_2nn4boss11StorageKind", LogType::Placeholder); - cafeExportRegisterFunc(nn::boss::Storage::Initialize2, "nn_boss", "Initialize__Q3_2nn4boss7StorageFPCcQ3_2nn4boss11StorageKind", LogType::Placeholder); - - // AlmightyStorage - cafeExportRegisterFunc(nn::boss::AlmightyStorage::ctor, "nn_boss", "__ct__Q3_2nn4boss15AlmightyStorageFv", LogType::Placeholder ); - cafeExportRegisterFunc(nn::boss::AlmightyStorage::Initialize, "nn_boss", "Initialize__Q3_2nn4boss15AlmightyStorageFQ3_2nn4boss7TitleIDPCcUiQ3_2nn4boss11StorageKind", LogType::Placeholder ); + nn::boss::BossAccount::InitVTable(); + cafeExportRegisterFunc(nn::boss::BossAccount::ctor, "nn_boss", "__ct__Q3_2nn4boss7AccountFUi", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::BossAccount::dtor, "nn_boss", "__dt__Q3_2nn4boss7AccountFv", LogType::NN_BOSS); // AlmightyTask - cafeExportRegisterFunc(nn::boss::AlmightyTask::ctor, "nn_boss", "__ct__Q3_2nn4boss12AlmightyTaskFv", LogType::Placeholder); - cafeExportRegisterFunc(nn::boss::AlmightyTask::Initialize, "nn_boss", "Initialize__Q3_2nn4boss12AlmightyTaskFQ3_2nn4boss7TitleIDPCcUi", LogType::Placeholder); - // cafeExportRegisterFunc(nn::boss::AlmightyTask::dtor, "nn_boss", "__dt__Q3_2nn4boss12AlmightyTaskFv", LogType::Placeholder); + nn::boss::AlmightyTask::InitVTable(); + cafeExportRegisterFunc(nn::boss::AlmightyTask::ctor, "nn_boss", "__ct__Q3_2nn4boss12AlmightyTaskFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::AlmightyTask::Initialize, "nn_boss", "Initialize__Q3_2nn4boss12AlmightyTaskFQ3_2nn4boss7TitleIDPCcUi", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::AlmightyTask::dtor, "nn_boss", "__dt__Q3_2nn4boss12AlmightyTaskFv", LogType::NN_BOSS); + + // Storage + nn::boss::Storage::InitVTable(); + cafeExportRegisterFunc(nn::boss::Storage::ctor1, "nn_boss", "__ct__Q3_2nn4boss7StorageFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Storage::dtor, "nn_boss", "__dt__Q3_2nn4boss7StorageFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Storage::Finalize, "nn_boss", "Finalize__Q3_2nn4boss7StorageFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Storage::Exist, "nn_boss", "Exist__Q3_2nn4boss7StorageCFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Storage::GetDataList, "nn_boss", "GetDataList__Q3_2nn4boss7StorageCFPQ3_2nn4boss8DataNameUiPUiT2", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Storage::Initialize, "nn_boss", "Initialize__Q3_2nn4boss7StorageFPCcUiQ3_2nn4boss11StorageKind", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::Storage::Initialize2, "nn_boss", "Initialize__Q3_2nn4boss7StorageFPCcQ3_2nn4boss11StorageKind", LogType::NN_BOSS); + + // AlmightyStorage + nn::boss::AlmightyStorage::InitVTable(); + cafeExportRegisterFunc(nn::boss::AlmightyStorage::ctor, "nn_boss", "__ct__Q3_2nn4boss15AlmightyStorageFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::AlmightyStorage::Initialize, "nn_boss", "Initialize__Q3_2nn4boss15AlmightyStorageFQ3_2nn4boss7TitleIDPCcUiQ3_2nn4boss11StorageKind", LogType::NN_BOSS); // NsData - osLib_addFunction("nn_boss", "__ct__Q3_2nn4boss6NsDataFv", nnBossNsDataExport_ct); - osLib_addFunction("nn_boss", "Initialize__Q3_2nn4boss6NsDataFRCQ3_2nn4boss7StoragePCc", nnBossNsDataExport_initialize); - osLib_addFunction("nn_boss", "DeleteRealFileWithHistory__Q3_2nn4boss6NsDataFv", nnBossNsDataExport_DeleteRealFileWithHistory); - osLib_addFunction("nn_boss", "Exist__Q3_2nn4boss6NsDataCFv", nnBossNsDataExport_Exist); - osLib_addFunction("nn_boss", "GetSize__Q3_2nn4boss6NsDataCFv", nnBossNsDataExport_getSize); - cafeExportRegisterFunc(nnBossNsData_GetCreatedTime, "nn_boss", "GetCreatedTime__Q3_2nn4boss6NsDataCFv", LogType::Placeholder); - osLib_addFunction("nn_boss", "Read__Q3_2nn4boss6NsDataFPvUi", nnBossNsDataExport_read); - osLib_addFunction("nn_boss", "Read__Q3_2nn4boss6NsDataFPLPvUi", nnBossNsDataExport_readWithSizeOut); - osLib_addFunction("nn_boss", "Seek__Q3_2nn4boss6NsDataFLQ3_2nn4boss12PositionBase", nnBossNsDataExport_seek); - + nn::boss::NsData::InitVTable(); + cafeExportRegisterFunc(nn::boss::NsData::ctor, "nn_boss", "__ct__Q3_2nn4boss6NsDataFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NsData::dtor, "nn_boss", "__dt__Q3_2nn4boss6NsDataFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NsData::Initialize, "nn_boss", "Initialize__Q3_2nn4boss6NsDataFRCQ3_2nn4boss7StoragePCc", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NsData::DeleteRealFileWithHistory, "nn_boss", "DeleteRealFileWithHistory__Q3_2nn4boss6NsDataFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NsData::Exist, "nn_boss", "Exist__Q3_2nn4boss6NsDataCFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NsData::GetSize, "nn_boss", "GetSize__Q3_2nn4boss6NsDataCFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NsData::GetCreatedTime, "nn_boss", "GetCreatedTime__Q3_2nn4boss6NsDataCFv", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NsData::Read, "nn_boss", "Read__Q3_2nn4boss6NsDataFPvUi", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NsData::ReadWithSizeOut, "nn_boss", "Read__Q3_2nn4boss6NsDataFPLPvUi", LogType::NN_BOSS); + cafeExportRegisterFunc(nn::boss::NsData::Seek, "nn_boss", "Seek__Q3_2nn4boss6NsDataFLQ3_2nn4boss12PositionBase", LogType::NN_BOSS); } diff --git a/src/Cemu/Logging/CemuLogging.cpp b/src/Cemu/Logging/CemuLogging.cpp index 6d596acf..058ab07a 100644 --- a/src/Cemu/Logging/CemuLogging.cpp +++ b/src/Cemu/Logging/CemuLogging.cpp @@ -44,6 +44,7 @@ const std::map<LogType, std::string> g_logging_window_mapping {LogType::CoreinitThread, "Coreinit Thread"}, {LogType::NN_NFP, "nn::nfp"}, {LogType::NN_FP, "nn::fp"}, + {LogType::NN_BOSS, "nn::boss"}, {LogType::GX2, "GX2"}, {LogType::SoundAPI, "Audio"}, {LogType::InputAPI, "Input"}, diff --git a/src/Cemu/Logging/CemuLogging.h b/src/Cemu/Logging/CemuLogging.h index bbffd164..fe74a6bc 100644 --- a/src/Cemu/Logging/CemuLogging.h +++ b/src/Cemu/Logging/CemuLogging.h @@ -35,6 +35,7 @@ enum class LogType : sint32 NN_OLV = 23, NN_NFP = 13, NN_FP = 24, + NN_BOSS = 25, TextureReadback = 29, diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 023918bd..4d2fb478 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -2232,6 +2232,7 @@ void MainWindow::RecreateMenu() debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitThread), _("&Coreinit Thread API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitThread)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_NFP), _("&NN NFP"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NN_NFP)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_FP), _("&NN FP"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NN_FP)); + debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_BOSS), _("&NN BOSS"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NN_BOSS)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::GX2), _("&GX2 API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::GX2)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::SoundAPI), _("&Audio API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::SoundAPI)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::InputAPI), _("&Input API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::InputAPI)); From 5c0d5a54acf4263631d855f4181462f97a26b426 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 3 Apr 2024 02:39:25 +0200 Subject: [PATCH 062/299] vcpkg/linux: Avoid dependency on liblzma for now Use port of tiff which does not rely on lzma --- .../tiff/FindCMath.patch | 13 +++ .../tiff/portfile.cmake | 86 +++++++++++++++ .../vcpkg_overlay_ports_linux/tiff/usage | 9 ++ .../tiff/vcpkg-cmake-wrapper.cmake.in | 104 ++++++++++++++++++ .../vcpkg_overlay_ports_linux/tiff/vcpkg.json | 67 +++++++++++ 5 files changed, 279 insertions(+) create mode 100644 dependencies/vcpkg_overlay_ports_linux/tiff/FindCMath.patch create mode 100644 dependencies/vcpkg_overlay_ports_linux/tiff/portfile.cmake create mode 100644 dependencies/vcpkg_overlay_ports_linux/tiff/usage create mode 100644 dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg-cmake-wrapper.cmake.in create mode 100644 dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg.json diff --git a/dependencies/vcpkg_overlay_ports_linux/tiff/FindCMath.patch b/dependencies/vcpkg_overlay_ports_linux/tiff/FindCMath.patch new file mode 100644 index 00000000..70654cf8 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_linux/tiff/FindCMath.patch @@ -0,0 +1,13 @@ +diff --git a/cmake/FindCMath.cmake b/cmake/FindCMath.cmake +index ad92218..dd42aba 100644 +--- a/cmake/FindCMath.cmake ++++ b/cmake/FindCMath.cmake +@@ -31,7 +31,7 @@ include(CheckSymbolExists) + include(CheckLibraryExists) + + check_symbol_exists(pow "math.h" CMath_HAVE_LIBC_POW) +-find_library(CMath_LIBRARY NAMES m) ++find_library(CMath_LIBRARY NAMES m PATHS ${CMAKE_C_IMPLICIT_LINK_DIRECTORIES}) + + if(NOT CMath_HAVE_LIBC_POW) + set(CMAKE_REQUIRED_LIBRARIES_SAVE ${CMAKE_REQUIRED_LIBRARIES}) diff --git a/dependencies/vcpkg_overlay_ports_linux/tiff/portfile.cmake b/dependencies/vcpkg_overlay_ports_linux/tiff/portfile.cmake new file mode 100644 index 00000000..426d8af7 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_linux/tiff/portfile.cmake @@ -0,0 +1,86 @@ +vcpkg_from_gitlab( + GITLAB_URL https://gitlab.com + OUT_SOURCE_PATH SOURCE_PATH + REPO libtiff/libtiff + REF "v${VERSION}" + SHA512 ef2f1d424219d9e245069b7d23e78f5e817cf6ee516d46694915ab6c8909522166f84997513d20a702f4e52c3f18467813935b328fafa34bea5156dee00f66fa + HEAD_REF master + PATCHES + FindCMath.patch +) + +vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS + FEATURES + cxx cxx + jpeg jpeg + jpeg CMAKE_REQUIRE_FIND_PACKAGE_JPEG + libdeflate libdeflate + libdeflate CMAKE_REQUIRE_FIND_PACKAGE_Deflate + lzma lzma + lzma CMAKE_REQUIRE_FIND_PACKAGE_liblzma + tools tiff-tools + webp webp + webp CMAKE_REQUIRE_FIND_PACKAGE_WebP + zip zlib + zip CMAKE_REQUIRE_FIND_PACKAGE_ZLIB + zstd zstd + zstd CMAKE_REQUIRE_FIND_PACKAGE_ZSTD +) + +vcpkg_cmake_configure( + SOURCE_PATH "${SOURCE_PATH}" + OPTIONS + ${FEATURE_OPTIONS} + -DCMAKE_FIND_PACKAGE_PREFER_CONFIG=ON + -Dtiff-docs=OFF + -Dtiff-contrib=OFF + -Dtiff-tests=OFF + -Djbig=OFF # This is disabled by default due to GPL/Proprietary licensing. + -Djpeg12=OFF + -Dlerc=OFF + -DCMAKE_DISABLE_FIND_PACKAGE_OpenGL=ON + -DCMAKE_DISABLE_FIND_PACKAGE_GLUT=ON + -DZSTD_HAVE_DECOMPRESS_STREAM=ON + -DHAVE_JPEGTURBO_DUAL_MODE_8_12=OFF + OPTIONS_DEBUG + -DCMAKE_DEBUG_POSTFIX=d # tiff sets "d" for MSVC only. + MAYBE_UNUSED_VARIABLES + CMAKE_DISABLE_FIND_PACKAGE_GLUT + CMAKE_DISABLE_FIND_PACKAGE_OpenGL + ZSTD_HAVE_DECOMPRESS_STREAM +) + +vcpkg_cmake_install() + +# CMake config wasn't packaged in the past and is not yet usable now, +# cf. https://gitlab.com/libtiff/libtiff/-/merge_requests/496 +# vcpkg_cmake_config_fixup(CONFIG_PATH "lib/cmake/tiff") +file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/lib/cmake" "${CURRENT_PACKAGES_DIR}/debug/lib/cmake") + +set(_file "${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/libtiff-4.pc") +if(EXISTS "${_file}") + vcpkg_replace_string("${_file}" "-ltiff" "-ltiffd") +endif() +vcpkg_fixup_pkgconfig() + +file(REMOVE_RECURSE + "${CURRENT_PACKAGES_DIR}/debug/include" + "${CURRENT_PACKAGES_DIR}/debug/share" +) + +configure_file("${CMAKE_CURRENT_LIST_DIR}/vcpkg-cmake-wrapper.cmake.in" "${CURRENT_PACKAGES_DIR}/share/${PORT}/vcpkg-cmake-wrapper.cmake" @ONLY) + +if ("tools" IN_LIST FEATURES) + vcpkg_copy_tools(TOOL_NAMES + tiffcp + tiffdump + tiffinfo + tiffset + tiffsplit + AUTO_CLEAN + ) +endif() + +vcpkg_copy_pdbs() +file(COPY "${CURRENT_PORT_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") +vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.md") diff --git a/dependencies/vcpkg_overlay_ports_linux/tiff/usage b/dependencies/vcpkg_overlay_ports_linux/tiff/usage new file mode 100644 index 00000000..d47265b1 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_linux/tiff/usage @@ -0,0 +1,9 @@ +tiff is compatible with built-in CMake targets: + + find_package(TIFF REQUIRED) + target_link_libraries(main PRIVATE TIFF::TIFF) + +tiff provides pkg-config modules: + + # Tag Image File Format (TIFF) library. + libtiff-4 diff --git a/dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg-cmake-wrapper.cmake.in b/dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg-cmake-wrapper.cmake.in new file mode 100644 index 00000000..1d04ec7a --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg-cmake-wrapper.cmake.in @@ -0,0 +1,104 @@ +cmake_policy(PUSH) +cmake_policy(SET CMP0012 NEW) +cmake_policy(SET CMP0057 NEW) +set(z_vcpkg_tiff_find_options "") +if("REQUIRED" IN_LIST ARGS) + list(APPEND z_vcpkg_tiff_find_options "REQUIRED") +endif() +if("QUIET" IN_LIST ARGS) + list(APPEND z_vcpkg_tiff_find_options "QUIET") +endif() + +_find_package(${ARGS}) + +if(TIFF_FOUND AND "@VCPKG_LIBRARY_LINKAGE@" STREQUAL "static") + include(SelectLibraryConfigurations) + set(z_vcpkg_tiff_link_libraries "") + set(z_vcpkg_tiff_libraries "") + if("@webp@") + find_package(WebP CONFIG ${z_vcpkg_tiff_find_options}) + list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:WebP::WebP>") + list(APPEND z_vcpkg_tiff_libraries ${WebP_LIBRARIES}) + endif() + if("@lzma@") + find_package(LibLZMA ${z_vcpkg_tiff_find_options}) + list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:LibLZMA::LibLZMA>") + list(APPEND z_vcpkg_tiff_libraries ${LIBLZMA_LIBRARIES}) + endif() + if("@jpeg@") + find_package(JPEG ${z_vcpkg_tiff_find_options}) + list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:JPEG::JPEG>") + list(APPEND z_vcpkg_tiff_libraries ${JPEG_LIBRARIES}) + endif() + if("@zstd@") + find_package(zstd CONFIG ${z_vcpkg_tiff_find_options}) + set(z_vcpkg_tiff_zstd_target_property "IMPORTED_LOCATION_") + if(TARGET zstd::libzstd_shared) + set(z_vcpkg_tiff_zstd "\$<LINK_ONLY:zstd::libzstd_shared>") + set(z_vcpkg_tiff_zstd_target zstd::libzstd_shared) + if(WIN32) + set(z_vcpkg_tiff_zstd_target_property "IMPORTED_IMPLIB_") + endif() + else() + set(z_vcpkg_tiff_zstd "\$<LINK_ONLY:zstd::libzstd_static>") + set(z_vcpkg_tiff_zstd_target zstd::libzstd_static) + endif() + get_target_property(z_vcpkg_tiff_zstd_configs "${z_vcpkg_tiff_zstd_target}" IMPORTED_CONFIGURATIONS) + foreach(z_vcpkg_config IN LISTS z_vcpkg_tiff_zstd_configs) + get_target_property(ZSTD_LIBRARY_${z_vcpkg_config} "${z_vcpkg_tiff_zstd_target}" "${z_vcpkg_tiff_zstd_target_property}${z_vcpkg_config}") + endforeach() + select_library_configurations(ZSTD) + if(NOT TARGET ZSTD::ZSTD) + add_library(ZSTD::ZSTD INTERFACE IMPORTED) + set_property(TARGET ZSTD::ZSTD APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${z_vcpkg_tiff_zstd}) + endif() + list(APPEND z_vcpkg_tiff_link_libraries ${z_vcpkg_tiff_zstd}) + list(APPEND z_vcpkg_tiff_libraries ${ZSTD_LIBRARIES}) + unset(z_vcpkg_tiff_zstd) + unset(z_vcpkg_tiff_zstd_configs) + unset(z_vcpkg_config) + unset(z_vcpkg_tiff_zstd_target) + endif() + if("@libdeflate@") + find_package(libdeflate ${z_vcpkg_tiff_find_options}) + set(z_vcpkg_property "IMPORTED_LOCATION_") + if(TARGET libdeflate::libdeflate_shared) + set(z_vcpkg_libdeflate_target libdeflate::libdeflate_shared) + if(WIN32) + set(z_vcpkg_property "IMPORTED_IMPLIB_") + endif() + else() + set(z_vcpkg_libdeflate_target libdeflate::libdeflate_static) + endif() + get_target_property(z_vcpkg_libdeflate_configs "${z_vcpkg_libdeflate_target}" IMPORTED_CONFIGURATIONS) + foreach(z_vcpkg_config IN LISTS z_vcpkg_libdeflate_configs) + get_target_property(Z_VCPKG_DEFLATE_LIBRARY_${z_vcpkg_config} "${z_vcpkg_libdeflate_target}" "${z_vcpkg_property}${z_vcpkg_config}") + endforeach() + select_library_configurations(Z_VCPKG_DEFLATE) + list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:${z_vcpkg_libdeflate_target}>") + list(APPEND z_vcpkg_tiff_libraries ${Z_VCPKG_DEFLATE_LIBRARIES}) + unset(z_vcpkg_config) + unset(z_vcpkg_libdeflate_configs) + unset(z_vcpkg_libdeflate_target) + unset(z_vcpkg_property) + unset(Z_VCPKG_DEFLATE_FOUND) + endif() + if("@zlib@") + find_package(ZLIB ${z_vcpkg_tiff_find_options}) + list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:ZLIB::ZLIB>") + list(APPEND z_vcpkg_tiff_libraries ${ZLIB_LIBRARIES}) + endif() + if(UNIX) + list(APPEND z_vcpkg_tiff_link_libraries m) + list(APPEND z_vcpkg_tiff_libraries m) + endif() + + if(TARGET TIFF::TIFF) + set_property(TARGET TIFF::TIFF APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${z_vcpkg_tiff_link_libraries}) + endif() + list(APPEND TIFF_LIBRARIES ${z_vcpkg_tiff_libraries}) + unset(z_vcpkg_tiff_link_libraries) + unset(z_vcpkg_tiff_libraries) +endif() +unset(z_vcpkg_tiff_find_options) +cmake_policy(POP) diff --git a/dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg.json b/dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg.json new file mode 100644 index 00000000..9b36e1a8 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg.json @@ -0,0 +1,67 @@ +{ + "name": "tiff", + "version": "4.6.0", + "port-version": 2, + "description": "A library that supports the manipulation of TIFF image files", + "homepage": "https://libtiff.gitlab.io/libtiff/", + "license": "libtiff", + "dependencies": [ + { + "name": "vcpkg-cmake", + "host": true + }, + { + "name": "vcpkg-cmake-config", + "host": true + } + ], + "default-features": [ + "jpeg", + "zip" + ], + "features": { + "cxx": { + "description": "Build C++ libtiffxx library" + }, + "jpeg": { + "description": "Support JPEG compression in TIFF image files", + "dependencies": [ + "libjpeg-turbo" + ] + }, + "libdeflate": { + "description": "Use libdeflate for faster ZIP support", + "dependencies": [ + "libdeflate", + { + "name": "tiff", + "default-features": false, + "features": [ + "zip" + ] + } + ] + }, + "tools": { + "description": "Build tools" + }, + "webp": { + "description": "Support WEBP compression in TIFF image files", + "dependencies": [ + "libwebp" + ] + }, + "zip": { + "description": "Support ZIP/deflate compression in TIFF image files", + "dependencies": [ + "zlib" + ] + }, + "zstd": { + "description": "Support ZSTD compression in TIFF image files", + "dependencies": [ + "zstd" + ] + } + } +} From 85141f17f977157b91b72883d879f50b27f17dda Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 3 Apr 2024 15:28:00 +0200 Subject: [PATCH 063/299] vcpkg/linux: Avoid dependency on libsystemd/liblzma libsystemd which is required by dbus has an optional dependency on liblzma and since we don't need it we can just strip it out of dbus --- .../dbus/cmake.dep.patch | 15 ++++ .../dbus/getpeereid.patch | 26 ++++++ .../dbus/libsystemd.patch | 15 ++++ .../dbus/pkgconfig.patch | 21 +++++ .../dbus/portfile.cmake | 88 +++++++++++++++++++ .../vcpkg_overlay_ports_linux/dbus/vcpkg.json | 30 +++++++ 6 files changed, 195 insertions(+) create mode 100644 dependencies/vcpkg_overlay_ports_linux/dbus/cmake.dep.patch create mode 100644 dependencies/vcpkg_overlay_ports_linux/dbus/getpeereid.patch create mode 100644 dependencies/vcpkg_overlay_ports_linux/dbus/libsystemd.patch create mode 100644 dependencies/vcpkg_overlay_ports_linux/dbus/pkgconfig.patch create mode 100644 dependencies/vcpkg_overlay_ports_linux/dbus/portfile.cmake create mode 100644 dependencies/vcpkg_overlay_ports_linux/dbus/vcpkg.json diff --git a/dependencies/vcpkg_overlay_ports_linux/dbus/cmake.dep.patch b/dependencies/vcpkg_overlay_ports_linux/dbus/cmake.dep.patch new file mode 100644 index 00000000..ac827f0c --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_linux/dbus/cmake.dep.patch @@ -0,0 +1,15 @@ +diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt +index 8cde1ffe0..d4d09f223 100644 +--- a/tools/CMakeLists.txt ++++ b/tools/CMakeLists.txt +@@ -91,7 +91,9 @@ endif() + add_executable(dbus-launch ${dbus_launch_SOURCES}) + target_link_libraries(dbus-launch ${DBUS_LIBRARIES}) + if(DBUS_BUILD_X11) +- target_link_libraries(dbus-launch ${X11_LIBRARIES} ) ++ find_package(Threads REQUIRED) ++ target_link_libraries(dbus-launch ${X11_LIBRARIES} ${X11_xcb_LIB} ${X11_Xau_LIB} ${X11_Xdmcp_LIB} Threads::Threads) ++ target_include_directories(dbus-launch PRIVATE ${X11_INCLUDE_DIR}) + endif() + install(TARGETS dbus-launch ${INSTALL_TARGETS_DEFAULT_ARGS}) + diff --git a/dependencies/vcpkg_overlay_ports_linux/dbus/getpeereid.patch b/dependencies/vcpkg_overlay_ports_linux/dbus/getpeereid.patch new file mode 100644 index 00000000..5cd2309e --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_linux/dbus/getpeereid.patch @@ -0,0 +1,26 @@ +diff --git a/cmake/ConfigureChecks.cmake b/cmake/ConfigureChecks.cmake +index b7f3702..e2336ba 100644 +--- a/cmake/ConfigureChecks.cmake ++++ b/cmake/ConfigureChecks.cmake +@@ -51,6 +51,7 @@ check_symbol_exists(closefrom "unistd.h" HAVE_CLOSEFROM) # + check_symbol_exists(environ "unistd.h" HAVE_DECL_ENVIRON) + check_symbol_exists(fstatfs "sys/vfs.h" HAVE_FSTATFS) + check_symbol_exists(getgrouplist "grp.h" HAVE_GETGROUPLIST) # dbus-sysdeps.c ++check_symbol_exists(getpeereid "sys/types.h;unistd.h" HAVE_GETPEEREID) # dbus-sysdeps.c, + check_symbol_exists(getpeerucred "ucred.h" HAVE_GETPEERUCRED) # dbus-sysdeps.c, dbus-sysdeps-win.c + check_symbol_exists(getpwnam_r "errno.h;pwd.h" HAVE_GETPWNAM_R) # dbus-sysdeps-util-unix.c + check_symbol_exists(getrandom "sys/random.h" HAVE_GETRANDOM) +diff --git a/cmake/config.h.cmake b/cmake/config.h.cmake +index 77fc19c..2f25643 100644 +--- a/cmake/config.h.cmake ++++ b/cmake/config.h.cmake +@@ -140,6 +140,9 @@ + /* Define to 1 if you have getgrouplist */ + #cmakedefine HAVE_GETGROUPLIST 1 + ++/* Define to 1 if you have getpeereid */ ++#cmakedefine HAVE_GETPEEREID 1 ++ + /* Define to 1 if you have getpeerucred */ + #cmakedefine HAVE_GETPEERUCRED 1 + diff --git a/dependencies/vcpkg_overlay_ports_linux/dbus/libsystemd.patch b/dependencies/vcpkg_overlay_ports_linux/dbus/libsystemd.patch new file mode 100644 index 00000000..74193dc4 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_linux/dbus/libsystemd.patch @@ -0,0 +1,15 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index d3ec71b..932066a 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -141,6 +141,10 @@ if(DBUS_LINUX) + if(ENABLE_SYSTEMD AND SYSTEMD_FOUND) + set(DBUS_BUS_ENABLE_SYSTEMD ON) + set(HAVE_SYSTEMD ${SYSTEMD_FOUND}) ++ pkg_check_modules(SYSTEMD libsystemd IMPORTED_TARGET) ++ set(SYSTEMD_LIBRARIES PkgConfig::SYSTEMD CACHE INTERNAL "") ++ else() ++ set(SYSTEMD_LIBRARIES "" CACHE INTERNAL "") + endif() + option(ENABLE_USER_SESSION "enable user-session semantics for session bus under systemd" OFF) + set(DBUS_ENABLE_USER_SESSION ${ENABLE_USER_SESSION}) diff --git a/dependencies/vcpkg_overlay_ports_linux/dbus/pkgconfig.patch b/dependencies/vcpkg_overlay_ports_linux/dbus/pkgconfig.patch new file mode 100644 index 00000000..63581487 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_linux/dbus/pkgconfig.patch @@ -0,0 +1,21 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index caef738..b878f42 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -724,11 +724,11 @@ add_custom_target(help-options + # + if(DBUS_ENABLE_PKGCONFIG) + set(PLATFORM_LIBS pthread ${LIBRT}) +- if(PKG_CONFIG_FOUND) +- # convert lists of link libraries into -lstdc++ -lm etc.. +- foreach(LIB ${CMAKE_C_IMPLICIT_LINK_LIBRARIES} ${PLATFORM_LIBS}) +- set(LIBDBUS_LIBS "${LIBDBUS_LIBS} -l${LIB}") +- endforeach() ++ if(1) ++ set(LIBDBUS_LIBS "${CMAKE_THREAD_LIBS_INIT}") ++ if(LIBRT) ++ string(APPEND LIBDBUS_LIBS " -lrt") ++ endif() + set(original_prefix "${CMAKE_INSTALL_PREFIX}") + if(DBUS_RELOCATABLE) + set(pkgconfig_prefix "\${pcfiledir}/../..") diff --git a/dependencies/vcpkg_overlay_ports_linux/dbus/portfile.cmake b/dependencies/vcpkg_overlay_ports_linux/dbus/portfile.cmake new file mode 100644 index 00000000..56c7e182 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_linux/dbus/portfile.cmake @@ -0,0 +1,88 @@ +vcpkg_check_linkage(ONLY_DYNAMIC_LIBRARY) + +vcpkg_from_gitlab( + GITLAB_URL https://gitlab.freedesktop.org/ + OUT_SOURCE_PATH SOURCE_PATH + REPO dbus/dbus + REF "dbus-${VERSION}" + SHA512 8e476b408514e6540c36beb84e8025827c22cda8958b6eb74d22b99c64765eb3cd5a6502aea546e3e5f0534039857b37edee89c659acef40e7cab0939947d4af + HEAD_REF master + PATCHES + cmake.dep.patch + pkgconfig.patch + getpeereid.patch # missing check from configure.ac + libsystemd.patch +) + +vcpkg_check_features(OUT_FEATURE_OPTIONS options + FEATURES + systemd ENABLE_SYSTEMD + x11 DBUS_BUILD_X11 + x11 CMAKE_REQUIRE_FIND_PACKAGE_X11 +) + +unset(ENV{DBUSDIR}) + +vcpkg_cmake_configure( + SOURCE_PATH "${SOURCE_PATH}" + OPTIONS + -DDBUS_BUILD_TESTS=OFF + -DDBUS_ENABLE_DOXYGEN_DOCS=OFF + -DDBUS_ENABLE_XML_DOCS=OFF + -DDBUS_INSTALL_SYSTEM_LIBS=OFF + #-DDBUS_SERVICE=ON + -DDBUS_WITH_GLIB=OFF + -DTHREADS_PREFER_PTHREAD_FLAG=ON + -DXSLTPROC_EXECUTABLE=FALSE + "-DCMAKE_INSTALL_SYSCONFDIR=${CURRENT_PACKAGES_DIR}/etc/${PORT}" + "-DWITH_SYSTEMD_SYSTEMUNITDIR=lib/systemd/system" + "-DWITH_SYSTEMD_USERUNITDIR=lib/systemd/user" + ${options} + OPTIONS_RELEASE + -DDBUS_DISABLE_ASSERT=OFF + -DDBUS_ENABLE_STATS=OFF + -DDBUS_ENABLE_VERBOSE_MODE=OFF + MAYBE_UNUSED_VARIABLES + DBUS_BUILD_X11 + DBUS_WITH_GLIB + ENABLE_SYSTEMD + THREADS_PREFER_PTHREAD_FLAG + WITH_SYSTEMD_SYSTEMUNITDIR + WITH_SYSTEMD_USERUNITDIR +) +vcpkg_cmake_install() +vcpkg_copy_pdbs() +vcpkg_cmake_config_fixup(PACKAGE_NAME "DBus1" CONFIG_PATH "lib/cmake/DBus1") +vcpkg_fixup_pkgconfig() + +file(REMOVE_RECURSE + "${CURRENT_PACKAGES_DIR}/debug/include" + "${CURRENT_PACKAGES_DIR}/debug/share" + "${CURRENT_PACKAGES_DIR}/debug/var/" + "${CURRENT_PACKAGES_DIR}/etc" + "${CURRENT_PACKAGES_DIR}/share/dbus-1/services" + "${CURRENT_PACKAGES_DIR}/share/dbus-1/session.d" + "${CURRENT_PACKAGES_DIR}/share/dbus-1/system-services" + "${CURRENT_PACKAGES_DIR}/share/dbus-1/system.d" + "${CURRENT_PACKAGES_DIR}/share/dbus-1/system.conf" + "${CURRENT_PACKAGES_DIR}/share/dbus-1/system.conf" + "${CURRENT_PACKAGES_DIR}/share/doc" + "${CURRENT_PACKAGES_DIR}/var" +) + +vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/share/dbus-1/session.conf" "<include ignore_missing=\"yes\">${CURRENT_PACKAGES_DIR}/etc/dbus/dbus-1/session.conf</include>" "") +vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/share/dbus-1/session.conf" "<includedir>${CURRENT_PACKAGES_DIR}/etc/dbus/dbus-1/session.d</includedir>" "") +vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/share/dbus-1/session.conf" "<include ignore_missing=\"yes\">${CURRENT_PACKAGES_DIR}/etc/dbus/dbus-1/session-local.conf</include>" "") + +set(TOOLS daemon launch monitor run-session send test-tool update-activation-environment) +if(VCPKG_TARGET_IS_WINDOWS) + file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/tools/${PORT}") + file(RENAME "${CURRENT_PACKAGES_DIR}/bin/dbus-env.bat" "${CURRENT_PACKAGES_DIR}/tools/${PORT}/dbus-env.bat") + vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/tools/${PORT}/dbus-env.bat" "${CURRENT_PACKAGES_DIR}" "%~dp0/../..") +else() + list(APPEND TOOLS cleanup-sockets uuidgen) +endif() +list(TRANSFORM TOOLS PREPEND "dbus-" ) +vcpkg_copy_tools(TOOL_NAMES ${TOOLS} AUTO_CLEAN) + +file(INSTALL "${SOURCE_PATH}/COPYING" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" RENAME copyright) diff --git a/dependencies/vcpkg_overlay_ports_linux/dbus/vcpkg.json b/dependencies/vcpkg_overlay_ports_linux/dbus/vcpkg.json new file mode 100644 index 00000000..853dff05 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_linux/dbus/vcpkg.json @@ -0,0 +1,30 @@ +{ + "name": "dbus", + "version": "1.15.8", + "port-version": 2, + "description": "D-Bus specification and reference implementation, including libdbus and dbus-daemon", + "homepage": "https://gitlab.freedesktop.org/dbus/dbus", + "license": "AFL-2.1 OR GPL-2.0-or-later", + "supports": "!uwp & !staticcrt", + "dependencies": [ + "expat", + { + "name": "vcpkg-cmake", + "host": true + }, + { + "name": "vcpkg-cmake-config", + "host": true + } + ], + "default-features": [ + ], + "features": { + "x11": { + "description": "Build with X11 autolaunch support", + "dependencies": [ + "libx11" + ] + } + } +} From 075eac626b162dd2e23ae337860f4717bf3041fe Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Sat, 6 Apr 2024 22:13:19 +0200 Subject: [PATCH 064/299] ELF: Fix crash due to not allocating recompiler ranges (#1154) --- src/Cafe/OS/RPL/elf.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Cafe/OS/RPL/elf.cpp b/src/Cafe/OS/RPL/elf.cpp index c61afb21..7ee3ba47 100644 --- a/src/Cafe/OS/RPL/elf.cpp +++ b/src/Cafe/OS/RPL/elf.cpp @@ -50,6 +50,10 @@ typedef struct static_assert(sizeof(elfSectionEntry_t) == 0x28, ""); +#define PF_X (1 << 0) /* Segment is executable */ +#define PF_W (1 << 1) /* Segment is writable */ +#define PF_R (1 << 2) /* Segment is readable */ + // Map elf into memory uint32 ELF_LoadFromMemory(uint8* elfData, sint32 size, const char* name) { @@ -68,6 +72,7 @@ uint32 ELF_LoadFromMemory(uint8* elfData, sint32 size, const char* name) uint32 shSize = (uint32)sectionTable[i].shSize; uint32 shOffset = (uint32)sectionTable[i].shOffset; uint32 shType = (uint32)sectionTable[i].shType; + uint32 shFlags = (uint32)sectionTable[i].shFlags; if (shOffset > (uint32)size) { @@ -89,6 +94,8 @@ uint32 ELF_LoadFromMemory(uint8* elfData, sint32 size, const char* name) } // SHT_NOBITS } + if((shFlags & PF_X) > 0) + PPCRecompiler_allocateRange(shAddr, shSize); } return header->entrypoint; } From fde7230191a07c40b6811eb3275b5e9c68af6cd3 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 6 Apr 2024 14:25:13 +0200 Subject: [PATCH 065/299] vcpkg/windows/mac: Avoid dependency on liblzma via tiff --- .../vcpkg_overlay_ports/tiff/FindCMath.patch | 13 +++ .../vcpkg_overlay_ports/tiff/portfile.cmake | 86 +++++++++++++++ dependencies/vcpkg_overlay_ports/tiff/usage | 9 ++ .../tiff/vcpkg-cmake-wrapper.cmake.in | 104 ++++++++++++++++++ .../vcpkg_overlay_ports/tiff/vcpkg.json | 67 +++++++++++ .../tiff/FindCMath.patch | 13 +++ .../tiff/portfile.cmake | 86 +++++++++++++++ .../vcpkg_overlay_ports_mac/tiff/usage | 9 ++ .../tiff/vcpkg-cmake-wrapper.cmake.in | 104 ++++++++++++++++++ .../vcpkg_overlay_ports_mac/tiff/vcpkg.json | 67 +++++++++++ 10 files changed, 558 insertions(+) create mode 100644 dependencies/vcpkg_overlay_ports/tiff/FindCMath.patch create mode 100644 dependencies/vcpkg_overlay_ports/tiff/portfile.cmake create mode 100644 dependencies/vcpkg_overlay_ports/tiff/usage create mode 100644 dependencies/vcpkg_overlay_ports/tiff/vcpkg-cmake-wrapper.cmake.in create mode 100644 dependencies/vcpkg_overlay_ports/tiff/vcpkg.json create mode 100644 dependencies/vcpkg_overlay_ports_mac/tiff/FindCMath.patch create mode 100644 dependencies/vcpkg_overlay_ports_mac/tiff/portfile.cmake create mode 100644 dependencies/vcpkg_overlay_ports_mac/tiff/usage create mode 100644 dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg-cmake-wrapper.cmake.in create mode 100644 dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg.json diff --git a/dependencies/vcpkg_overlay_ports/tiff/FindCMath.patch b/dependencies/vcpkg_overlay_ports/tiff/FindCMath.patch new file mode 100644 index 00000000..70654cf8 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/tiff/FindCMath.patch @@ -0,0 +1,13 @@ +diff --git a/cmake/FindCMath.cmake b/cmake/FindCMath.cmake +index ad92218..dd42aba 100644 +--- a/cmake/FindCMath.cmake ++++ b/cmake/FindCMath.cmake +@@ -31,7 +31,7 @@ include(CheckSymbolExists) + include(CheckLibraryExists) + + check_symbol_exists(pow "math.h" CMath_HAVE_LIBC_POW) +-find_library(CMath_LIBRARY NAMES m) ++find_library(CMath_LIBRARY NAMES m PATHS ${CMAKE_C_IMPLICIT_LINK_DIRECTORIES}) + + if(NOT CMath_HAVE_LIBC_POW) + set(CMAKE_REQUIRED_LIBRARIES_SAVE ${CMAKE_REQUIRED_LIBRARIES}) diff --git a/dependencies/vcpkg_overlay_ports/tiff/portfile.cmake b/dependencies/vcpkg_overlay_ports/tiff/portfile.cmake new file mode 100644 index 00000000..426d8af7 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/tiff/portfile.cmake @@ -0,0 +1,86 @@ +vcpkg_from_gitlab( + GITLAB_URL https://gitlab.com + OUT_SOURCE_PATH SOURCE_PATH + REPO libtiff/libtiff + REF "v${VERSION}" + SHA512 ef2f1d424219d9e245069b7d23e78f5e817cf6ee516d46694915ab6c8909522166f84997513d20a702f4e52c3f18467813935b328fafa34bea5156dee00f66fa + HEAD_REF master + PATCHES + FindCMath.patch +) + +vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS + FEATURES + cxx cxx + jpeg jpeg + jpeg CMAKE_REQUIRE_FIND_PACKAGE_JPEG + libdeflate libdeflate + libdeflate CMAKE_REQUIRE_FIND_PACKAGE_Deflate + lzma lzma + lzma CMAKE_REQUIRE_FIND_PACKAGE_liblzma + tools tiff-tools + webp webp + webp CMAKE_REQUIRE_FIND_PACKAGE_WebP + zip zlib + zip CMAKE_REQUIRE_FIND_PACKAGE_ZLIB + zstd zstd + zstd CMAKE_REQUIRE_FIND_PACKAGE_ZSTD +) + +vcpkg_cmake_configure( + SOURCE_PATH "${SOURCE_PATH}" + OPTIONS + ${FEATURE_OPTIONS} + -DCMAKE_FIND_PACKAGE_PREFER_CONFIG=ON + -Dtiff-docs=OFF + -Dtiff-contrib=OFF + -Dtiff-tests=OFF + -Djbig=OFF # This is disabled by default due to GPL/Proprietary licensing. + -Djpeg12=OFF + -Dlerc=OFF + -DCMAKE_DISABLE_FIND_PACKAGE_OpenGL=ON + -DCMAKE_DISABLE_FIND_PACKAGE_GLUT=ON + -DZSTD_HAVE_DECOMPRESS_STREAM=ON + -DHAVE_JPEGTURBO_DUAL_MODE_8_12=OFF + OPTIONS_DEBUG + -DCMAKE_DEBUG_POSTFIX=d # tiff sets "d" for MSVC only. + MAYBE_UNUSED_VARIABLES + CMAKE_DISABLE_FIND_PACKAGE_GLUT + CMAKE_DISABLE_FIND_PACKAGE_OpenGL + ZSTD_HAVE_DECOMPRESS_STREAM +) + +vcpkg_cmake_install() + +# CMake config wasn't packaged in the past and is not yet usable now, +# cf. https://gitlab.com/libtiff/libtiff/-/merge_requests/496 +# vcpkg_cmake_config_fixup(CONFIG_PATH "lib/cmake/tiff") +file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/lib/cmake" "${CURRENT_PACKAGES_DIR}/debug/lib/cmake") + +set(_file "${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/libtiff-4.pc") +if(EXISTS "${_file}") + vcpkg_replace_string("${_file}" "-ltiff" "-ltiffd") +endif() +vcpkg_fixup_pkgconfig() + +file(REMOVE_RECURSE + "${CURRENT_PACKAGES_DIR}/debug/include" + "${CURRENT_PACKAGES_DIR}/debug/share" +) + +configure_file("${CMAKE_CURRENT_LIST_DIR}/vcpkg-cmake-wrapper.cmake.in" "${CURRENT_PACKAGES_DIR}/share/${PORT}/vcpkg-cmake-wrapper.cmake" @ONLY) + +if ("tools" IN_LIST FEATURES) + vcpkg_copy_tools(TOOL_NAMES + tiffcp + tiffdump + tiffinfo + tiffset + tiffsplit + AUTO_CLEAN + ) +endif() + +vcpkg_copy_pdbs() +file(COPY "${CURRENT_PORT_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") +vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.md") diff --git a/dependencies/vcpkg_overlay_ports/tiff/usage b/dependencies/vcpkg_overlay_ports/tiff/usage new file mode 100644 index 00000000..d47265b1 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/tiff/usage @@ -0,0 +1,9 @@ +tiff is compatible with built-in CMake targets: + + find_package(TIFF REQUIRED) + target_link_libraries(main PRIVATE TIFF::TIFF) + +tiff provides pkg-config modules: + + # Tag Image File Format (TIFF) library. + libtiff-4 diff --git a/dependencies/vcpkg_overlay_ports/tiff/vcpkg-cmake-wrapper.cmake.in b/dependencies/vcpkg_overlay_ports/tiff/vcpkg-cmake-wrapper.cmake.in new file mode 100644 index 00000000..1d04ec7a --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/tiff/vcpkg-cmake-wrapper.cmake.in @@ -0,0 +1,104 @@ +cmake_policy(PUSH) +cmake_policy(SET CMP0012 NEW) +cmake_policy(SET CMP0057 NEW) +set(z_vcpkg_tiff_find_options "") +if("REQUIRED" IN_LIST ARGS) + list(APPEND z_vcpkg_tiff_find_options "REQUIRED") +endif() +if("QUIET" IN_LIST ARGS) + list(APPEND z_vcpkg_tiff_find_options "QUIET") +endif() + +_find_package(${ARGS}) + +if(TIFF_FOUND AND "@VCPKG_LIBRARY_LINKAGE@" STREQUAL "static") + include(SelectLibraryConfigurations) + set(z_vcpkg_tiff_link_libraries "") + set(z_vcpkg_tiff_libraries "") + if("@webp@") + find_package(WebP CONFIG ${z_vcpkg_tiff_find_options}) + list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:WebP::WebP>") + list(APPEND z_vcpkg_tiff_libraries ${WebP_LIBRARIES}) + endif() + if("@lzma@") + find_package(LibLZMA ${z_vcpkg_tiff_find_options}) + list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:LibLZMA::LibLZMA>") + list(APPEND z_vcpkg_tiff_libraries ${LIBLZMA_LIBRARIES}) + endif() + if("@jpeg@") + find_package(JPEG ${z_vcpkg_tiff_find_options}) + list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:JPEG::JPEG>") + list(APPEND z_vcpkg_tiff_libraries ${JPEG_LIBRARIES}) + endif() + if("@zstd@") + find_package(zstd CONFIG ${z_vcpkg_tiff_find_options}) + set(z_vcpkg_tiff_zstd_target_property "IMPORTED_LOCATION_") + if(TARGET zstd::libzstd_shared) + set(z_vcpkg_tiff_zstd "\$<LINK_ONLY:zstd::libzstd_shared>") + set(z_vcpkg_tiff_zstd_target zstd::libzstd_shared) + if(WIN32) + set(z_vcpkg_tiff_zstd_target_property "IMPORTED_IMPLIB_") + endif() + else() + set(z_vcpkg_tiff_zstd "\$<LINK_ONLY:zstd::libzstd_static>") + set(z_vcpkg_tiff_zstd_target zstd::libzstd_static) + endif() + get_target_property(z_vcpkg_tiff_zstd_configs "${z_vcpkg_tiff_zstd_target}" IMPORTED_CONFIGURATIONS) + foreach(z_vcpkg_config IN LISTS z_vcpkg_tiff_zstd_configs) + get_target_property(ZSTD_LIBRARY_${z_vcpkg_config} "${z_vcpkg_tiff_zstd_target}" "${z_vcpkg_tiff_zstd_target_property}${z_vcpkg_config}") + endforeach() + select_library_configurations(ZSTD) + if(NOT TARGET ZSTD::ZSTD) + add_library(ZSTD::ZSTD INTERFACE IMPORTED) + set_property(TARGET ZSTD::ZSTD APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${z_vcpkg_tiff_zstd}) + endif() + list(APPEND z_vcpkg_tiff_link_libraries ${z_vcpkg_tiff_zstd}) + list(APPEND z_vcpkg_tiff_libraries ${ZSTD_LIBRARIES}) + unset(z_vcpkg_tiff_zstd) + unset(z_vcpkg_tiff_zstd_configs) + unset(z_vcpkg_config) + unset(z_vcpkg_tiff_zstd_target) + endif() + if("@libdeflate@") + find_package(libdeflate ${z_vcpkg_tiff_find_options}) + set(z_vcpkg_property "IMPORTED_LOCATION_") + if(TARGET libdeflate::libdeflate_shared) + set(z_vcpkg_libdeflate_target libdeflate::libdeflate_shared) + if(WIN32) + set(z_vcpkg_property "IMPORTED_IMPLIB_") + endif() + else() + set(z_vcpkg_libdeflate_target libdeflate::libdeflate_static) + endif() + get_target_property(z_vcpkg_libdeflate_configs "${z_vcpkg_libdeflate_target}" IMPORTED_CONFIGURATIONS) + foreach(z_vcpkg_config IN LISTS z_vcpkg_libdeflate_configs) + get_target_property(Z_VCPKG_DEFLATE_LIBRARY_${z_vcpkg_config} "${z_vcpkg_libdeflate_target}" "${z_vcpkg_property}${z_vcpkg_config}") + endforeach() + select_library_configurations(Z_VCPKG_DEFLATE) + list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:${z_vcpkg_libdeflate_target}>") + list(APPEND z_vcpkg_tiff_libraries ${Z_VCPKG_DEFLATE_LIBRARIES}) + unset(z_vcpkg_config) + unset(z_vcpkg_libdeflate_configs) + unset(z_vcpkg_libdeflate_target) + unset(z_vcpkg_property) + unset(Z_VCPKG_DEFLATE_FOUND) + endif() + if("@zlib@") + find_package(ZLIB ${z_vcpkg_tiff_find_options}) + list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:ZLIB::ZLIB>") + list(APPEND z_vcpkg_tiff_libraries ${ZLIB_LIBRARIES}) + endif() + if(UNIX) + list(APPEND z_vcpkg_tiff_link_libraries m) + list(APPEND z_vcpkg_tiff_libraries m) + endif() + + if(TARGET TIFF::TIFF) + set_property(TARGET TIFF::TIFF APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${z_vcpkg_tiff_link_libraries}) + endif() + list(APPEND TIFF_LIBRARIES ${z_vcpkg_tiff_libraries}) + unset(z_vcpkg_tiff_link_libraries) + unset(z_vcpkg_tiff_libraries) +endif() +unset(z_vcpkg_tiff_find_options) +cmake_policy(POP) diff --git a/dependencies/vcpkg_overlay_ports/tiff/vcpkg.json b/dependencies/vcpkg_overlay_ports/tiff/vcpkg.json new file mode 100644 index 00000000..9b36e1a8 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports/tiff/vcpkg.json @@ -0,0 +1,67 @@ +{ + "name": "tiff", + "version": "4.6.0", + "port-version": 2, + "description": "A library that supports the manipulation of TIFF image files", + "homepage": "https://libtiff.gitlab.io/libtiff/", + "license": "libtiff", + "dependencies": [ + { + "name": "vcpkg-cmake", + "host": true + }, + { + "name": "vcpkg-cmake-config", + "host": true + } + ], + "default-features": [ + "jpeg", + "zip" + ], + "features": { + "cxx": { + "description": "Build C++ libtiffxx library" + }, + "jpeg": { + "description": "Support JPEG compression in TIFF image files", + "dependencies": [ + "libjpeg-turbo" + ] + }, + "libdeflate": { + "description": "Use libdeflate for faster ZIP support", + "dependencies": [ + "libdeflate", + { + "name": "tiff", + "default-features": false, + "features": [ + "zip" + ] + } + ] + }, + "tools": { + "description": "Build tools" + }, + "webp": { + "description": "Support WEBP compression in TIFF image files", + "dependencies": [ + "libwebp" + ] + }, + "zip": { + "description": "Support ZIP/deflate compression in TIFF image files", + "dependencies": [ + "zlib" + ] + }, + "zstd": { + "description": "Support ZSTD compression in TIFF image files", + "dependencies": [ + "zstd" + ] + } + } +} diff --git a/dependencies/vcpkg_overlay_ports_mac/tiff/FindCMath.patch b/dependencies/vcpkg_overlay_ports_mac/tiff/FindCMath.patch new file mode 100644 index 00000000..70654cf8 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_mac/tiff/FindCMath.patch @@ -0,0 +1,13 @@ +diff --git a/cmake/FindCMath.cmake b/cmake/FindCMath.cmake +index ad92218..dd42aba 100644 +--- a/cmake/FindCMath.cmake ++++ b/cmake/FindCMath.cmake +@@ -31,7 +31,7 @@ include(CheckSymbolExists) + include(CheckLibraryExists) + + check_symbol_exists(pow "math.h" CMath_HAVE_LIBC_POW) +-find_library(CMath_LIBRARY NAMES m) ++find_library(CMath_LIBRARY NAMES m PATHS ${CMAKE_C_IMPLICIT_LINK_DIRECTORIES}) + + if(NOT CMath_HAVE_LIBC_POW) + set(CMAKE_REQUIRED_LIBRARIES_SAVE ${CMAKE_REQUIRED_LIBRARIES}) diff --git a/dependencies/vcpkg_overlay_ports_mac/tiff/portfile.cmake b/dependencies/vcpkg_overlay_ports_mac/tiff/portfile.cmake new file mode 100644 index 00000000..426d8af7 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_mac/tiff/portfile.cmake @@ -0,0 +1,86 @@ +vcpkg_from_gitlab( + GITLAB_URL https://gitlab.com + OUT_SOURCE_PATH SOURCE_PATH + REPO libtiff/libtiff + REF "v${VERSION}" + SHA512 ef2f1d424219d9e245069b7d23e78f5e817cf6ee516d46694915ab6c8909522166f84997513d20a702f4e52c3f18467813935b328fafa34bea5156dee00f66fa + HEAD_REF master + PATCHES + FindCMath.patch +) + +vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS + FEATURES + cxx cxx + jpeg jpeg + jpeg CMAKE_REQUIRE_FIND_PACKAGE_JPEG + libdeflate libdeflate + libdeflate CMAKE_REQUIRE_FIND_PACKAGE_Deflate + lzma lzma + lzma CMAKE_REQUIRE_FIND_PACKAGE_liblzma + tools tiff-tools + webp webp + webp CMAKE_REQUIRE_FIND_PACKAGE_WebP + zip zlib + zip CMAKE_REQUIRE_FIND_PACKAGE_ZLIB + zstd zstd + zstd CMAKE_REQUIRE_FIND_PACKAGE_ZSTD +) + +vcpkg_cmake_configure( + SOURCE_PATH "${SOURCE_PATH}" + OPTIONS + ${FEATURE_OPTIONS} + -DCMAKE_FIND_PACKAGE_PREFER_CONFIG=ON + -Dtiff-docs=OFF + -Dtiff-contrib=OFF + -Dtiff-tests=OFF + -Djbig=OFF # This is disabled by default due to GPL/Proprietary licensing. + -Djpeg12=OFF + -Dlerc=OFF + -DCMAKE_DISABLE_FIND_PACKAGE_OpenGL=ON + -DCMAKE_DISABLE_FIND_PACKAGE_GLUT=ON + -DZSTD_HAVE_DECOMPRESS_STREAM=ON + -DHAVE_JPEGTURBO_DUAL_MODE_8_12=OFF + OPTIONS_DEBUG + -DCMAKE_DEBUG_POSTFIX=d # tiff sets "d" for MSVC only. + MAYBE_UNUSED_VARIABLES + CMAKE_DISABLE_FIND_PACKAGE_GLUT + CMAKE_DISABLE_FIND_PACKAGE_OpenGL + ZSTD_HAVE_DECOMPRESS_STREAM +) + +vcpkg_cmake_install() + +# CMake config wasn't packaged in the past and is not yet usable now, +# cf. https://gitlab.com/libtiff/libtiff/-/merge_requests/496 +# vcpkg_cmake_config_fixup(CONFIG_PATH "lib/cmake/tiff") +file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/lib/cmake" "${CURRENT_PACKAGES_DIR}/debug/lib/cmake") + +set(_file "${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/libtiff-4.pc") +if(EXISTS "${_file}") + vcpkg_replace_string("${_file}" "-ltiff" "-ltiffd") +endif() +vcpkg_fixup_pkgconfig() + +file(REMOVE_RECURSE + "${CURRENT_PACKAGES_DIR}/debug/include" + "${CURRENT_PACKAGES_DIR}/debug/share" +) + +configure_file("${CMAKE_CURRENT_LIST_DIR}/vcpkg-cmake-wrapper.cmake.in" "${CURRENT_PACKAGES_DIR}/share/${PORT}/vcpkg-cmake-wrapper.cmake" @ONLY) + +if ("tools" IN_LIST FEATURES) + vcpkg_copy_tools(TOOL_NAMES + tiffcp + tiffdump + tiffinfo + tiffset + tiffsplit + AUTO_CLEAN + ) +endif() + +vcpkg_copy_pdbs() +file(COPY "${CURRENT_PORT_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") +vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.md") diff --git a/dependencies/vcpkg_overlay_ports_mac/tiff/usage b/dependencies/vcpkg_overlay_ports_mac/tiff/usage new file mode 100644 index 00000000..d47265b1 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_mac/tiff/usage @@ -0,0 +1,9 @@ +tiff is compatible with built-in CMake targets: + + find_package(TIFF REQUIRED) + target_link_libraries(main PRIVATE TIFF::TIFF) + +tiff provides pkg-config modules: + + # Tag Image File Format (TIFF) library. + libtiff-4 diff --git a/dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg-cmake-wrapper.cmake.in b/dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg-cmake-wrapper.cmake.in new file mode 100644 index 00000000..1d04ec7a --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg-cmake-wrapper.cmake.in @@ -0,0 +1,104 @@ +cmake_policy(PUSH) +cmake_policy(SET CMP0012 NEW) +cmake_policy(SET CMP0057 NEW) +set(z_vcpkg_tiff_find_options "") +if("REQUIRED" IN_LIST ARGS) + list(APPEND z_vcpkg_tiff_find_options "REQUIRED") +endif() +if("QUIET" IN_LIST ARGS) + list(APPEND z_vcpkg_tiff_find_options "QUIET") +endif() + +_find_package(${ARGS}) + +if(TIFF_FOUND AND "@VCPKG_LIBRARY_LINKAGE@" STREQUAL "static") + include(SelectLibraryConfigurations) + set(z_vcpkg_tiff_link_libraries "") + set(z_vcpkg_tiff_libraries "") + if("@webp@") + find_package(WebP CONFIG ${z_vcpkg_tiff_find_options}) + list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:WebP::WebP>") + list(APPEND z_vcpkg_tiff_libraries ${WebP_LIBRARIES}) + endif() + if("@lzma@") + find_package(LibLZMA ${z_vcpkg_tiff_find_options}) + list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:LibLZMA::LibLZMA>") + list(APPEND z_vcpkg_tiff_libraries ${LIBLZMA_LIBRARIES}) + endif() + if("@jpeg@") + find_package(JPEG ${z_vcpkg_tiff_find_options}) + list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:JPEG::JPEG>") + list(APPEND z_vcpkg_tiff_libraries ${JPEG_LIBRARIES}) + endif() + if("@zstd@") + find_package(zstd CONFIG ${z_vcpkg_tiff_find_options}) + set(z_vcpkg_tiff_zstd_target_property "IMPORTED_LOCATION_") + if(TARGET zstd::libzstd_shared) + set(z_vcpkg_tiff_zstd "\$<LINK_ONLY:zstd::libzstd_shared>") + set(z_vcpkg_tiff_zstd_target zstd::libzstd_shared) + if(WIN32) + set(z_vcpkg_tiff_zstd_target_property "IMPORTED_IMPLIB_") + endif() + else() + set(z_vcpkg_tiff_zstd "\$<LINK_ONLY:zstd::libzstd_static>") + set(z_vcpkg_tiff_zstd_target zstd::libzstd_static) + endif() + get_target_property(z_vcpkg_tiff_zstd_configs "${z_vcpkg_tiff_zstd_target}" IMPORTED_CONFIGURATIONS) + foreach(z_vcpkg_config IN LISTS z_vcpkg_tiff_zstd_configs) + get_target_property(ZSTD_LIBRARY_${z_vcpkg_config} "${z_vcpkg_tiff_zstd_target}" "${z_vcpkg_tiff_zstd_target_property}${z_vcpkg_config}") + endforeach() + select_library_configurations(ZSTD) + if(NOT TARGET ZSTD::ZSTD) + add_library(ZSTD::ZSTD INTERFACE IMPORTED) + set_property(TARGET ZSTD::ZSTD APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${z_vcpkg_tiff_zstd}) + endif() + list(APPEND z_vcpkg_tiff_link_libraries ${z_vcpkg_tiff_zstd}) + list(APPEND z_vcpkg_tiff_libraries ${ZSTD_LIBRARIES}) + unset(z_vcpkg_tiff_zstd) + unset(z_vcpkg_tiff_zstd_configs) + unset(z_vcpkg_config) + unset(z_vcpkg_tiff_zstd_target) + endif() + if("@libdeflate@") + find_package(libdeflate ${z_vcpkg_tiff_find_options}) + set(z_vcpkg_property "IMPORTED_LOCATION_") + if(TARGET libdeflate::libdeflate_shared) + set(z_vcpkg_libdeflate_target libdeflate::libdeflate_shared) + if(WIN32) + set(z_vcpkg_property "IMPORTED_IMPLIB_") + endif() + else() + set(z_vcpkg_libdeflate_target libdeflate::libdeflate_static) + endif() + get_target_property(z_vcpkg_libdeflate_configs "${z_vcpkg_libdeflate_target}" IMPORTED_CONFIGURATIONS) + foreach(z_vcpkg_config IN LISTS z_vcpkg_libdeflate_configs) + get_target_property(Z_VCPKG_DEFLATE_LIBRARY_${z_vcpkg_config} "${z_vcpkg_libdeflate_target}" "${z_vcpkg_property}${z_vcpkg_config}") + endforeach() + select_library_configurations(Z_VCPKG_DEFLATE) + list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:${z_vcpkg_libdeflate_target}>") + list(APPEND z_vcpkg_tiff_libraries ${Z_VCPKG_DEFLATE_LIBRARIES}) + unset(z_vcpkg_config) + unset(z_vcpkg_libdeflate_configs) + unset(z_vcpkg_libdeflate_target) + unset(z_vcpkg_property) + unset(Z_VCPKG_DEFLATE_FOUND) + endif() + if("@zlib@") + find_package(ZLIB ${z_vcpkg_tiff_find_options}) + list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:ZLIB::ZLIB>") + list(APPEND z_vcpkg_tiff_libraries ${ZLIB_LIBRARIES}) + endif() + if(UNIX) + list(APPEND z_vcpkg_tiff_link_libraries m) + list(APPEND z_vcpkg_tiff_libraries m) + endif() + + if(TARGET TIFF::TIFF) + set_property(TARGET TIFF::TIFF APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${z_vcpkg_tiff_link_libraries}) + endif() + list(APPEND TIFF_LIBRARIES ${z_vcpkg_tiff_libraries}) + unset(z_vcpkg_tiff_link_libraries) + unset(z_vcpkg_tiff_libraries) +endif() +unset(z_vcpkg_tiff_find_options) +cmake_policy(POP) diff --git a/dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg.json b/dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg.json new file mode 100644 index 00000000..9b36e1a8 --- /dev/null +++ b/dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg.json @@ -0,0 +1,67 @@ +{ + "name": "tiff", + "version": "4.6.0", + "port-version": 2, + "description": "A library that supports the manipulation of TIFF image files", + "homepage": "https://libtiff.gitlab.io/libtiff/", + "license": "libtiff", + "dependencies": [ + { + "name": "vcpkg-cmake", + "host": true + }, + { + "name": "vcpkg-cmake-config", + "host": true + } + ], + "default-features": [ + "jpeg", + "zip" + ], + "features": { + "cxx": { + "description": "Build C++ libtiffxx library" + }, + "jpeg": { + "description": "Support JPEG compression in TIFF image files", + "dependencies": [ + "libjpeg-turbo" + ] + }, + "libdeflate": { + "description": "Use libdeflate for faster ZIP support", + "dependencies": [ + "libdeflate", + { + "name": "tiff", + "default-features": false, + "features": [ + "zip" + ] + } + ] + }, + "tools": { + "description": "Build tools" + }, + "webp": { + "description": "Support WEBP compression in TIFF image files", + "dependencies": [ + "libwebp" + ] + }, + "zip": { + "description": "Support ZIP/deflate compression in TIFF image files", + "dependencies": [ + "zlib" + ] + }, + "zstd": { + "description": "Support ZSTD compression in TIFF image files", + "dependencies": [ + "zstd" + ] + } + } +} From 74e8d205b07247b851c716ad285d938f4bc277b5 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 6 Apr 2024 22:18:30 +0200 Subject: [PATCH 066/299] coreinit: Handle SD mounting permission in FSGetMountSource One Piece requires this to not get stuck in an infinite loop on boot. This also sets up initial infrastructure for handling cos.xml permissions --- src/Cafe/CafeSystem.cpp | 21 +++++ src/Cafe/CafeSystem.h | 4 + src/Cafe/OS/libs/coreinit/coreinit_FS.cpp | 10 +++ src/Cafe/TitleList/TitleInfo.cpp | 43 +++++++-- src/Cafe/TitleList/TitleInfo.h | 101 ++++++++++++++++++---- 5 files changed, 157 insertions(+), 22 deletions(-) diff --git a/src/Cafe/CafeSystem.cpp b/src/Cafe/CafeSystem.cpp index 75cb1116..bde1611c 100644 --- a/src/Cafe/CafeSystem.cpp +++ b/src/Cafe/CafeSystem.cpp @@ -914,6 +914,27 @@ namespace CafeSystem return sGameInfo_ForegroundTitle.GetBase().GetArgStr(); } + CosCapabilityBits GetForegroundTitleCosCapabilities(CosCapabilityGroup group) + { + if (sLaunchModeIsStandalone) + return CosCapabilityBits::All; + auto& update = sGameInfo_ForegroundTitle.GetUpdate(); + if (update.IsValid()) + { + ParsedCosXml* cosXml = update.GetCosInfo(); + if (cosXml) + return cosXml->GetCapabilityBits(group); + } + auto& base = sGameInfo_ForegroundTitle.GetBase(); + if(base.IsValid()) + { + ParsedCosXml* cosXml = base.GetCosInfo(); + if (cosXml) + return cosXml->GetCapabilityBits(group); + } + return CosCapabilityBits::All; + } + // when switching titles custom parameters can be passed, returns true if override args are used bool GetOverrideArgStr(std::vector<std::string>& args) { diff --git a/src/Cafe/CafeSystem.h b/src/Cafe/CafeSystem.h index 336c2f40..c4043a59 100644 --- a/src/Cafe/CafeSystem.h +++ b/src/Cafe/CafeSystem.h @@ -4,6 +4,9 @@ #include "Cafe/TitleList/TitleId.h" #include "config/CemuConfig.h" +enum class CosCapabilityBits : uint64; +enum class CosCapabilityGroup : uint32; + namespace CafeSystem { class SystemImplementation @@ -41,6 +44,7 @@ namespace CafeSystem std::string GetForegroundTitleName(); std::string GetForegroundTitleArgStr(); uint32 GetForegroundTitleOlvAccesskey(); + CosCapabilityBits GetForegroundTitleCosCapabilities(CosCapabilityGroup group); void ShutdownTitle(); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp b/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp index 1e6eb92b..a007f5ee 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp @@ -11,6 +11,8 @@ #include "coreinit_IPC.h" #include "Cafe/Filesystem/fsc.h" #include "coreinit_IPCBuf.h" +#include "Cafe/CafeSystem.h" +#include "Cafe/TitleList/TitleInfo.h" #define FS_CB_PLACEHOLDER_FINISHCMD (MPTR)(0xF122330E) @@ -94,6 +96,14 @@ namespace coreinit // so we can just hard code it. Other mount types are not (yet) supported. if (mountSourceType == MOUNT_TYPE::SD) { + // check for SD card permissions (from cos.xml) + // One Piece relies on failing here, otherwise it will call FSGetMountSource in an infinite loop + CosCapabilityBitsFS perms = static_cast<CosCapabilityBitsFS>(CafeSystem::GetForegroundTitleCosCapabilities(CosCapabilityGroup::FS)); + if(!HAS_FLAG(perms, CosCapabilityBitsFS::SDCARD_MOUNT)) + { + cemuLog_logOnce(LogType::Force, "Title is trying to access SD card mount info without having SD card permissions. This may not be a bug"); + return FS_RESULT::END_ITERATION; + } mountSourceInfo->sourceType = 0; strcpy(mountSourceInfo->path, "/sd"); return FS_RESULT::SUCCESS; diff --git a/src/Cafe/TitleList/TitleInfo.cpp b/src/Cafe/TitleList/TitleInfo.cpp index d23e1d0a..6d21929e 100644 --- a/src/Cafe/TitleList/TitleInfo.cpp +++ b/src/Cafe/TitleList/TitleInfo.cpp @@ -1,13 +1,11 @@ #include "TitleInfo.h" - #include "Cafe/Filesystem/fscDeviceHostFS.h" #include "Cafe/Filesystem/FST/FST.h" - #include "pugixml.hpp" #include "Common/FileStream.h" - #include <zarchive/zarchivereader.h> #include "config/ActiveSettings.h" +#include "util/helpers/helpers.h" // detect format by reading file header/footer CafeTitleFileType DetermineCafeSystemFileType(fs::path filePath) @@ -709,10 +707,41 @@ std::string TitleInfo::GetInstallPath() const { TitleId titleId = GetAppTitleId(); TitleIdParser tip(titleId); - std::string tmp; + std::string tmp; if (tip.IsSystemTitle()) - tmp = fmt::format("sys/title/{:08x}/{:08x}", GetTitleIdHigh(titleId), GetTitleIdLow(titleId)); - else - tmp = fmt::format("usr/title/{:08x}/{:08x}", GetTitleIdHigh(titleId), GetTitleIdLow(titleId)); + tmp = fmt::format("sys/title/{:08x}/{:08x}", GetTitleIdHigh(titleId), GetTitleIdLow(titleId)); + else + tmp = fmt::format("usr/title/{:08x}/{:08x}", GetTitleIdHigh(titleId), GetTitleIdLow(titleId)); return tmp; } + +ParsedCosXml* ParsedCosXml::Parse(uint8* xmlData, size_t xmlLen) +{ + pugi::xml_document app_doc; + if (!app_doc.load_buffer_inplace(xmlData, xmlLen)) + return nullptr; + + const auto root = app_doc.child("app"); + if (!root) + return nullptr; + + ParsedCosXml* parsedCos = new ParsedCosXml(); + + auto node = root.child("argstr"); + if (node) + parsedCos->argstr = node.text().as_string(); + + // parse permissions + auto permissionsNode = root.child("permissions"); + for(uint32 permissionIndex = 0; permissionIndex < 19; ++permissionIndex) + { + std::string permissionName = fmt::format("p{}", permissionIndex); + auto permissionNode = permissionsNode.child(permissionName.c_str()); + if (!permissionNode) + break; + parsedCos->permissions[permissionIndex].group = static_cast<CosCapabilityGroup>(ConvertString<uint32>(permissionNode.child("group").text().as_string(), 10)); + parsedCos->permissions[permissionIndex].mask = static_cast<CosCapabilityBits>(ConvertString<uint64>(permissionNode.child("mask").text().as_string(), 16)); + } + + return parsedCos; +} \ No newline at end of file diff --git a/src/Cafe/TitleList/TitleInfo.h b/src/Cafe/TitleList/TitleInfo.h index eca6624d..e9347db7 100644 --- a/src/Cafe/TitleList/TitleInfo.h +++ b/src/Cafe/TitleList/TitleInfo.h @@ -26,29 +26,95 @@ struct ParsedAppXml uint32 sdk_version; }; +enum class CosCapabilityGroup : uint32 +{ + None = 0, + BSP = 1, + DK = 3, + USB = 9, + UHS = 12, + FS = 11, + MCP = 13, + NIM = 14, + ACT = 15, + FPD = 16, + BOSS = 17, + ACP = 18, + PDM = 19, + AC = 20, + NDM = 21, + NSEC = 22 +}; + +enum class CosCapabilityBits : uint64 +{ + All = 0xFFFFFFFFFFFFFFFFull +}; + +enum class CosCapabilityBitsFS : uint64 +{ + ODD_READ = (1llu << 0), + ODD_WRITE = (1llu << 1), + ODD_RAW_OPEN = (1llu << 2), + ODD_MOUNT = (1llu << 3), + SLCCMPT_READ = (1llu << 4), + SLCCMPT_WRITE = (1llu << 5), + SLCCMPT_RAW_OPEN = (1llu << 6), + SLCCMPT_MOUNT = (1llu << 7), + SLC_READ = (1llu << 8), + SLC_WRITE = (1llu << 9), + SLC_RAW_OPEN = (1llu << 10), + SLC_MOUNT = (1llu << 11), + MLC_READ = (1llu << 12), + MLC_WRITE = (1llu << 13), + MLC_RAW_OPEN = (1llu << 14), + MLC_MOUNT = (1llu << 15), + SDCARD_READ = (1llu << 16), + SDCARD_WRITE = (1llu << 17), + SDCARD_RAW_OPEN = (1llu << 18), + SDCARD_MOUNT = (1llu << 19), + HFIO_READ = (1llu << 20), + HFIO_WRITE = (1llu << 21), + HFIO_RAW_OPEN = (1llu << 22), + HFIO_MOUNT = (1llu << 23), + RAMDISK_READ = (1llu << 24), + RAMDISK_WRITE = (1llu << 25), + RAMDISK_RAW_OPEN = (1llu << 26), + RAMDISK_MOUNT = (1llu << 27), + USB_READ = (1llu << 28), + USB_WRITE = (1llu << 29), + USB_RAW_OPEN = (1llu << 30), + USB_MOUNT = (1llu << 31), + OTHER_READ = (1llu << 32), + OTHER_WRITE = (1llu << 33), + OTHER_RAW_OPEN = (1llu << 34), + OTHER_MOUNT = (1llu << 35) +}; +ENABLE_BITMASK_OPERATORS(CosCapabilityBitsFS); + struct ParsedCosXml { + public: + std::string argstr; - static ParsedCosXml* Parse(uint8* xmlData, size_t xmlLen) + struct Permission { - pugi::xml_document app_doc; - if (!app_doc.load_buffer_inplace(xmlData, xmlLen)) - return nullptr; + CosCapabilityGroup group{CosCapabilityGroup::None}; + CosCapabilityBits mask{CosCapabilityBits::All}; + }; + Permission permissions[19]{}; - const auto root = app_doc.child("app"); - if (!root) - return nullptr; + static ParsedCosXml* Parse(uint8* xmlData, size_t xmlLen); - ParsedCosXml* parsedCos = new ParsedCosXml(); - - for (const auto& child : root.children()) + CosCapabilityBits GetCapabilityBits(CosCapabilityGroup group) const + { + for (const auto& perm : permissions) { - std::string_view name = child.name(); - if (name == "argstr") - parsedCos->argstr = child.text().as_string(); + if (perm.group == group) + return perm.mask; } - return parsedCos; + return CosCapabilityBits::All; } }; @@ -151,7 +217,7 @@ public: // cos.xml std::string GetArgStr() const; - // meta.xml also contains a version which seems to match the one from app.xml + // meta.xml also contains a version field which seems to match the one from app.xml // the titleId in meta.xml seems to be the title id of the base game for updates specifically. For AOC content it's the AOC's titleId TitleIdParser::TITLE_TYPE GetTitleType(); @@ -160,6 +226,11 @@ public: return m_parsedMetaXml; } + ParsedCosXml* GetCosInfo() + { + return m_parsedCosXml; + } + std::string GetPrintPath() const; // formatted path including type and WUA subpath. Intended for logging and user-facing information std::string GetInstallPath() const; // installation subpath, relative to storage base. E.g. "usr/title/.../..." or "sys/title/.../..." From efbf712305fe59081d90d566e0ec310ae68c969c Mon Sep 17 00:00:00 2001 From: Maschell <Maschell@gmx.de> Date: Mon, 8 Apr 2024 19:15:49 +0200 Subject: [PATCH 067/299] nn_sl: Stub GetDefaultWhiteListAccessor__Q2_2nn2slFv to avoid crash in Wii U Menu when an online account is used (#1159) --- src/Cafe/CMakeLists.txt | 2 + src/Cafe/OS/common/OSCommon.cpp | 2 + src/Cafe/OS/libs/nn_sl/nn_sl.cpp | 115 +++++++++++++++++++++++++++++++ src/Cafe/OS/libs/nn_sl/nn_sl.h | 1 + src/Cemu/Logging/CemuLogging.h | 1 + 5 files changed, 121 insertions(+) create mode 100644 src/Cafe/OS/libs/nn_sl/nn_sl.cpp create mode 100644 src/Cafe/OS/libs/nn_sl/nn_sl.h diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index 20853789..d64a5998 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -404,6 +404,8 @@ add_library(CemuCafe OS/libs/nn_ndm/nn_ndm.h OS/libs/nn_spm/nn_spm.cpp OS/libs/nn_spm/nn_spm.h + OS/libs/nn_sl/nn_sl.cpp + OS/libs/nn_sl/nn_sl.h OS/libs/nn_nfp/AmiiboCrypto.h OS/libs/nn_nfp/nn_nfp.cpp OS/libs/nn_nfp/nn_nfp.h diff --git a/src/Cafe/OS/common/OSCommon.cpp b/src/Cafe/OS/common/OSCommon.cpp index 5aedd197..a4410028 100644 --- a/src/Cafe/OS/common/OSCommon.cpp +++ b/src/Cafe/OS/common/OSCommon.cpp @@ -13,6 +13,7 @@ #include "Cafe/OS/libs/nn_spm/nn_spm.h" #include "Cafe/OS/libs/nn_ec/nn_ec.h" #include "Cafe/OS/libs/nn_boss/nn_boss.h" +#include "Cafe/OS/libs/nn_sl/nn_sl.h" #include "Cafe/OS/libs/nn_fp/nn_fp.h" #include "Cafe/OS/libs/nn_olv/nn_olv.h" #include "Cafe/OS/libs/nn_idbe/nn_idbe.h" @@ -208,6 +209,7 @@ void osLib_load() nn::ndm::load(); nn::spm::load(); nn::save::load(); + nnSL_load(); nsysnet_load(); nn::fp::load(); nn::olv::load(); diff --git a/src/Cafe/OS/libs/nn_sl/nn_sl.cpp b/src/Cafe/OS/libs/nn_sl/nn_sl.cpp new file mode 100644 index 00000000..b25a91bc --- /dev/null +++ b/src/Cafe/OS/libs/nn_sl/nn_sl.cpp @@ -0,0 +1,115 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/coreinit/coreinit_IOS.h" +#include "Cafe/OS/libs/coreinit/coreinit_MEM.h" +#include "config/ActiveSettings.h" +#include "Cafe/CafeSystem.h" + +namespace nn +{ + typedef uint32 Result; + namespace sl + { + struct VTableEntry + { + uint16be offsetA{0}; + uint16be offsetB{0}; + MEMPTR<void> ptr; + }; + static_assert(sizeof(VTableEntry) == 8); + + constexpr uint32 SL_MEM_MAGIC = 0xCAFE4321; + +#define DTOR_WRAPPER(__TYPE) RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) { dtor(MEMPTR<__TYPE>(hCPU->gpr[3]), hCPU->gpr[4]); osLib_returnFromFunction(hCPU, 0); }) + + template<typename T> + MEMPTR<T> sl_new() + { + uint32 objSize = sizeof(T); + uint32be* basePtr = (uint32be*)coreinit::_weak_MEMAllocFromDefaultHeapEx(objSize + 8, 0x8); + basePtr[0] = SL_MEM_MAGIC; + basePtr[1] = objSize; + return (T*)(basePtr + 2); + } + + void sl_delete(MEMPTR<void> mem) + { + if (!mem) + return; + uint32be* basePtr = (uint32be*)mem.GetPtr() - 2; + if (basePtr[0] != SL_MEM_MAGIC) + { + cemuLog_log(LogType::Force, "nn_sl: Detected memory corruption"); + cemu_assert_suspicious(); + } + coreinit::_weak_MEMFreeToDefaultHeap(basePtr); + } + +#pragma pack(1) + struct WhiteList + { + uint32be titleTypes[50]; + uint32be titleTypesCount; + uint32be padding; + uint64be titleIds[50]; + uint32be titleIdCount; + }; + static_assert(sizeof(WhiteList) == 0x264); +#pragma pack() + + struct WhiteListAccessor + { + MEMPTR<void> vTablePtr{}; // 0x00 + + struct VTable + { + VTableEntry rtti; + VTableEntry dtor; + VTableEntry get; + }; + static inline SysAllocator<VTable> s_titleVTable; + + static WhiteListAccessor* ctor(WhiteListAccessor* _this) + { + if (!_this) + _this = sl_new<WhiteListAccessor>(); + *_this = {}; + _this->vTablePtr = s_titleVTable; + return _this; + } + + static void dtor(WhiteListAccessor* _this, uint32 options) + { + if (_this && (options & 1)) + sl_delete(_this); + } + + static void Get(WhiteListAccessor* _this, nn::sl::WhiteList* outWhiteList) + { + *outWhiteList = {}; + } + + static void InitVTable() + { + s_titleVTable->rtti.ptr = nullptr; // todo + s_titleVTable->dtor.ptr = DTOR_WRAPPER(WhiteListAccessor); + s_titleVTable->get.ptr = RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) { Get(MEMPTR<WhiteListAccessor>(hCPU->gpr[3]), MEMPTR<WhiteList>(hCPU->gpr[4])); osLib_returnFromFunction(hCPU, 0); }); + } + }; + static_assert(sizeof(WhiteListAccessor) == 0x04); + + SysAllocator<WhiteListAccessor> s_defaultWhiteListAccessor; + + WhiteListAccessor* GetDefaultWhiteListAccessor() + { + return s_defaultWhiteListAccessor; + } + } // namespace sl +} // namespace nn + +void nnSL_load() +{ + nn::sl::WhiteListAccessor::InitVTable(); + nn::sl::WhiteListAccessor::ctor(nn::sl::s_defaultWhiteListAccessor); + + cafeExportRegisterFunc(nn::sl::GetDefaultWhiteListAccessor, "nn_sl", "GetDefaultWhiteListAccessor__Q2_2nn2slFv", LogType::NN_SL); +} diff --git a/src/Cafe/OS/libs/nn_sl/nn_sl.h b/src/Cafe/OS/libs/nn_sl/nn_sl.h new file mode 100644 index 00000000..08d936cb --- /dev/null +++ b/src/Cafe/OS/libs/nn_sl/nn_sl.h @@ -0,0 +1 @@ +void nnSL_load(); \ No newline at end of file diff --git a/src/Cemu/Logging/CemuLogging.h b/src/Cemu/Logging/CemuLogging.h index fe74a6bc..e789c2ea 100644 --- a/src/Cemu/Logging/CemuLogging.h +++ b/src/Cemu/Logging/CemuLogging.h @@ -36,6 +36,7 @@ enum class LogType : sint32 NN_NFP = 13, NN_FP = 24, NN_BOSS = 25, + NN_SL = 26, TextureReadback = 29, From 9b30be02585ac3419973c2cbef30a5300d768d09 Mon Sep 17 00:00:00 2001 From: Maschell <Maschell@gmx.de> Date: Mon, 8 Apr 2024 19:50:57 +0200 Subject: [PATCH 068/299] drmapp: Stub more functions to allow title loading from Wii U Menu (#1161) --- src/Cafe/OS/libs/drmapp/drmapp.cpp | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/Cafe/OS/libs/drmapp/drmapp.cpp b/src/Cafe/OS/libs/drmapp/drmapp.cpp index e1969486..6c57b209 100644 --- a/src/Cafe/OS/libs/drmapp/drmapp.cpp +++ b/src/Cafe/OS/libs/drmapp/drmapp.cpp @@ -9,8 +9,29 @@ namespace drmapp return 1; } + uint32 PatchChkIsFinished() + { + cemuLog_logDebug(LogType::Force, "drmapp.PatchChkIsFinished() - placeholder"); + return 1; + } + + uint32 AocChkIsFinished() + { + cemuLog_logDebug(LogType::Force, "drmapp.AocChkIsFinished() - placeholder"); + return 1; + } + + uint32 TicketChkIsFinished() + { + cemuLog_logDebug(LogType::Force, "drmapp.TicketChkIsFinished__3RplFv() - placeholder"); + return 1; + } + void Initialize() { cafeExportRegisterFunc(NupChkIsFinished, "drmapp", "NupChkIsFinished__3RplFv", LogType::Placeholder); + cafeExportRegisterFunc(PatchChkIsFinished, "drmapp", "PatchChkIsFinished__3RplFv", LogType::Placeholder); + cafeExportRegisterFunc(AocChkIsFinished, "drmapp", "AocChkIsFinished__3RplFv", LogType::Placeholder); + cafeExportRegisterFunc(TicketChkIsFinished, "drmapp", "TicketChkIsFinished__3RplFv", LogType::Placeholder); } -} +} // namespace drmapp From 7b635e7eb87784a21014e8fcf0fdc420cf3a8c8d Mon Sep 17 00:00:00 2001 From: Maschell <Maschell@gmx.de> Date: Mon, 8 Apr 2024 19:51:30 +0200 Subject: [PATCH 069/299] nn_boss: Implement startIndex parameter usage in nn:boss:::GetDataList (#1162) --- src/Cafe/OS/libs/nn_boss/nn_boss.cpp | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/Cafe/OS/libs/nn_boss/nn_boss.cpp b/src/Cafe/OS/libs/nn_boss/nn_boss.cpp index f53a6d79..2a05fa7a 100644 --- a/src/Cafe/OS/libs/nn_boss/nn_boss.cpp +++ b/src/Cafe/OS/libs/nn_boss/nn_boss.cpp @@ -9,7 +9,7 @@ #include "Cafe/CafeSystem.h" #include "Cafe/Filesystem/fsc.h" -namespace nn +namespace nn { typedef uint32 Result; namespace boss @@ -782,9 +782,9 @@ bossBufferVector->buffer = (uint8*)bossRequest; bossRequest->taskId = _thisptr->taskId.id; bossRequest->titleId = _thisptr->titleId.u64; bossRequest->bool_parameter = isForegroundRun != 0; - + __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - + return 0; } @@ -796,9 +796,9 @@ bossBufferVector->buffer = (uint8*)bossRequest; bossRequest->taskId = _thisptr->taskId.id; bossRequest->titleId = _thisptr->titleId.u64; bossRequest->bool_parameter = executeImmediately != 0; - + __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - + return 0; } @@ -809,9 +809,9 @@ bossBufferVector->buffer = (uint8*)bossRequest; bossRequest->accountId = _thisptr->accountId; bossRequest->taskId = _thisptr->taskId.id; bossRequest->titleId = _thisptr->titleId.u64; - + __depr__IOS_Ioctlv(IOS_DEVICE_BOSS, IOSU_BOSS_REQUEST_CEMU, 1, 1, bossBufferVector); - + return 0; } @@ -1001,7 +1001,7 @@ bossBufferVector->buffer = (uint8*)bossRequest; } }; static_assert(sizeof(PrivilegedTask) == 0x20); - + struct AlmightyTask : PrivilegedTask { struct VTableAlmightyTask : public VTablePrivilegedTask @@ -1169,14 +1169,17 @@ bossBufferVector->buffer = (uint8*)bossRequest; // initialize titleId of storage if not already done nnBossStorage_prepareTitleId(storage); - cemu_assert_debug(startIndex == 0); // non-zero index is todo + if(startIndex >= FAD_ENTRY_MAX_COUNT) { + *outputEntryCount = 0; + return 0; + } // load fad.db BossStorageFadEntry* fadTable = nnBossStorageFad_getTable(storage); if (fadTable) { sint32 validEntryCount = 0; - for (sint32 i = 0; i < FAD_ENTRY_MAX_COUNT; i++) + for (sint32 i = startIndex; i < FAD_ENTRY_MAX_COUNT; i++) { if( fadTable[i].name[0] == '\0' ) continue; @@ -1612,7 +1615,7 @@ bossBufferVector->buffer = (uint8*)bossRequest; }; static_assert(sizeof(NsData) == 0x58); -} +} } void nnBoss_load() { @@ -1663,7 +1666,7 @@ void nnBoss_load() cafeExportRegisterFunc(nn::boss::NbdlTaskSetting::dtor, "nn_boss", "__dt__Q3_2nn4boss15NbdlTaskSettingFv", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::NbdlTaskSetting::Initialize, "nn_boss", "Initialize__Q3_2nn4boss15NbdlTaskSettingFPCcLT1", LogType::NN_BOSS); cafeExportRegisterFunc(nn::boss::NbdlTaskSetting::SetFileName, "nn_boss", "SetFileName__Q3_2nn4boss15NbdlTaskSettingFPCc", LogType::NN_BOSS); - + // PlayReportSetting nn::boss::PlayReportSetting::InitVTable(); cafeExportRegisterFunc(nn::boss::PlayReportSetting::ctor, "nn_boss", "__ct__Q3_2nn4boss17PlayReportSettingFv", LogType::NN_BOSS); From 33a74c203574090d563288ea05ffd28323e8c544 Mon Sep 17 00:00:00 2001 From: 47463915 <147349656+47463915@users.noreply.github.com> Date: Mon, 8 Apr 2024 19:33:50 -0300 Subject: [PATCH 070/299] nn_nfp: Avoid current app from showing up as "???" for others in Friend List + View friends' status (#1157) --- src/Cafe/IOSU/legacy/iosu_fpd.cpp | 33 ++++++++++++++++++++++++++----- src/Cafe/IOSU/legacy/iosu_fpd.h | 4 ++-- src/Cemu/nex/nexFriends.cpp | 13 ++++++++++++ src/Cemu/nex/nexFriends.h | 4 ++-- 4 files changed, 45 insertions(+), 9 deletions(-) diff --git a/src/Cafe/IOSU/legacy/iosu_fpd.cpp b/src/Cafe/IOSU/legacy/iosu_fpd.cpp index 9130b28d..aca1a332 100644 --- a/src/Cafe/IOSU/legacy/iosu_fpd.cpp +++ b/src/Cafe/IOSU/legacy/iosu_fpd.cpp @@ -214,6 +214,12 @@ namespace iosu friendData->friendExtraData.gameKey.ukn08 = frd->presence.gameKey.ukn; NexPresenceToGameMode(&frd->presence, &friendData->friendExtraData.gameMode); + auto fixed_presence_msg = '\0' + frd->presence.msg; // avoid first character of comment from being cut off + friendData->friendExtraData.gameModeDescription.assignFromUTF8(fixed_presence_msg); + + auto fixed_comment = '\0' + frd->comment.commentString; // avoid first character of comment from being cut off + friendData->friendExtraData.comment.assignFromUTF8(fixed_comment); + // set valid dates friendData->uknDate.year = 2018; friendData->uknDate.day = 1; @@ -750,9 +756,18 @@ namespace iosu { if(numVecIn != 0 || numVecOut != 1) return FPResult_InvalidIPCParam; - SelfPlayingGame selfPlayingGame{0}; - cemuLog_log(LogType::Force, "GetMyPlayingGame is todo"); - return WriteValueOutput<SelfPlayingGame>(vecOut, selfPlayingGame); + GameKey selfPlayingGame + { + CafeSystem::GetForegroundTitleId(), + CafeSystem::GetForegroundTitleVersion(), + {0,0,0,0,0,0} + }; + if (GetTitleIdHigh(CafeSystem::GetForegroundTitleId()) != 0x00050000) + { + selfPlayingGame.titleId = 0; + selfPlayingGame.ukn08 = 0; + } + return WriteValueOutput<GameKey>(vecOut, selfPlayingGame); } nnResult CallHandler_GetFriendAccountId(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) @@ -1410,8 +1425,16 @@ namespace iosu act::getCountryIndex(currentSlot, &countryCode); // init presence g_fpd.myPresence.isOnline = 1; - g_fpd.myPresence.gameKey.titleId = CafeSystem::GetForegroundTitleId(); - g_fpd.myPresence.gameKey.ukn = CafeSystem::GetForegroundTitleVersion(); + if (GetTitleIdHigh(CafeSystem::GetForegroundTitleId()) == 0x00050000) + { + g_fpd.myPresence.gameKey.titleId = CafeSystem::GetForegroundTitleId(); + g_fpd.myPresence.gameKey.ukn = CafeSystem::GetForegroundTitleVersion(); + } + else + { + g_fpd.myPresence.gameKey.titleId = 0; // icon will not be ??? or invalid to others + g_fpd.myPresence.gameKey.ukn = 0; + } // resolve potential domain to IP address struct addrinfo hints = {0}, *addrs; hints.ai_family = AF_INET; diff --git a/src/Cafe/IOSU/legacy/iosu_fpd.h b/src/Cafe/IOSU/legacy/iosu_fpd.h index 79f524d6..0a6f0885 100644 --- a/src/Cafe/IOSU/legacy/iosu_fpd.h +++ b/src/Cafe/IOSU/legacy/iosu_fpd.h @@ -94,7 +94,7 @@ namespace iosu /* +0x1EC */ uint8 isOnline; /* +0x1ED */ uint8 _padding1ED[3]; // some other sub struct? - /* +0x1F0 */ char comment[36]; // pops up every few seconds in friend list + /* +0x1F0 */ CafeWideString<0x12> comment; // pops up every few seconds in friend list /* +0x214 */ uint32be _padding214; /* +0x218 */ FPDDate approvalTime; /* +0x220 */ FPDDate lastOnline; @@ -263,4 +263,4 @@ namespace iosu IOSUModule* GetModule(); } -} \ No newline at end of file +} diff --git a/src/Cemu/nex/nexFriends.cpp b/src/Cemu/nex/nexFriends.cpp index 4fae8143..ae87ce44 100644 --- a/src/Cemu/nex/nexFriends.cpp +++ b/src/Cemu/nex/nexFriends.cpp @@ -1,6 +1,7 @@ #include "prudp.h" #include "nex.h" #include "nexFriends.h" +#include "Cafe/CafeSystem.h" static const int NOTIFICATION_SRV_FRIEND_OFFLINE = 0x0A; // the opposite event (friend online) is notified via _PRESENCE_CHANGE static const int NOTIFICATION_SRV_FRIEND_PRESENCE_CHANGE = 0x18; @@ -912,6 +913,18 @@ void NexFriends::markFriendRequestsAsReceived(uint64* messageIdList, sint32 coun void NexFriends::updateMyPresence(nexPresenceV2& myPresence) { this->myPresence = myPresence; + + if (GetTitleIdHigh(CafeSystem::GetForegroundTitleId()) == 0x00050000) + { + myPresence.gameKey.titleId = CafeSystem::GetForegroundTitleId(); + myPresence.gameKey.ukn = CafeSystem::GetForegroundTitleVersion(); + } + else + { + myPresence.gameKey.titleId = 0; // icon will not be ??? or invalid to others + myPresence.gameKey.ukn = 0; + } + if (nexCon == nullptr || nexCon->getState() != nexService::STATE_CONNECTED) { // not connected diff --git a/src/Cemu/nex/nexFriends.h b/src/Cemu/nex/nexFriends.h index 06c75110..1077b0d5 100644 --- a/src/Cemu/nex/nexFriends.h +++ b/src/Cemu/nex/nexFriends.h @@ -431,7 +431,7 @@ public: { nnaInfo.readData(pb); presence.readData(pb); - gameModeMessage.readData(pb); + comment.readData(pb); friendsSinceTimestamp = pb->readU64(); lastOnlineTimestamp = pb->readU64(); ukn6 = pb->readU64(); @@ -439,7 +439,7 @@ public: public: nexNNAInfo nnaInfo; nexPresenceV2 presence; - nexComment gameModeMessage; + nexComment comment; uint64 friendsSinceTimestamp; uint64 lastOnlineTimestamp; uint64 ukn6; From 12eda103876287283442880908425f751d14fd37 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 10 Apr 2024 20:22:17 +0200 Subject: [PATCH 071/299] nn_acp: Implement ACPGetOlvAccesskey + code clean up Added ACPGetOlvAccesskey() which is used by Super Mario Maker iosu acp, nn_acp and nn_save all cross talk with each other and are mostly legacy code. Modernized it a tiny bit and moved functions to where they should be. A larger refactor should be done in the future but for now this works ok --- src/Cafe/CafeSystem.cpp | 1 + src/Cafe/IOSU/legacy/iosu_acp.cpp | 289 ++++++++++++++++++++++----- src/Cafe/IOSU/legacy/iosu_acp.h | 23 +++ src/Cafe/IOSU/legacy/iosu_act.cpp | 12 ++ src/Cafe/IOSU/legacy/iosu_act.h | 1 + src/Cafe/IOSU/nn/iosu_nn_service.h | 4 +- src/Cafe/OS/libs/nn_acp/nn_acp.cpp | 209 ++----------------- src/Cafe/OS/libs/nn_acp/nn_acp.h | 14 +- src/Cafe/OS/libs/nn_save/nn_save.cpp | 10 +- 9 files changed, 314 insertions(+), 249 deletions(-) diff --git a/src/Cafe/CafeSystem.cpp b/src/Cafe/CafeSystem.cpp index bde1611c..3c62a686 100644 --- a/src/Cafe/CafeSystem.cpp +++ b/src/Cafe/CafeSystem.cpp @@ -530,6 +530,7 @@ namespace CafeSystem { // entries in this list are ordered by initialization order. Shutdown in reverse order iosu::kernel::GetModule(), + iosu::acp::GetModule(), iosu::fpd::GetModule(), iosu::pdm::GetModule(), }; diff --git a/src/Cafe/IOSU/legacy/iosu_acp.cpp b/src/Cafe/IOSU/legacy/iosu_acp.cpp index ef5f7083..f5144ee6 100644 --- a/src/Cafe/IOSU/legacy/iosu_acp.cpp +++ b/src/Cafe/IOSU/legacy/iosu_acp.cpp @@ -8,10 +8,19 @@ #include "Cafe/OS/libs/nn_acp/nn_acp.h" #include "Cafe/OS/libs/coreinit/coreinit_FS.h" #include "Cafe/Filesystem/fsc.h" -#include "Cafe/HW/Espresso/PPCState.h" +//#include "Cafe/HW/Espresso/PPCState.h" + +#include "Cafe/IOSU/iosu_types_common.h" +#include "Cafe/IOSU/nn/iosu_nn_service.h" + +#include "Cafe/IOSU/legacy/iosu_act.h" +#include "Cafe/CafeSystem.h" +#include "config/ActiveSettings.h" #include <inttypes.h> +using ACPDeviceType = iosu::acp::ACPDeviceType; + static_assert(sizeof(acpMetaXml_t) == 0x3440); static_assert(offsetof(acpMetaXml_t, title_id) == 0x0000); static_assert(offsetof(acpMetaXml_t, boss_id) == 0x0008); @@ -506,48 +515,6 @@ namespace iosu return 0; } - sint32 ACPCreateSaveDirEx(uint8 accountSlot, uint64 titleId) - { - uint32 persistentId = 0; - nn::save::GetPersistentIdEx(accountSlot, &persistentId); - - uint32 high = GetTitleIdHigh(titleId) & (~0xC); - uint32 low = GetTitleIdLow(titleId); - - sint32 fscStatus = FSC_STATUS_FILE_NOT_FOUND; - char path[256]; - - sprintf(path, "%susr/boss/", "/vol/storage_mlc01/"); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/boss/%08x/", "/vol/storage_mlc01/", high); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/boss/%08x/%08x/", "/vol/storage_mlc01/", high, low); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/boss/%08x/%08x/user/", "/vol/storage_mlc01/", high, low); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/boss/%08x/%08x/user/common", "/vol/storage_mlc01/", high, low); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/boss/%08x/%08x/user/%08x/", "/vol/storage_mlc01/", high, low, persistentId == 0 ? 0x80000001 : persistentId); - fsc_createDir(path, &fscStatus); - - sprintf(path, "%susr/save/%08x/", "/vol/storage_mlc01/", high); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/save/%08x/%08x/", "/vol/storage_mlc01/", high, low); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/save/%08x/%08x/meta/", "/vol/storage_mlc01/", high, low); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/save/%08x/%08x/user/", "/vol/storage_mlc01/", high, low); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/save/%08x/%08x/user/common", "/vol/storage_mlc01/", high, low); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/save/%08x/%08x/user/%08x", "/vol/storage_mlc01/", high, low, persistentId == 0 ? 0x80000001 : persistentId); - fsc_createDir(path, &fscStatus); - - // copy xml meta files - nn::acp::CreateSaveMetaFiles(persistentId, titleId); - return 0; - } - int iosuAcp_thread() { SetThreadName("iosuAcp_thread"); @@ -584,7 +551,7 @@ namespace iosu } else if (acpCemuRequest->requestCode == IOSU_ACP_CREATE_SAVE_DIR_EX) { - acpCemuRequest->returnCode = ACPCreateSaveDirEx(acpCemuRequest->accountSlot, acpCemuRequest->titleId); + acpCemuRequest->returnCode = acp::ACPCreateSaveDirEx(acpCemuRequest->accountSlot, acpCemuRequest->titleId); } else cemu_assert_unimplemented(); @@ -610,5 +577,237 @@ namespace iosu return iosuAcp.isInitialized; } + /* Above is the legacy implementation. Below is the new style implementation which also matches the official IPC protocol and works with the real nn_acp.rpl */ -} + namespace acp + { + + uint64 _ACPGetTimestamp() + { + return coreinit::coreinit_getOSTime() / ESPRESSO_TIMER_CLOCK; + } + + nnResult ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, ACPDeviceType deviceType) + { + if (deviceType == ACPDeviceType::UnknownType) + { + return (nnResult)0xA030FB80; + } + + // create or modify the saveinfo + const auto saveinfoPath = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/saveinfo.xml", GetTitleIdHigh(titleId), GetTitleIdLow(titleId)); + auto saveinfoData = FileStream::LoadIntoMemory(saveinfoPath); + if (saveinfoData && !saveinfoData->empty()) + { + namespace xml = tinyxml2; + xml::XMLDocument doc; + tinyxml2::XMLError xmlError = doc.Parse((const char*)saveinfoData->data(), saveinfoData->size()); + if (xmlError == xml::XML_SUCCESS || xmlError == xml::XML_ERROR_EMPTY_DOCUMENT) + { + xml::XMLNode* child = doc.FirstChild(); + // check for declaration -> <?xml version="1.0" encoding="utf-8"?> + if (!child || !child->ToDeclaration()) + { + xml::XMLDeclaration* decl = doc.NewDeclaration(); + doc.InsertFirstChild(decl); + } + + xml::XMLElement* info = doc.FirstChildElement("info"); + if (!info) + { + info = doc.NewElement("info"); + doc.InsertEndChild(info); + } + + // find node with persistentId + char tmp[64]; + sprintf(tmp, "%08x", persistentId); + bool foundNode = false; + for (xml::XMLElement* account = info->FirstChildElement("account"); account; account = account->NextSiblingElement("account")) + { + if (account->Attribute("persistentId", tmp)) + { + // found the entry! -> update timestamp + xml::XMLElement* timestamp = account->FirstChildElement("timestamp"); + sprintf(tmp, "%" PRIx64, _ACPGetTimestamp()); + if (timestamp) + timestamp->SetText(tmp); + else + { + timestamp = doc.NewElement("timestamp"); + account->InsertFirstChild(timestamp); + } + + foundNode = true; + break; + } + } + + if (!foundNode) + { + tinyxml2::XMLElement* account = doc.NewElement("account"); + { + sprintf(tmp, "%08x", persistentId); + account->SetAttribute("persistentId", tmp); + + tinyxml2::XMLElement* timestamp = doc.NewElement("timestamp"); + { + sprintf(tmp, "%" PRIx64, _ACPGetTimestamp()); + timestamp->SetText(tmp); + } + + account->InsertFirstChild(timestamp); + } + + info->InsertFirstChild(account); + } + + // update file + tinyxml2::XMLPrinter printer; + doc.Print(&printer); + FileStream* fs = FileStream::createFile2(saveinfoPath); + if (fs) + { + fs->writeString(printer.CStr()); + delete fs; + } + } + } + return NN_RESULT_SUCCESS; + } + + void CreateSaveMetaFiles(uint32 persistentId, uint64 titleId) + { + std::string titlePath = CafeSystem::GetMlcStoragePath(CafeSystem::GetForegroundTitleId()); + + sint32 fscStatus; + FSCVirtualFile* fscFile = fsc_open((titlePath + "/meta/meta.xml").c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); + if (fscFile) + { + sint32 fileSize = fsc_getFileSize(fscFile); + + std::unique_ptr<uint8[]> fileContent = std::make_unique<uint8[]>(fileSize); + fsc_readFile(fscFile, fileContent.get(), fileSize); + fsc_close(fscFile); + + const auto outPath = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/meta.xml", GetTitleIdHigh(titleId), GetTitleIdLow(titleId)); + + std::ofstream myFile(outPath, std::ios::out | std::ios::binary); + myFile.write((char*)fileContent.get(), fileSize); + myFile.close(); + } + + fscFile = fsc_open((titlePath + "/meta/iconTex.tga").c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); + if (fscFile) + { + sint32 fileSize = fsc_getFileSize(fscFile); + + std::unique_ptr<uint8[]> fileContent = std::make_unique<uint8[]>(fileSize); + fsc_readFile(fscFile, fileContent.get(), fileSize); + fsc_close(fscFile); + + const auto outPath = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/iconTex.tga", GetTitleIdHigh(titleId), GetTitleIdLow(titleId)); + + std::ofstream myFile(outPath, std::ios::out | std::ios::binary); + myFile.write((char*)fileContent.get(), fileSize); + myFile.close(); + } + + ACPUpdateSaveTimeStamp(persistentId, titleId, iosu::acp::ACPDeviceType::InternalDeviceType); + } + + + sint32 _ACPCreateSaveDir(uint32 persistentId, uint64 titleId, ACPDeviceType type) + { + uint32 high = GetTitleIdHigh(titleId) & (~0xC); + uint32 low = GetTitleIdLow(titleId); + + sint32 fscStatus = FSC_STATUS_FILE_NOT_FOUND; + char path[256]; + + sprintf(path, "%susr/boss/", "/vol/storage_mlc01/"); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/boss/%08x/", "/vol/storage_mlc01/", high); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/boss/%08x/%08x/", "/vol/storage_mlc01/", high, low); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/boss/%08x/%08x/user/", "/vol/storage_mlc01/", high, low); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/boss/%08x/%08x/user/common", "/vol/storage_mlc01/", high, low); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/boss/%08x/%08x/user/%08x/", "/vol/storage_mlc01/", high, low, persistentId == 0 ? 0x80000001 : persistentId); + fsc_createDir(path, &fscStatus); + + sprintf(path, "%susr/save/%08x/", "/vol/storage_mlc01/", high); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/save/%08x/%08x/", "/vol/storage_mlc01/", high, low); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/save/%08x/%08x/meta/", "/vol/storage_mlc01/", high, low); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/save/%08x/%08x/user/", "/vol/storage_mlc01/", high, low); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/save/%08x/%08x/user/common", "/vol/storage_mlc01/", high, low); + fsc_createDir(path, &fscStatus); + sprintf(path, "%susr/save/%08x/%08x/user/%08x", "/vol/storage_mlc01/", high, low, persistentId == 0 ? 0x80000001 : persistentId); + fsc_createDir(path, &fscStatus); + + // copy xml meta files + CreateSaveMetaFiles(persistentId, titleId); + return 0; + } + + nnResult ACPCreateSaveDir(uint32 persistentId, ACPDeviceType type) + { + uint64 titleId = CafeSystem::GetForegroundTitleId(); + return _ACPCreateSaveDir(persistentId, titleId, type); + } + + sint32 ACPCreateSaveDirEx(uint8 accountSlot, uint64 titleId) + { + uint32 persistentId = 0; + cemu_assert_debug(accountSlot >= 1 && accountSlot <= 13); // outside valid slot range? + bool r = iosu::act::GetPersistentId(accountSlot, &persistentId); + cemu_assert_debug(r); + return _ACPCreateSaveDir(persistentId, titleId, ACPDeviceType::InternalDeviceType); + } + + nnResult ACPGetOlvAccesskey(uint32be* accessKey) + { + *accessKey = CafeSystem::GetForegroundTitleOlvAccesskey(); + return 0; + } + + class AcpMainService : public iosu::nn::IPCService + { + public: + AcpMainService() : iosu::nn::IPCService("/dev/acp_main") {} + + nnResult ServiceCall(uint32 serviceId, void* request, void* response) override + { + cemuLog_log(LogType::Force, "Unsupported service call to /dev/acp_main"); + cemu_assert_unimplemented(); + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_ACP, 0); + } + }; + + AcpMainService gACPMainService; + + class : public ::IOSUModule + { + void TitleStart() override + { + gACPMainService.Start(); + // gACPMainService.SetTimerUpdate(1000); // call TimerUpdate() once a second + } + void TitleStop() override + { + gACPMainService.Stop(); + } + }sIOSUModuleNNACP; + + IOSUModule* GetModule() + { + return static_cast<IOSUModule*>(&sIOSUModuleNNACP); + } + } // namespace acp +} // namespace iosu diff --git a/src/Cafe/IOSU/legacy/iosu_acp.h b/src/Cafe/IOSU/legacy/iosu_acp.h index 18197bd8..a6fb6bfd 100644 --- a/src/Cafe/IOSU/legacy/iosu_acp.h +++ b/src/Cafe/IOSU/legacy/iosu_acp.h @@ -1,5 +1,8 @@ #pragma once +#include "Cafe/IOSU/iosu_types_common.h" +#include "Cafe/OS/libs/nn_common.h" // for nnResult + typedef struct { /* +0x0000 */ uint64 title_id; // parsed via GetHex64 @@ -192,4 +195,24 @@ typedef struct namespace iosu { void iosuAcp_init(); + + namespace acp + { + enum ACPDeviceType + { + UnknownType = 0, + InternalDeviceType = 1, + USBDeviceType = 3, + }; + + class IOSUModule* GetModule(); + + void CreateSaveMetaFiles(uint32 persistentId, uint64 titleId); + nnResult ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, ACPDeviceType deviceType); + + nnResult ACPCreateSaveDir(uint32 persistentId, ACPDeviceType type); + sint32 ACPCreateSaveDirEx(uint8 accountSlot, uint64 titleId); + nnResult ACPGetOlvAccesskey(uint32be* accessKey); + } + } \ No newline at end of file diff --git a/src/Cafe/IOSU/legacy/iosu_act.cpp b/src/Cafe/IOSU/legacy/iosu_act.cpp index ed3a69bd..42856684 100644 --- a/src/Cafe/IOSU/legacy/iosu_act.cpp +++ b/src/Cafe/IOSU/legacy/iosu_act.cpp @@ -240,6 +240,18 @@ namespace iosu return true; } + bool GetPersistentId(uint8 slot, uint32* persistentId) + { + sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); + if(!_actAccountData[accountIndex].isValid) + { + *persistentId = 0; + return false; + } + *persistentId = _actAccountData[accountIndex].persistentId; + return true; + } + class ActService : public iosu::nn::IPCService { public: diff --git a/src/Cafe/IOSU/legacy/iosu_act.h b/src/Cafe/IOSU/legacy/iosu_act.h index 5336f519..d60966d4 100644 --- a/src/Cafe/IOSU/legacy/iosu_act.h +++ b/src/Cafe/IOSU/legacy/iosu_act.h @@ -49,6 +49,7 @@ namespace iosu bool getMii(uint8 slot, FFLData_t* fflData); bool getScreenname(uint8 slot, uint16 screenname[ACT_NICKNAME_LENGTH]); bool getCountryIndex(uint8 slot, uint32* countryIndex); + bool GetPersistentId(uint8 slot, uint32* persistentId); std::string getAccountId2(uint8 slot); diff --git a/src/Cafe/IOSU/nn/iosu_nn_service.h b/src/Cafe/IOSU/nn/iosu_nn_service.h index d50a0794..d7d4cb01 100644 --- a/src/Cafe/IOSU/nn/iosu_nn_service.h +++ b/src/Cafe/IOSU/nn/iosu_nn_service.h @@ -8,7 +8,7 @@ namespace iosu { namespace nn { - // a simple service interface which wraps handle management and Ioctlv/IoctlvAsync + // a simple service interface which wraps handle management and Ioctlv/IoctlvAsync (used by /dev/fpd and others are still to be determined) class IPCSimpleService { public: @@ -88,7 +88,7 @@ namespace iosu uint32be nnResultCode; }; - // a complex service interface which wraps Ioctlv and adds an additional service channel, used by /dev/act, ? + // a complex service interface which wraps Ioctlv and adds an additional service channel, used by /dev/act, /dev/acp_main, ? class IPCService { public: diff --git a/src/Cafe/OS/libs/nn_acp/nn_acp.cpp b/src/Cafe/OS/libs/nn_acp/nn_acp.cpp index 516087a3..61640ae7 100644 --- a/src/Cafe/OS/libs/nn_acp/nn_acp.cpp +++ b/src/Cafe/OS/libs/nn_acp/nn_acp.cpp @@ -17,6 +17,8 @@ #include "Common/FileStream.h" #include "Cafe/CafeSystem.h" +using ACPDeviceType = iosu::acp::ACPDeviceType; + #define acpPrepareRequest() \ StackAllocator<iosuAcpCemuRequest_t> _buf_acpRequest; \ StackAllocator<ioBufferVector_t> _buf_bufferVector; \ @@ -30,12 +32,14 @@ namespace nn { namespace acp { - ACPStatus _ACPConvertResultToACPStatus(uint32* nnResult, const char* functionName, uint32 someConstant) + ACPStatus ACPConvertResultToACPStatus(uint32* nnResult, const char* functionName, uint32 lineNumber) { // todo return ACPStatus::SUCCESS; } + #define _ACPConvertResultToACPStatus(nnResult) ACPConvertResultToACPStatus(nnResult, __func__, __LINE__) + ACPStatus ACPGetApplicationBox(uint32be* applicationBox, uint64 titleId) { // todo @@ -43,6 +47,12 @@ namespace acp return ACPStatus::SUCCESS; } + ACPStatus ACPGetOlvAccesskey(uint32be* accessKey) + { + nnResult r = iosu::acp::ACPGetOlvAccesskey(accessKey); + return _ACPConvertResultToACPStatus(&r); + } + bool sSaveDirMounted{false}; ACPStatus ACPMountSaveDir() @@ -56,7 +66,7 @@ namespace acp const auto mlc = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/user/", high, low); FSCDeviceHostFS_Mount("/vol/save/", _pathToUtf8(mlc), FSC_PRIORITY_BASE); nnResult mountResult = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_ACP, 0); - return _ACPConvertResultToACPStatus(&mountResult, "ACPMountSaveDir", 0x60); + return _ACPConvertResultToACPStatus(&mountResult); } ACPStatus ACPUnmountSaveDir() @@ -66,201 +76,24 @@ namespace acp return ACPStatus::SUCCESS; } - uint64 _acpGetTimestamp() - { - return coreinit::coreinit_getOSTime() / ESPRESSO_TIMER_CLOCK; - } - - nnResult __ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, ACPDeviceType deviceType) - { - if (deviceType == UnknownType) - { - return (nnResult)0xA030FB80; - } - - // create or modify the saveinfo - const auto saveinfoPath = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/saveinfo.xml", GetTitleIdHigh(titleId), GetTitleIdLow(titleId)); - auto saveinfoData = FileStream::LoadIntoMemory(saveinfoPath); - if (saveinfoData && !saveinfoData->empty()) - { - namespace xml = tinyxml2; - xml::XMLDocument doc; - tinyxml2::XMLError xmlError = doc.Parse((const char*)saveinfoData->data(), saveinfoData->size()); - if (xmlError == xml::XML_SUCCESS || xmlError == xml::XML_ERROR_EMPTY_DOCUMENT) - { - xml::XMLNode* child = doc.FirstChild(); - // check for declaration -> <?xml version="1.0" encoding="utf-8"?> - if (!child || !child->ToDeclaration()) - { - xml::XMLDeclaration* decl = doc.NewDeclaration(); - doc.InsertFirstChild(decl); - } - - xml::XMLElement* info = doc.FirstChildElement("info"); - if (!info) - { - info = doc.NewElement("info"); - doc.InsertEndChild(info); - } - - // find node with persistentId - char tmp[64]; - sprintf(tmp, "%08x", persistentId); - bool foundNode = false; - for (xml::XMLElement* account = info->FirstChildElement("account"); account; account = account->NextSiblingElement("account")) - { - if (account->Attribute("persistentId", tmp)) - { - // found the entry! -> update timestamp - xml::XMLElement* timestamp = account->FirstChildElement("timestamp"); - sprintf(tmp, "%" PRIx64, _acpGetTimestamp()); - if (timestamp) - timestamp->SetText(tmp); - else - { - timestamp = doc.NewElement("timestamp"); - account->InsertFirstChild(timestamp); - } - - foundNode = true; - break; - } - } - - if (!foundNode) - { - tinyxml2::XMLElement* account = doc.NewElement("account"); - { - sprintf(tmp, "%08x", persistentId); - account->SetAttribute("persistentId", tmp); - - tinyxml2::XMLElement* timestamp = doc.NewElement("timestamp"); - { - sprintf(tmp, "%" PRIx64, _acpGetTimestamp()); - timestamp->SetText(tmp); - } - - account->InsertFirstChild(timestamp); - } - - info->InsertFirstChild(account); - } - - // update file - tinyxml2::XMLPrinter printer; - doc.Print(&printer); - FileStream* fs = FileStream::createFile2(saveinfoPath); - if (fs) - { - fs->writeString(printer.CStr()); - delete fs; - } - } - } - return NN_RESULT_SUCCESS; - } - ACPStatus ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, ACPDeviceType deviceType) { - nnResult r = __ACPUpdateSaveTimeStamp(persistentId, titleId, deviceType); + nnResult r = iosu::acp::ACPUpdateSaveTimeStamp(persistentId, titleId, deviceType); return ACPStatus::SUCCESS; } - void CreateSaveMetaFiles(uint32 persistentId, uint64 titleId) - { - std::string titlePath = CafeSystem::GetMlcStoragePath(CafeSystem::GetForegroundTitleId()); - - sint32 fscStatus; - FSCVirtualFile* fscFile = fsc_open((titlePath + "/meta/meta.xml").c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); - if (fscFile) - { - sint32 fileSize = fsc_getFileSize(fscFile); - - std::unique_ptr<uint8[]> fileContent = std::make_unique<uint8[]>(fileSize); - fsc_readFile(fscFile, fileContent.get(), fileSize); - fsc_close(fscFile); - - const auto outPath = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/meta.xml", GetTitleIdHigh(titleId), GetTitleIdLow(titleId)); - - std::ofstream myFile(outPath, std::ios::out | std::ios::binary); - myFile.write((char*)fileContent.get(), fileSize); - myFile.close(); - } - - fscFile = fsc_open((titlePath + "/meta/iconTex.tga").c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); - if (fscFile) - { - sint32 fileSize = fsc_getFileSize(fscFile); - - std::unique_ptr<uint8[]> fileContent = std::make_unique<uint8[]>(fileSize); - fsc_readFile(fscFile, fileContent.get(), fileSize); - fsc_close(fscFile); - - const auto outPath = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/meta/iconTex.tga", GetTitleIdHigh(titleId), GetTitleIdLow(titleId)); - - std::ofstream myFile(outPath, std::ios::out | std::ios::binary); - myFile.write((char*)fileContent.get(), fileSize); - myFile.close(); - } - - ACPUpdateSaveTimeStamp(persistentId, titleId, InternalDeviceType); - } - - nnResult CreateSaveDir(uint32 persistentId, ACPDeviceType type) - { - uint64 titleId = CafeSystem::GetForegroundTitleId(); - uint32 high = GetTitleIdHigh(titleId) & (~0xC); - uint32 low = GetTitleIdLow(titleId); - - sint32 fscStatus = FSC_STATUS_FILE_NOT_FOUND; - char path[256]; - - sprintf(path, "%susr/save/%08x/", "/vol/storage_mlc01/", high); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/save/%08x/%08x/", "/vol/storage_mlc01/", high, low); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/save/%08x/%08x/meta/", "/vol/storage_mlc01/", high, low); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/save/%08x/%08x/user/", "/vol/storage_mlc01/", high, low); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/save/%08x/%08x/user/common", "/vol/storage_mlc01/", high, low); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/save/%08x/%08x/user/%08x", "/vol/storage_mlc01/", high, low, persistentId == 0 ? 0x80000001 : persistentId); - fsc_createDir(path, &fscStatus); - - // not sure about this - sprintf(path, "%susr/boss/", "/vol/storage_mlc01/"); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/boss/%08x/", "/vol/storage_mlc01/", high); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/boss/%08x/%08x/", "/vol/storage_mlc01/", high, low); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/boss/%08x/%08x/user/", "/vol/storage_mlc01/", high, low); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/boss/%08x/%08x/user/common", "/vol/storage_mlc01/", high, low); - fsc_createDir(path, &fscStatus); - sprintf(path, "%susr/boss/%08x/%08x/user/%08x/", "/vol/storage_mlc01/", high, low, persistentId == 0 ? 0x80000001 : persistentId); - fsc_createDir(path, &fscStatus); - - // copy xml meta files - CreateSaveMetaFiles(persistentId, titleId); - - nnResult result = BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_ACP, 0); - return result; - } - - ACPStatus ACPCreateSaveDir(uint32 persistentId, ACPDeviceType type) - { - nnResult result = CreateSaveDir(persistentId, type); - return _ACPConvertResultToACPStatus(&result, "ACPCreateSaveDir", 0x2FA); - } - ACPStatus ACPCheckApplicationDeviceEmulation(uint32be* isEmulated) { *isEmulated = 0; return ACPStatus::SUCCESS; } + ACPStatus ACPCreateSaveDir(uint32 persistentId, ACPDeviceType type) + { + nnResult result = iosu::acp::ACPCreateSaveDir(persistentId, type); + return _ACPConvertResultToACPStatus(&result); + } + nnResult ACPCreateSaveDirEx(uint8 accountSlot, uint64 titleId) { acpPrepareRequest(); @@ -279,7 +112,7 @@ namespace acp ppcDefineParamU8(accountSlot, 0); ppcDefineParamU64(titleId, 2); // index 2 because of alignment -> guessed parameters nnResult result = ACPCreateSaveDirEx(accountSlot, titleId); - osLib_returnFromFunction(hCPU, _ACPConvertResultToACPStatus(&result, "ACPCreateSaveDirEx", 0x300)); + osLib_returnFromFunction(hCPU, _ACPConvertResultToACPStatus(&result)); } void export_ACPGetSaveDataTitleIdList(PPCInterpreter_t* hCPU) @@ -511,6 +344,8 @@ namespace acp cafeExportRegister("nn_acp", ACPGetApplicationBox, LogType::Placeholder); + cafeExportRegister("nn_acp", ACPGetOlvAccesskey, LogType::Placeholder); + osLib_addFunction("nn_acp", "ACPIsOverAgeEx", export_ACPIsOverAgeEx); osLib_addFunction("nn_acp", "ACPGetNetworkTime", export_ACPGetNetworkTime); diff --git a/src/Cafe/OS/libs/nn_acp/nn_acp.h b/src/Cafe/OS/libs/nn_acp/nn_acp.h index cbf36c64..9890a6df 100644 --- a/src/Cafe/OS/libs/nn_acp/nn_acp.h +++ b/src/Cafe/OS/libs/nn_acp/nn_acp.h @@ -1,4 +1,5 @@ #pragma once +#include "Cafe/IOSU/legacy/iosu_acp.h" namespace nn { @@ -9,20 +10,13 @@ namespace acp SUCCESS = 0, }; - enum ACPDeviceType - { - UnknownType = 0, - InternalDeviceType = 1, - USBDeviceType = 3, - }; - - void CreateSaveMetaFiles(uint32 persistentId, uint64 titleId); + using ACPDeviceType = iosu::acp::ACPDeviceType; ACPStatus ACPGetApplicationBox(uint32be* applicationBox, uint64 titleId); ACPStatus ACPMountSaveDir(); ACPStatus ACPUnmountSaveDir(); - ACPStatus ACPCreateSaveDir(uint32 persistentId, ACPDeviceType type); - ACPStatus ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, ACPDeviceType deviceType);; + ACPStatus ACPCreateSaveDir(uint32 persistentId, iosu::acp::ACPDeviceType type); + ACPStatus ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, iosu::acp::ACPDeviceType deviceType); void load(); } diff --git a/src/Cafe/OS/libs/nn_save/nn_save.cpp b/src/Cafe/OS/libs/nn_save/nn_save.cpp index 78de8291..05e49438 100644 --- a/src/Cafe/OS/libs/nn_save/nn_save.cpp +++ b/src/Cafe/OS/libs/nn_save/nn_save.cpp @@ -72,11 +72,11 @@ namespace save return result != 0; } - bool GetCurrentTitleApplicationBox(acp::ACPDeviceType* deviceType) + bool GetCurrentTitleApplicationBox(nn::acp::ACPDeviceType* deviceType) { if (deviceType) { - *deviceType = acp::InternalDeviceType; + *deviceType = nn::acp::ACPDeviceType::InternalDeviceType; return true; } return false; @@ -84,7 +84,7 @@ namespace save void UpdateSaveTimeStamp(uint32 persistentId) { - acp::ACPDeviceType deviceType; + nn::acp::ACPDeviceType deviceType; if (GetCurrentTitleApplicationBox(&deviceType)) ACPUpdateSaveTimeStamp(persistentId, CafeSystem::GetForegroundTitleId(), deviceType); } @@ -314,7 +314,7 @@ namespace save sprintf(path, "%susr/save/%08x/%08x/meta/", "/vol/storage_mlc01/", high, low); fsc_createDir(path, &fscStatus); - acp::CreateSaveMetaFiles(ActiveSettings::GetPersistentId(), titleId); + iosu::acp::CreateSaveMetaFiles(ActiveSettings::GetPersistentId(), titleId); } return SAVE_STATUS_OK; @@ -669,7 +669,7 @@ namespace save uint32 persistentId; if (GetPersistentIdEx(accountSlot, &persistentId)) { - acp::ACPStatus status = ACPCreateSaveDir(persistentId, acp::InternalDeviceType); + acp::ACPStatus status = nn::acp::ACPCreateSaveDir(persistentId, iosu::acp::ACPDeviceType::InternalDeviceType); result = ConvertACPToSaveStatus(status); } else From d45c2fa6d1ccd008a2ef22d814530894abd691d1 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 10 Apr 2024 20:23:15 +0200 Subject: [PATCH 072/299] erreula: Avoid triggering debug assert in imgui It does not like empty window titles --- src/Cafe/OS/libs/erreula/erreula.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Cafe/OS/libs/erreula/erreula.cpp b/src/Cafe/OS/libs/erreula/erreula.cpp index c95816b6..a7f2f35c 100644 --- a/src/Cafe/OS/libs/erreula/erreula.cpp +++ b/src/Cafe/OS/libs/erreula/erreula.cpp @@ -277,10 +277,11 @@ namespace erreula ImGui::SetNextWindowBgAlpha(0.9f); ImGui::PushFont(font); - std::string title = "ErrEula"; + std::string title; if (appearArg.title) title = boost::nowide::narrow(GetText(appearArg.title.GetPtr())); - + if(title.empty()) // ImGui doesn't allow empty titles, so set one if appearArg.title is not set or empty + title = "ErrEula"; if (ImGui::Begin(title.c_str(), nullptr, kPopupFlags)) { const float startx = ImGui::GetWindowSize().x / 2.0f; From bac1ac3b499d84d502fdcec32e2fa5e8b176975c Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Thu, 11 Apr 2024 03:06:36 +0200 Subject: [PATCH 073/299] CI: use last vcpkg compatible CMake 3.29.0 (#1167) --- .github/workflows/build.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f3b834b4..58a8508d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -55,6 +55,11 @@ jobs: sudo apt update -qq sudo apt install -y clang-15 cmake freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libudev-dev nasm ninja-build + - name: "Setup cmake" + uses: jwlawson/actions-setup-cmake@v2 + with: + cmake-version: '3.29.0' + - name: "Bootstrap vcpkg" run: | bash ./dependencies/vcpkg/bootstrap-vcpkg.sh @@ -154,6 +159,11 @@ jobs: echo "[INFO] Experimental version ${{ inputs.experimentalversion }}" echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEXPERIMENTAL_VERSION=${{ inputs.experimentalversion }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append + - name: "Setup cmake" + uses: jwlawson/actions-setup-cmake@v2 + with: + cmake-version: '3.29.0' + - name: "Bootstrap vcpkg" run: | ./dependencies/vcpkg/bootstrap-vcpkg.bat @@ -234,6 +244,11 @@ jobs: brew update brew install llvm@15 ninja nasm molten-vk automake libtool + - name: "Setup cmake" + uses: jwlawson/actions-setup-cmake@v2 + with: + cmake-version: '3.29.0' + - name: "Bootstrap vcpkg" run: | bash ./dependencies/vcpkg/bootstrap-vcpkg.sh From 391533dbe5eec4c1f8d1f00a05629baf2216b423 Mon Sep 17 00:00:00 2001 From: qurious-pixel <62252937+qurious-pixel@users.noreply.github.com> Date: Wed, 10 Apr 2024 21:08:26 -0700 Subject: [PATCH 074/299] Gamelist: Enable icon column by default (#1168) --- src/config/CemuConfig.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index bcaf8467..9f1e7983 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -418,7 +418,7 @@ struct CemuConfig ConfigValue<bool> did_show_graphic_pack_download{false}; ConfigValue<bool> did_show_macos_disclaimer{false}; - ConfigValue<bool> show_icon_column{ false }; + ConfigValue<bool> show_icon_column{ true }; int game_list_style = 0; std::string game_list_column_order; From 84cad8b280afa9db7637ec1fd125d1b59970b548 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Thu, 11 Apr 2024 06:41:57 +0200 Subject: [PATCH 075/299] Vulkan: Remove unecessary present fence (#1166) --- src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp | 5 --- .../Latte/Renderer/Vulkan/SwapchainInfoVk.cpp | 39 ++++--------------- .../Latte/Renderer/Vulkan/SwapchainInfoVk.h | 7 +--- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 9 +---- .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 1 - 5 files changed, 10 insertions(+), 51 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp index 5b9fc349..60124c02 100644 --- a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp +++ b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp @@ -875,11 +875,6 @@ void LatteRenderTarget_getScreenImageArea(sint32* x, sint32* y, sint32* width, s void LatteRenderTarget_copyToBackbuffer(LatteTextureView* textureView, bool isPadView) { - if (g_renderer->GetType() == RendererAPI::Vulkan) - { - ((VulkanRenderer*)g_renderer.get())->PreparePresentationFrame(!isPadView); - } - // make sure texture is updated to latest data in cache LatteTexture_UpdateDataToLatest(textureView->baseTexture); // mark source texture as still in use diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp index b00f5490..75ff02ba 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp @@ -146,13 +146,6 @@ void SwapchainInfoVk::Create() UnrecoverableError("Failed to create semaphore for swapchain acquire"); } - VkFenceCreateInfo fenceInfo = {}; - fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; - fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; - result = vkCreateFence(m_logicalDevice, &fenceInfo, nullptr, &m_imageAvailableFence); - if (result != VK_SUCCESS) - UnrecoverableError("Failed to create fence for swapchain"); - m_acquireIndex = 0; hasDefinedSwapchainImage = false; } @@ -184,12 +177,6 @@ void SwapchainInfoVk::Cleanup() m_swapchainFramebuffers.clear(); - if (m_imageAvailableFence) - { - WaitAvailableFence(); - vkDestroyFence(m_logicalDevice, m_imageAvailableFence, nullptr); - m_imageAvailableFence = nullptr; - } if (m_swapchain) { vkDestroySwapchainKHR(m_logicalDevice, m_swapchain, nullptr); @@ -202,18 +189,6 @@ bool SwapchainInfoVk::IsValid() const return m_swapchain && !m_acquireSemaphores.empty(); } -void SwapchainInfoVk::WaitAvailableFence() -{ - if(m_awaitableFence != VK_NULL_HANDLE) - vkWaitForFences(m_logicalDevice, 1, &m_awaitableFence, VK_TRUE, UINT64_MAX); - m_awaitableFence = VK_NULL_HANDLE; -} - -void SwapchainInfoVk::ResetAvailableFence() const -{ - vkResetFences(m_logicalDevice, 1, &m_imageAvailableFence); -} - VkSemaphore SwapchainInfoVk::ConsumeAcquireSemaphore() { VkSemaphore ret = m_currentSemaphore; @@ -221,15 +196,18 @@ VkSemaphore SwapchainInfoVk::ConsumeAcquireSemaphore() return ret; } -bool SwapchainInfoVk::AcquireImage(uint64 timeout) +bool SwapchainInfoVk::AcquireImage() { - WaitAvailableFence(); - ResetAvailableFence(); - VkSemaphore acquireSemaphore = m_acquireSemaphores[m_acquireIndex]; - VkResult result = vkAcquireNextImageKHR(m_logicalDevice, m_swapchain, timeout, acquireSemaphore, m_imageAvailableFence, &swapchainImageIndex); + VkResult result = vkAcquireNextImageKHR(m_logicalDevice, m_swapchain, 1'000'000'000, acquireSemaphore, nullptr, &swapchainImageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) m_shouldRecreate = true; + if (result == VK_TIMEOUT) + { + swapchainImageIndex = -1; + return false; + } + if (result < 0) { swapchainImageIndex = -1; @@ -238,7 +216,6 @@ bool SwapchainInfoVk::AcquireImage(uint64 timeout) return false; } m_currentSemaphore = acquireSemaphore; - m_awaitableFence = m_imageAvailableFence; m_acquireIndex = (m_acquireIndex + 1) % m_swapchainImages.size(); return true; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.h b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.h index 0e8c2ade..ceffab41 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.h @@ -26,10 +26,7 @@ struct SwapchainInfoVk bool IsValid() const; - void WaitAvailableFence(); - void ResetAvailableFence() const; - - bool AcquireImage(uint64 timeout); + bool AcquireImage(); // retrieve semaphore of last acquire for submitting a wait operation // only one wait operation must be submitted per acquire (which submits a single signal operation) // therefore subsequent calls will return a NULL handle @@ -84,9 +81,7 @@ struct SwapchainInfoVk private: uint32 m_acquireIndex = 0; std::vector<VkSemaphore> m_acquireSemaphores; // indexed by m_acquireIndex - VkFence m_imageAvailableFence{}; VkSemaphore m_currentSemaphore = VK_NULL_HANDLE; - VkFence m_awaitableFence = VK_NULL_HANDLE; std::array<uint32, 2> m_swapchainQueueFamilyIndices; VkExtent2D m_actualExtent{}; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index e0ebda2a..9209e3cd 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -1824,11 +1824,6 @@ void VulkanRenderer::DrawEmptyFrame(bool mainWindow) SwapBuffers(mainWindow, !mainWindow); } -void VulkanRenderer::PreparePresentationFrame(bool mainWindow) -{ - AcquireNextSwapchainImage(mainWindow); -} - void VulkanRenderer::InitFirstCommandBuffer() { cemu_assert_debug(m_state.currentCommandBuffer == nullptr); @@ -2599,7 +2594,7 @@ bool VulkanRenderer::AcquireNextSwapchainImage(bool mainWindow) if (!UpdateSwapchainProperties(mainWindow)) return false; - bool result = chainInfo.AcquireImage(UINT64_MAX); + bool result = chainInfo.AcquireImage(); if (!result) return false; @@ -2612,8 +2607,6 @@ void VulkanRenderer::RecreateSwapchain(bool mainWindow, bool skipCreate) SubmitCommandBuffer(); WaitDeviceIdle(); auto& chainInfo = GetChainInfo(mainWindow); - // make sure fence has no signal operation submitted - chainInfo.WaitAvailableFence(); Vector2i size; if (mainWindow) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index 2491d052..6df53da4 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -228,7 +228,6 @@ public: uint64 GenUniqueId(); // return unique id (uses incrementing counter) void DrawEmptyFrame(bool mainWindow) override; - void PreparePresentationFrame(bool mainWindow); void InitFirstCommandBuffer(); void ProcessFinishedCommandBuffers(); From d5a8530246a6411c318c8df1dadb2d3e6fd658f7 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 13 Apr 2024 10:38:10 +0200 Subject: [PATCH 076/299] nlibcurl: Detect invalid header combo + refactoring Fixes error 106-0526 when opening course world on Super Mario Maker Manually attaching Content-Length header for POST requests is undefined behavior on recent libcurl. To detect the bad case some refactoring was necessary. In general we should try to move away from directly forwarding curl_easy_setopt() to the underlying instance as the behavior is diverging in modern libcurl. Much more refactoring work is required in the future to fix all of this. --- src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp | 404 +++++++++++++++++-------- 1 file changed, 280 insertions(+), 124 deletions(-) diff --git a/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp b/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp index 53981a5a..318e658e 100644 --- a/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp +++ b/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp @@ -1,4 +1,5 @@ #include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/common/OSUtil.h" #include "Cafe/HW/Espresso/PPCCallback.h" #include "nlibcurl.h" @@ -6,6 +7,8 @@ #include "openssl/x509.h" #include "openssl/ssl.h" +#define CURL_STRICTER + #include "curl/curl.h" #include <unordered_map> #include <atomic> @@ -98,6 +101,17 @@ struct MEMPTRHash_t } }; +struct WU_curl_slist +{ + MEMPTR<char> data; + MEMPTR<WU_curl_slist> next; +}; + +enum class WU_CURLcode +{ + placeholder = 0, +}; + struct { sint32 initialized; @@ -110,8 +124,53 @@ struct MEMPTR<curl_calloc_callback> calloc; } g_nlibcurl = {}; +using WU_CURL_off_t = uint64be; -#pragma pack(1) +enum class WU_HTTPREQ : uint32 +{ + HTTPREQ_GET = 0x1, + HTTPREQ_POST = 0x2, + UKN_3 = 0x3, +}; + +struct WU_UserDefined +{ + // starting at 0xD8 (probably) in CURL_t + /* 0x0D8 / +0x00 */ uint32be ukn0D8; + /* 0x0DC / +0x04 */ uint32be ukn0DC; + /* 0x0E0 / +0x08 */ MEMPTR<WU_curl_slist> headers; + /* 0x0E4 / +0x0C */ uint32be ukn0E4; + /* 0x0E8 / +0x10 */ uint32be ukn0E8; + /* 0x0EC / +0x14 */ uint32be ukn0EC; + /* 0x0F0 / +0x18 */ uint32be ukn0F0[4]; + /* 0x100 / +0x28 */ uint32be ukn100[4]; + /* 0x110 / +0x38 */ uint32be ukn110[4]; // +0x40 -> WU_CURL_off_t postfieldsize ? + /* 0x120 / +0x48 */ uint32be ukn120[4]; + /* 0x130 / +0x58 */ uint32be ukn130[4]; + /* 0x140 / +0x68 */ uint32be ukn140[4]; + /* 0x150 / +0x78 */ uint32be ukn150[4]; + /* 0x160 / +0x88 */ uint32be ukn160[4]; + /* 0x170 / +0x98 */ uint32be ukn170[4]; + /* 0x180 / +0xA8 */ uint32be ukn180[4]; + /* 0x190 / +0xB0 */ sint64be infilesize_190{0}; + /* 0x198 / +0xB8 */ uint32be ukn198; + /* 0x19C / +0xBC */ uint32be ukn19C; + /* 0x1A0 / +0xC8 */ uint32be ukn1A0[4]; + /* 0x1B0 / +0xD8 */ uint32be ukn1B0[4]; + /* 0x1C0 / +0xE8 */ uint32be ukn1C0[4]; + /* 0x1D0 / +0xF8 */ uint32be ukn1D0[4]; + /* 0x1E0 / +0x108 */ uint32be ukn1E0; + /* 0x1E4 / +0x108 */ uint32be ukn1E4; + /* 0x1E8 / +0x108 */ uint32be ukn1E8; + /* 0x1EC / +0x108 */ betype<WU_HTTPREQ> httpreq_1EC; + /* 0x1F0 / +0x118 */ uint32be ukn1F0[4]; + + void SetToDefault() + { + memset(this, 0, sizeof(WU_UserDefined)); + httpreq_1EC = WU_HTTPREQ::HTTPREQ_GET; + } +}; struct CURL_t { @@ -137,6 +196,7 @@ struct CURL_t OSThread_t* curlThread; MEMPTR<char> info_redirectUrl; // stores CURLINFO_REDIRECT_URL ptr MEMPTR<char> info_contentType; // stores CURLINFO_CONTENT_TYPE ptr + bool isDirty{true}; // debug struct @@ -149,10 +209,44 @@ struct CURL_t FileStream* file_responseRaw{}; }debug; + // fields below match the actual memory layout, above still needs refactoring + /* 0x78 */ uint32be ukn078; + /* 0x7C */ uint32be ukn07C; + /* 0x80 */ uint32be ukn080; + /* 0x84 */ uint32be ukn084; + /* 0x88 */ uint32be ukn088; + /* 0x8C */ uint32be ukn08C; + /* 0x90 */ uint32be ukn090[4]; + /* 0xA0 */ uint32be ukn0A0[4]; + /* 0xB0 */ uint32be ukn0B0[4]; + /* 0xC0 */ uint32be ukn0C0[4]; + /* 0xD0 */ uint32be ukn0D0; + /* 0xD4 */ uint32be ukn0D4; + /* 0xD8 */ WU_UserDefined set; + /* 0x200 */ uint32be ukn200[4]; + /* 0x210 */ uint32be ukn210[4]; + /* 0x220 */ uint32be ukn220[4]; + /* 0x230 */ uint32be ukn230[4]; + /* 0x240 */ uint32be ukn240[4]; + /* 0x250 */ uint32be ukn250[4]; + /* 0x260 */ uint32be ukn260[4]; + /* 0x270 */ uint32be ukn270[4]; + /* 0x280 */ uint8be ukn280; + /* 0x281 */ uint8be opt_no_body_281; + /* 0x282 */ uint8be ukn282; + /* 0x283 */ uint8be upload_283; }; static_assert(sizeof(CURL_t) <= 0x8698); +static_assert(offsetof(CURL_t, ukn078) == 0x78); +static_assert(offsetof(CURL_t, set) == 0xD8); +static_assert(offsetof(CURL_t, set) + offsetof(WU_UserDefined, headers) == 0xE0); +static_assert(offsetof(CURL_t, set) + offsetof(WU_UserDefined, infilesize_190) == 0x190); +static_assert(offsetof(CURL_t, set) + offsetof(WU_UserDefined, httpreq_1EC) == 0x1EC); +static_assert(offsetof(CURL_t, opt_no_body_281) == 0x281); typedef MEMPTR<CURL_t> CURLPtr; +#pragma pack(1) // may affect structs below, we can probably remove this but lets keep it for now as the code below is fragile + typedef struct { //uint32be specifier; // 0x00 @@ -173,18 +267,12 @@ typedef MEMPTR<CURLSH_t> CURLSHPtr; typedef struct { CURLM* curlm; - std::vector< MEMPTR<CURL> > curl; + std::vector<MEMPTR<CURL_t>> curl; }CURLM_t; static_assert(sizeof(CURLM_t) <= 0x80, "sizeof(CURLM_t)"); typedef MEMPTR<CURLM_t> CURLMPtr; -struct curl_slist_t -{ - MEMPTR<char> data; - MEMPTR<curl_slist_t> next; -}; - -static_assert(sizeof(curl_slist_t) <= 0x8, "sizeof(curl_slist_t)"); +static_assert(sizeof(WU_curl_slist) <= 0x8, "sizeof(curl_slist_t)"); struct CURLMsg_t { @@ -298,6 +386,89 @@ uint32 SendOrderToWorker(CURL_t* curl, QueueOrder order, uint32 arg1 = 0) return result; } +int curl_closesocket(void *clientp, curl_socket_t item); + +void _curl_set_default_parameters(CURL_t* curl) +{ + curl->set.SetToDefault(); + + // default parameters + curl_easy_setopt(curl->curl, CURLOPT_HEADERFUNCTION, header_callback); + curl_easy_setopt(curl->curl, CURLOPT_HEADERDATA, curl); + + curl_easy_setopt(curl->curl, CURLOPT_CLOSESOCKETFUNCTION, curl_closesocket); + curl_easy_setopt(curl->curl, CURLOPT_CLOSESOCKETDATA, nullptr); +} + +void _curl_sync_parameters(CURL_t* curl) +{ + // sync ppc curl to actual curl state + // not all parameters are covered yet, many are still set directly in easy_setopt + bool isPost = curl->set.httpreq_1EC == WU_HTTPREQ::HTTPREQ_POST; + // http request type + if(curl->set.httpreq_1EC == WU_HTTPREQ::HTTPREQ_GET) + { + ::curl_easy_setopt(curl->curl, CURLOPT_HTTPGET, 1); + cemu_assert_debug(curl->opt_no_body_281 == 0); + cemu_assert_debug(curl->upload_283 == 0); + } + else if(curl->set.httpreq_1EC == WU_HTTPREQ::HTTPREQ_POST) + { + ::curl_easy_setopt(curl->curl, CURLOPT_POST, 1); + cemu_assert_debug(curl->upload_283 == 0); + ::curl_easy_setopt(curl->curl, CURLOPT_NOBODY, curl->opt_no_body_281 ? 1 : 0); + } + else + { + cemu_assert_unimplemented(); + } + + // CURLOPT_HTTPHEADER + std::optional<uint64> manualHeaderContentLength; + if (curl->set.headers) + { + struct curl_slist* list = nullptr; + WU_curl_slist* ppcList = curl->set.headers; + while(ppcList) + { + if(isPost) + { + // for recent libcurl manually adding Content-Length header is undefined behavior. Instead CURLOPT_INFILESIZE(_LARGE) should be set + // here we remove Content-Length and instead substitute it with CURLOPT_INFILESIZE (NEX DataStore in Super Mario Maker requires this) + if(strncmp(ppcList->data.GetPtr(), "Content-Length:", 15) == 0) + { + manualHeaderContentLength = std::stoull(ppcList->data.GetPtr() + 15); + ppcList = ppcList->next; + continue; + } + } + + cemuLog_logDebug(LogType::Force, "curl_slist_append: {}", ppcList->data.GetPtr()); + curlDebug_logEasySetOptStr(curl, "CURLOPT_HTTPHEADER", (const char*)ppcList->data.GetPtr()); + list = ::curl_slist_append(list, ppcList->data.GetPtr()); + ppcList = ppcList->next; + } + ::curl_easy_setopt(curl->curl, CURLOPT_HTTPHEADER, list); + // todo - prevent leaking of list (maybe store in host curl object, similar to how our zlib implementation does stuff) + } + else + ::curl_easy_setopt(curl->curl, CURLOPT_HTTPHEADER, nullptr); + + // infile size (post data size) + if (curl->set.infilesize_190) + { + cemu_assert_debug(manualHeaderContentLength == 0); // should not have both? + ::curl_easy_setopt(curl->curl, CURLOPT_INFILESIZE_LARGE, curl->set.infilesize_190); + } + else + { + if(isPost && manualHeaderContentLength > 0) + ::curl_easy_setopt(curl->curl, CURLOPT_INFILESIZE_LARGE, manualHeaderContentLength); + else + ::curl_easy_setopt(curl->curl, CURLOPT_INFILESIZE_LARGE, 0); + } +} + void export_malloc(PPCInterpreter_t* hCPU) { ppcDefineParamU32(size, 0); @@ -340,7 +511,6 @@ void export_realloc(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, result.GetMPTR()); } - CURLcode curl_global_init(uint32 flags) { if (g_nlibcurl.initialized++) @@ -436,6 +606,18 @@ void export_curl_multi_perform(PPCInterpreter_t* hCPU) //cemuLog_logDebug(LogType::Force, "curl_multi_perform(0x{:08x}, 0x{:08x})", curlm.GetMPTR(), runningHandles.GetMPTR()); + //curl_multi_get_handles(curlm->curlm); + + for(auto _curl : curlm->curl) + { + CURL_t* curl = (CURL_t*)_curl.GetPtr(); + if(curl->isDirty) + { + curl->isDirty = false; + _curl_sync_parameters(curl); + } + } + //g_callerQueue = curlm->callerQueue; //g_threadQueue = curlm->threadQueue; int tempRunningHandles = 0; @@ -555,7 +737,7 @@ void export_curl_multi_info_read(PPCInterpreter_t* hCPU) if (msg->easy_handle) { const auto it = find_if(curlm->curl.cbegin(), curlm->curl.cend(), - [msg](const MEMPTR<void>& curl) + [msg](const MEMPTR<CURL_t>& curl) { const MEMPTR<CURL_t> _curl{ curl }; return _curl->curl = msg->easy_handle; @@ -661,26 +843,6 @@ void export_curl_share_cleanup(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, 0); } -int my_trace(CURL *handle, curl_infotype type, char *ptr, size_t size, - void *userp) -{ - FILE* f = (FILE*)userp; - - //if (type == CURLINFO_TEXT) - { - char tmp[1024] = {}; - sprintf(tmp, "0x%p: ", handle); - fwrite(tmp, 1, strlen(tmp), f); - - memcpy(tmp, ptr, std::min(size, (size_t)990)); - fwrite(tmp, 1, std::min(size + 1, (size_t)991), f); - - fflush(f); - - } - return 0; -} - static int curl_closesocket(void *clientp, curl_socket_t item) { nsysnet_notifyCloseSharedSocket((SOCKET)item); @@ -688,36 +850,30 @@ static int curl_closesocket(void *clientp, curl_socket_t item) return 0; } -void export_curl_easy_init(PPCInterpreter_t* hCPU) +CURL_t* curl_easy_init() { if (g_nlibcurl.initialized == 0) { if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) { - osLib_returnFromFunction(hCPU, 0); - return; + return nullptr; } } // Curl_open - CURLPtr result{ PPCCoreCallback(g_nlibcurl.calloc.GetMPTR(), (uint32)1, ppcsizeof<CURL_t>()) }; + MEMPTR<CURL_t> result{ PPCCoreCallback(g_nlibcurl.calloc.GetMPTR(), (uint32)1, ppcsizeof<CURL_t>()) }; cemuLog_logDebug(LogType::Force, "curl_easy_init() -> 0x{:08x}", result.GetMPTR()); if (result) { memset(result.GetPtr(), 0, sizeof(CURL_t)); *result = {}; - result->curl = curl_easy_init(); + result->curl = ::curl_easy_init(); result->curlThread = coreinit::OSGetCurrentThread(); result->info_contentType = nullptr; result->info_redirectUrl = nullptr; - // default parameters - curl_easy_setopt(result->curl, CURLOPT_HEADERFUNCTION, header_callback); - curl_easy_setopt(result->curl, CURLOPT_HEADERDATA, result.GetPtr()); - - curl_easy_setopt(result->curl, CURLOPT_CLOSESOCKETFUNCTION, curl_closesocket); - curl_easy_setopt(result->curl, CURLOPT_CLOSESOCKETDATA, nullptr); + _curl_set_default_parameters(result.GetPtr()); if (g_nlibcurl.proxyConfig) { @@ -725,7 +881,12 @@ void export_curl_easy_init(PPCInterpreter_t* hCPU) } } - osLib_returnFromFunction(hCPU, result.GetMPTR()); + return result; +} + +CURL_t* mw_curl_easy_init() +{ + return curl_easy_init(); } void export_curl_easy_pause(PPCInterpreter_t* hCPU) @@ -971,18 +1132,47 @@ void export_curl_easy_setopt(PPCInterpreter_t* hCPU) ppcDefineParamU64(parameterU64, 2); CURL* curlObj = curl->curl; + curl->isDirty = true; CURLcode result = CURLE_OK; switch (option) { - case CURLOPT_NOSIGNAL: + case CURLOPT_POST: + { + if(parameter) + { + curl->set.httpreq_1EC = WU_HTTPREQ::HTTPREQ_POST; + curl->opt_no_body_281 = 0; + } + else + curl->set.httpreq_1EC = WU_HTTPREQ::HTTPREQ_GET; + break; + } case CURLOPT_HTTPGET: + { + if (parameter) + { + curl->set.httpreq_1EC = WU_HTTPREQ::HTTPREQ_GET; + curl->opt_no_body_281 = 0; + curl->upload_283 = 0; + } + break; + } + case CURLOPT_INFILESIZE: + { + curl->set.infilesize_190 = (sint64)(sint32)(uint32)parameter.GetBEValue(); + break; + } + case CURLOPT_INFILESIZE_LARGE: + { + curl->set.infilesize_190 = (sint64)(uint64)parameterU64; + break; + } + case CURLOPT_NOSIGNAL: case CURLOPT_FOLLOWLOCATION: case CURLOPT_BUFFERSIZE: case CURLOPT_TIMEOUT: case CURLOPT_CONNECTTIMEOUT_MS: - case CURLOPT_POST: - case CURLOPT_INFILESIZE: case CURLOPT_NOPROGRESS: case CURLOPT_LOW_SPEED_LIMIT: case CURLOPT_LOW_SPEED_TIME: @@ -1068,8 +1258,6 @@ void export_curl_easy_setopt(PPCInterpreter_t* hCPU) curlSh->curl = curl; shObj = curlSh->curlsh; } - - result = ::curl_easy_setopt(curlObj, CURLOPT_SHARE, shObj); break; } @@ -1101,17 +1289,8 @@ void export_curl_easy_setopt(PPCInterpreter_t* hCPU) } case CURLOPT_HTTPHEADER: { - struct curl_slist* list = nullptr; - bool isFirst = true; - for (curl_slist_t* ppcList = (curl_slist_t*)parameter.GetPtr(); ppcList; ppcList = ppcList->next.GetPtr()) - { - cemuLog_logDebug(LogType::Force, "curl_slist_append: {}", ppcList->data.GetPtr()); - curlDebug_logEasySetOptStr(curl.GetPtr(), isFirst?"CURLOPT_HTTPHEADER" : "CURLOPT_HTTPHEADER(continue)", (const char*)ppcList->data.GetPtr()); - list = ::curl_slist_append(list, ppcList->data.GetPtr()); - isFirst = false; - } - - result = ::curl_easy_setopt(curlObj, CURLOPT_HTTPHEADER, list); + curl->set.headers = (WU_curl_slist*)parameter.GetPtr(); + result = CURLE_OK; break; } case CURLOPT_SOCKOPTFUNCTION: @@ -1163,15 +1342,18 @@ void export_curl_easy_setopt(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, result); } -void export_curl_easy_perform(PPCInterpreter_t* hCPU) +WU_CURLcode curl_easy_perform(CURL_t* curl) { - ppcDefineParamMEMPTR(curl, CURL_t, 0); - curlDebug_markActiveRequest(curl.GetPtr()); - curlDebug_notifySubmitRequest(curl.GetPtr()); - cemuLog_logDebug(LogType::Force, "curl_easy_perform(0x{:08x})", curl.GetMPTR()); - const uint32 result = SendOrderToWorker(curl.GetPtr(), QueueOrder_Perform); - cemuLog_logDebug(LogType::Force, "curl_easy_perform(0x{:08x}) -> 0x{:x} DONE", curl.GetMPTR(), result); - osLib_returnFromFunction(hCPU, result); + curlDebug_markActiveRequest(curl); + curlDebug_notifySubmitRequest(curl); + + if(curl->isDirty) + { + curl->isDirty = false; + _curl_sync_parameters(curl); + } + const uint32 result = SendOrderToWorker(curl, QueueOrder_Perform); + return static_cast<WU_CURLcode>(result); } void _updateGuestString(CURL_t* curl, MEMPTR<char>& ppcStr, char* hostStr) @@ -1246,14 +1428,6 @@ void export_curl_easy_getinfo(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, result); } - - -void export_curl_global_init(PPCInterpreter_t* hCPU) -{ - ppcDefineParamU32(flags, 0); - osLib_returnFromFunction(hCPU, curl_global_init(flags)); -} - void export_curl_easy_strerror(PPCInterpreter_t* hCPU) { ppcDefineParamU32(code, 0); @@ -1270,21 +1444,16 @@ void export_curl_easy_strerror(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, result.GetMPTR()); } -void export_curl_slist_append(PPCInterpreter_t* hCPU) +WU_curl_slist* curl_slist_append(WU_curl_slist* list, const char* data) { - ppcDefineParamMEMPTR(list, curl_slist_t, 0); - ppcDefineParamMEMPTR(data, const char, 1); - - - MEMPTR<char> dupdata{ PPCCoreCallback(g_nlibcurl.strdup.GetMPTR(), data.GetMPTR()) }; + MEMPTR<char> dupdata{ PPCCoreCallback(g_nlibcurl.strdup.GetMPTR(), data) }; if (!dupdata) { - cemuLog_logDebug(LogType::Force, "curl_slist_append(0x{:08x}, 0x{:08x} [{}]) -> 0x00000000", list.GetMPTR(), data.GetMPTR(), data.GetPtr()); - osLib_returnFromFunction(hCPU, 0); - return; + cemuLog_logDebug(LogType::Force, "curl_slist_append(): Failed to duplicate string"); + return nullptr; } - MEMPTR<curl_slist_t> result{ PPCCoreCallback(g_nlibcurl.malloc.GetMPTR(), ppcsizeof<curl_slist_t>()) }; + MEMPTR<WU_curl_slist> result{ PPCCoreCallback(g_nlibcurl.malloc.GetMPTR(), ppcsizeof<WU_curl_slist>()) }; if (result) { result->data = dupdata; @@ -1293,7 +1462,7 @@ void export_curl_slist_append(PPCInterpreter_t* hCPU) // update last obj of list if (list) { - MEMPTR<curl_slist_t> tmp = list; + MEMPTR<WU_curl_slist> tmp = list; while (tmp->next) { tmp = tmp->next; @@ -1303,38 +1472,24 @@ void export_curl_slist_append(PPCInterpreter_t* hCPU) } } else + { + cemuLog_logDebug(LogType::Force, "curl_slist_append(): Failed to allocate memory"); PPCCoreCallback(g_nlibcurl.free.GetMPTR(), dupdata.GetMPTR()); - - cemuLog_logDebug(LogType::Force, "curl_slist_append(0x{:08x}, 0x{:08x} [{}]) -> 0x{:08x}", list.GetMPTR(), data.GetMPTR(), data.GetPtr(), result.GetMPTR()); + } if(list) - osLib_returnFromFunction(hCPU, list.GetMPTR()); - else - osLib_returnFromFunction(hCPU, result.GetMPTR()); + return list; + return result; } -void export_curl_slist_free_all(PPCInterpreter_t* hCPU) +void curl_slist_free_all(WU_curl_slist* list) { - ppcDefineParamMEMPTR(list, curl_slist_t, 0); - cemuLog_logDebug(LogType::Force, "export_curl_slist_free_all: TODO"); - - osLib_returnFromFunction(hCPU, 0); } -void export_curl_global_init_mem(PPCInterpreter_t* hCPU) +CURLcode curl_global_init_mem(uint32 flags, MEMPTR<curl_malloc_callback> malloc_callback, MEMPTR<curl_free_callback> free_callback, MEMPTR<curl_realloc_callback> realloc_callback, MEMPTR<curl_strdup_callback> strdup_callback, MEMPTR<curl_calloc_callback> calloc_callback) { - ppcDefineParamU32(flags, 0); - ppcDefineParamMEMPTR(m, curl_malloc_callback, 1); - ppcDefineParamMEMPTR(f, curl_free_callback, 2); - ppcDefineParamMEMPTR(r, curl_realloc_callback, 3); - ppcDefineParamMEMPTR(s, curl_strdup_callback, 4); - ppcDefineParamMEMPTR(c, curl_calloc_callback, 5); - - if (!m || !f || !r || !s || !c) - { - osLib_returnFromFunction(hCPU, CURLE_FAILED_INIT); - return; - } + if(!malloc_callback || !free_callback || !realloc_callback || !strdup_callback || !calloc_callback) + return CURLE_FAILED_INIT; CURLcode result = CURLE_OK; if (g_nlibcurl.initialized == 0) @@ -1342,31 +1497,30 @@ void export_curl_global_init_mem(PPCInterpreter_t* hCPU) result = curl_global_init(flags); if (result == CURLE_OK) { - g_nlibcurl.malloc = m; - g_nlibcurl.free = f; - g_nlibcurl.realloc = r; - g_nlibcurl.strdup = s; - g_nlibcurl.calloc = c; + g_nlibcurl.malloc = malloc_callback; + g_nlibcurl.free = free_callback; + g_nlibcurl.realloc = realloc_callback; + g_nlibcurl.strdup = strdup_callback; + g_nlibcurl.calloc = calloc_callback; } } - - cemuLog_logDebug(LogType::Force, "curl_global_init_mem(0x{:x}, 0x{:08x}, 0x{:08x}, 0x{:08x}, 0x{:08x}, 0x{:08x}) -> 0x{:08x}", flags, m.GetMPTR(), f.GetMPTR(), r.GetMPTR(), s.GetMPTR(), c.GetMPTR(), result); - osLib_returnFromFunction(hCPU, result); + return result; } void load() { - osLib_addFunction("nlibcurl", "curl_global_init_mem", export_curl_global_init_mem); - osLib_addFunction("nlibcurl", "curl_global_init", export_curl_global_init); + cafeExportRegister("nlibcurl", curl_global_init_mem, LogType::Force); + cafeExportRegister("nlibcurl", curl_global_init, LogType::Force); - osLib_addFunction("nlibcurl", "curl_slist_append", export_curl_slist_append); - osLib_addFunction("nlibcurl", "curl_slist_free_all", export_curl_slist_free_all); + cafeExportRegister("nlibcurl", curl_slist_append, LogType::Force); + cafeExportRegister("nlibcurl", curl_slist_free_all, LogType::Force); osLib_addFunction("nlibcurl", "curl_easy_strerror", export_curl_easy_strerror); osLib_addFunction("nlibcurl", "curl_share_init", export_curl_share_init); osLib_addFunction("nlibcurl", "curl_share_setopt", export_curl_share_setopt); osLib_addFunction("nlibcurl", "curl_share_cleanup", export_curl_share_cleanup); + cafeExportRegister("nlibcurl", mw_curl_easy_init, LogType::Force); osLib_addFunction("nlibcurl", "curl_multi_init", export_curl_multi_init); osLib_addFunction("nlibcurl", "curl_multi_add_handle", export_curl_multi_add_handle); osLib_addFunction("nlibcurl", "curl_multi_perform", export_curl_multi_perform); @@ -1377,12 +1531,14 @@ void load() osLib_addFunction("nlibcurl", "curl_multi_cleanup", export_curl_multi_cleanup); osLib_addFunction("nlibcurl", "curl_multi_timeout", export_curl_multi_timeout); - osLib_addFunction("nlibcurl", "curl_easy_init", export_curl_easy_init); - osLib_addFunction("nlibcurl", "mw_curl_easy_init", export_curl_easy_init); + cafeExportRegister("nlibcurl", curl_easy_init, LogType::Force); osLib_addFunction("nlibcurl", "curl_easy_reset", export_curl_easy_reset); osLib_addFunction("nlibcurl", "curl_easy_setopt", export_curl_easy_setopt); osLib_addFunction("nlibcurl", "curl_easy_getinfo", export_curl_easy_getinfo); - osLib_addFunction("nlibcurl", "curl_easy_perform", export_curl_easy_perform); + cafeExportRegister("nlibcurl", curl_easy_perform, LogType::Force); + + + osLib_addFunction("nlibcurl", "curl_easy_cleanup", export_curl_easy_cleanup); osLib_addFunction("nlibcurl", "curl_easy_pause", export_curl_easy_pause); } From 9c28a728e4c78594e8f274990c0ffe6a3fabc07f Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 13 Apr 2024 10:43:13 +0200 Subject: [PATCH 077/299] prudp: Dont expect sessionId to match for PING+ACK Fixes friend service connection periodically timing-out on Pretendo. Seems that unlike Nintendo's servers, Pretendo doesn't set sessionId for PING ack packets. --- src/Cemu/nex/prudp.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Cemu/nex/prudp.cpp b/src/Cemu/nex/prudp.cpp index 051e4893..4ef50b11 100644 --- a/src/Cemu/nex/prudp.cpp +++ b/src/Cemu/nex/prudp.cpp @@ -452,9 +452,7 @@ prudpIncomingPacket::prudpIncomingPacket(prudpStreamSettings_t* streamSettings, } else { -#ifdef CEMU_DEBUG_ASSERT - assert_dbg(); -#endif + cemu_assert_suspicious(); } } @@ -696,6 +694,8 @@ void prudpClient::handleIncomingPacket(prudpIncomingPacket* incomingPacket) if (currentConnectionState == STATE_CONNECTING) { lastPingTimestamp = prudpGetMSTimestamp(); + if(serverSessionId != 0) + cemuLog_logDebug(LogType::Force, "PRUDP: ServerSessionId is already set"); serverSessionId = incomingPacket->sessionId; currentConnectionState = STATE_CONNECTED; //printf("Connection established. ClientSession %02x ServerSession %02x\n", clientSessionId, serverSessionId); @@ -763,7 +763,6 @@ bool prudpClient::update() sint32 r = recvfrom(socketUdp, (char*)receiveBuffer, sizeof(receiveBuffer), 0, &receiveFrom, &receiveFromLen); if (r >= 0) { - //printf("RECV 0x%04x byte\n", r); // todo: Verify sender (receiveFrom) // calculate packet size sint32 pIdx = 0; @@ -772,18 +771,25 @@ bool prudpClient::update() sint32 packetLength = prudpPacket::calculateSizeFromPacketData(receiveBuffer + pIdx, r - pIdx); if (packetLength <= 0 || (pIdx + packetLength) > r) { - //printf("Invalid packet length\n"); + cemuLog_logDebug(LogType::Force, "PRUDP: Invalid packet length"); break; } prudpIncomingPacket* incomingPacket = new prudpIncomingPacket(&streamSettings, receiveBuffer + pIdx, packetLength); + pIdx += packetLength; if (incomingPacket->hasError()) { + cemuLog_logDebug(LogType::Force, "PRUDP: Packet error"); delete incomingPacket; break; } - if (incomingPacket->type != prudpPacket::TYPE_CON && incomingPacket->sessionId != serverSessionId) + // sessionId validation is complicated and depends on specific flags and type combinations. It does not seem to cover all packet types + bool validateSessionId = serverSessionId != 0; + if((incomingPacket->type == prudpPacket::TYPE_PING && (incomingPacket->flags&prudpPacket::FLAG_ACK) != 0)) + validateSessionId = false; // PING + ack -> disable session id validation. Pretendo's friend server sends PING ack packets without setting the sessionId (it is 0) + if (validateSessionId && incomingPacket->sessionId != serverSessionId) { + cemuLog_logDebug(LogType::Force, "PRUDP: Invalid session id"); delete incomingPacket; continue; // different session } From 6ea42d958ca349944485e04b67d675954892dde3 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 13 Apr 2024 11:03:02 +0200 Subject: [PATCH 078/299] nlibcurl: Fix compile error --- src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp b/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp index 318e658e..0268c7df 100644 --- a/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp +++ b/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp @@ -386,7 +386,12 @@ uint32 SendOrderToWorker(CURL_t* curl, QueueOrder order, uint32 arg1 = 0) return result; } -int curl_closesocket(void *clientp, curl_socket_t item); +static int curl_closesocket(void *clientp, curl_socket_t item) +{ + nsysnet_notifyCloseSharedSocket((SOCKET)item); + closesocket(item); + return 0; +} void _curl_set_default_parameters(CURL_t* curl) { @@ -843,13 +848,6 @@ void export_curl_share_cleanup(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, 0); } -static int curl_closesocket(void *clientp, curl_socket_t item) -{ - nsysnet_notifyCloseSharedSocket((SOCKET)item); - closesocket(item); - return 0; -} - CURL_t* curl_easy_init() { if (g_nlibcurl.initialized == 0) From 10c78ecccef14b352f362db03f6d91f70e9d3e74 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Mon, 15 Apr 2024 05:20:39 +0200 Subject: [PATCH 079/299] CI: don't strip debug symbols from binary in AppImage (#1175) --- dist/linux/appimage.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/dist/linux/appimage.sh b/dist/linux/appimage.sh index 60a50329..7bfc4701 100755 --- a/dist/linux/appimage.sh +++ b/dist/linux/appimage.sh @@ -33,6 +33,7 @@ chmod +x AppDir/usr/bin/Cemu cp /usr/lib/x86_64-linux-gnu/{libsepol.so.1,libffi.so.7,libpcre.so.3,libGLU.so.1,libthai.so.0} AppDir/usr/lib export UPD_INFO="gh-releases-zsync|cemu-project|Cemu|ci|Cemu.AppImage.zsync" +export NO_STRIP=1 ./linuxdeploy-x86_64.AppImage --appimage-extract-and-run \ --appdir="${GITHUB_WORKSPACE}"/AppDir/ \ -d "${GITHUB_WORKSPACE}"/AppDir/info.cemu.Cemu.desktop \ From ee36992bd6f1e16f93cd847c293a04555139012d Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Tue, 16 Apr 2024 21:38:20 +0200 Subject: [PATCH 080/299] prudp: Improve ping and ack logic Fixes the issue where the friend service connection would always timeout on Pretendo servers The individual changes are: - Outgoing ping packets now use their own incrementing sequenceId (matches official NEX behavior) - If the server sends us a ping packet with NEEDS_ACK, we now respond - Misc smaller refactoring and code clean up - Added PRUDP as a separate logging option --- src/Cemu/Logging/CemuLogging.h | 1 + src/Cemu/nex/nexFriends.cpp | 3 +- src/Cemu/nex/prudp.cpp | 172 +++++++++++++++++++++------------ src/Cemu/nex/prudp.h | 10 +- src/gui/MainWindow.cpp | 1 + 5 files changed, 122 insertions(+), 65 deletions(-) diff --git a/src/Cemu/Logging/CemuLogging.h b/src/Cemu/Logging/CemuLogging.h index e789c2ea..44e89360 100644 --- a/src/Cemu/Logging/CemuLogging.h +++ b/src/Cemu/Logging/CemuLogging.h @@ -42,6 +42,7 @@ enum class LogType : sint32 ProcUi = 39, + PRUDP = 40, }; template <> diff --git a/src/Cemu/nex/nexFriends.cpp b/src/Cemu/nex/nexFriends.cpp index ae87ce44..927418ca 100644 --- a/src/Cemu/nex/nexFriends.cpp +++ b/src/Cemu/nex/nexFriends.cpp @@ -221,7 +221,8 @@ NexFriends::NexFriends(uint32 authServerIp, uint16 authServerPort, const char* a NexFriends::~NexFriends() { - nexCon->destroy(); + if(nexCon) + nexCon->destroy(); } void NexFriends::doAsyncLogin() diff --git a/src/Cemu/nex/prudp.cpp b/src/Cemu/nex/prudp.cpp index 4ef50b11..7c01bec7 100644 --- a/src/Cemu/nex/prudp.cpp +++ b/src/Cemu/nex/prudp.cpp @@ -219,7 +219,7 @@ prudpPacket::prudpPacket(prudpStreamSettings_t* streamSettings, uint8 src, uint8 this->type = type; this->flags = flags; this->sessionId = sessionId; - this->sequenceId = sequenceId; + this->m_sequenceId = sequenceId; this->specifiedPacketSignature = packetSignature; this->streamSettings = streamSettings; this->fragmentIndex = 0; @@ -257,7 +257,7 @@ sint32 prudpPacket::buildData(uint8* output, sint32 maxLength) *(uint16*)(packetBuffer + 0x02) = typeAndFlags; *(uint8*)(packetBuffer + 0x04) = sessionId; *(uint32*)(packetBuffer + 0x05) = packetSignature(); - *(uint16*)(packetBuffer + 0x09) = sequenceId; + *(uint16*)(packetBuffer + 0x09) = m_sequenceId; writeIndex = 0xB; // variable fields if (this->type == TYPE_SYN) @@ -286,7 +286,9 @@ sint32 prudpPacket::buildData(uint8* output, sint32 maxLength) // no data } else - assert_dbg(); + { + cemu_assert_suspicious(); + } // checksum *(uint8*)(packetBuffer + writeIndex) = calculateChecksum(packetBuffer, writeIndex); writeIndex++; @@ -585,7 +587,7 @@ void prudpClient::acknowledgePacket(uint16 sequenceId) auto it = std::begin(list_packetsWithAckReq); while (it != std::end(list_packetsWithAckReq)) { - if (it->packet->sequenceId == sequenceId) + if (it->packet->GetSequenceId() == sequenceId) { delete it->packet; list_packetsWithAckReq.erase(it); @@ -634,16 +636,45 @@ sint32 prudpClient::kerberosEncryptData(uint8* input, sint32 length, uint8* outp void prudpClient::handleIncomingPacket(prudpIncomingPacket* incomingPacket) { + if(incomingPacket->type == prudpPacket::TYPE_PING) + { + if (incomingPacket->flags&prudpPacket::FLAG_ACK) + { + // ack for our ping packet + if(incomingPacket->flags&prudpPacket::FLAG_NEED_ACK) + cemuLog_log(LogType::PRUDP, "[PRUDP] Received unexpected ping packet with both ACK and NEED_ACK set"); + if(m_unacknowledgedPingCount > 0) + { + if(incomingPacket->sequenceId == m_outgoingSequenceId_ping) + { + cemuLog_log(LogType::PRUDP, "[PRUDP] Received ping packet ACK (unacknowledged count: {})", m_unacknowledgedPingCount); + m_unacknowledgedPingCount = 0; + } + else + { + cemuLog_log(LogType::PRUDP, "[PRUDP] Received ping packet ACK with wrong sequenceId (expected: {}, received: {})", m_outgoingSequenceId_ping, incomingPacket->sequenceId); + } + } + else + { + cemuLog_log(LogType::PRUDP, "[PRUDP] Received ping packet ACK which we dont need"); + } + } + else if (incomingPacket->flags&prudpPacket::FLAG_NEED_ACK) + { + // other side is asking for ping ack + cemuLog_log(LogType::PRUDP, "[PRUDP] Received ping packet with NEED_ACK set. Sending ACK back"); + cemu_assert_debug(incomingPacket->packetData.empty()); // todo - echo data? + prudpPacket ackPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_PING, prudpPacket::FLAG_ACK, this->clientSessionId, incomingPacket->sequenceId, 0); + directSendPacket(&ackPacket, dstIp, dstPort); + } + delete incomingPacket; + return; + } + // handle general packet ACK if (incomingPacket->flags&prudpPacket::FLAG_ACK) { - // ack packet acknowledgePacket(incomingPacket->sequenceId); - if ((incomingPacket->type == prudpPacket::TYPE_DATA || incomingPacket->type == prudpPacket::TYPE_PING) && incomingPacket->packetData.empty()) - { - // ack packet - delete incomingPacket; - return; - } } // special cases if (incomingPacket->type == prudpPacket::TYPE_SYN) @@ -680,7 +711,7 @@ void prudpClient::handleIncomingPacket(prudpIncomingPacket* incomingPacket) // set packet specific data (client connection signature) conPacket->setData((uint8*)&this->clientConnectionSignature, sizeof(uint32)); } - // sent packet + // send packet queuePacket(conPacket, dstIp, dstPort); // remember con packet as sent hasSentCon = true; @@ -694,17 +725,28 @@ void prudpClient::handleIncomingPacket(prudpIncomingPacket* incomingPacket) if (currentConnectionState == STATE_CONNECTING) { lastPingTimestamp = prudpGetMSTimestamp(); - if(serverSessionId != 0) - cemuLog_logDebug(LogType::Force, "PRUDP: ServerSessionId is already set"); + cemu_assert_debug(serverSessionId == 0); serverSessionId = incomingPacket->sessionId; currentConnectionState = STATE_CONNECTED; - //printf("Connection established. ClientSession %02x ServerSession %02x\n", clientSessionId, serverSessionId); + cemuLog_log(LogType::PRUDP, "[PRUDP] Connection established. ClientSession {:02x} ServerSession {:02x}", clientSessionId, serverSessionId); } delete incomingPacket; return; } else if (incomingPacket->type == prudpPacket::TYPE_DATA) { + // send ack back if requested + if (incomingPacket->flags&prudpPacket::FLAG_NEED_ACK) + { + prudpPacket ackPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_DATA, prudpPacket::FLAG_ACK, this->clientSessionId, incomingPacket->sequenceId, 0); + directSendPacket(&ackPacket, dstIp, dstPort); + } + // skip data packets without payload + if (incomingPacket->packetData.empty()) + { + delete incomingPacket; + return; + } // verify some values uint16 seqDist = incomingPacket->sequenceId - incomingSequenceId; if (seqDist >= 0xC000) @@ -719,7 +761,7 @@ void prudpClient::handleIncomingPacket(prudpIncomingPacket* incomingPacket) if (it->sequenceId == incomingPacket->sequenceId) { // already queued (should check other values too, like packet type?) - cemuLog_logDebug(LogType::Force, "Duplicate PRUDP packet received"); + cemuLog_log(LogType::PRUDP, "Duplicate PRUDP packet received"); delete incomingPacket; return; } @@ -738,21 +780,12 @@ void prudpClient::handleIncomingPacket(prudpIncomingPacket* incomingPacket) delete incomingPacket; return; } - - if (incomingPacket->flags&prudpPacket::FLAG_NEED_ACK && incomingPacket->type == prudpPacket::TYPE_DATA) - { - // send ack back - prudpPacket* ackPacket = new prudpPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_DATA, prudpPacket::FLAG_ACK, this->clientSessionId, incomingPacket->sequenceId, 0); - queuePacket(ackPacket, dstIp, dstPort); - } } bool prudpClient::update() { if (currentConnectionState == STATE_DISCONNECTED) - { return false; - } uint32 currentTimestamp = prudpGetMSTimestamp(); // check for incoming packets uint8 receiveBuffer[4096]; @@ -771,25 +804,20 @@ bool prudpClient::update() sint32 packetLength = prudpPacket::calculateSizeFromPacketData(receiveBuffer + pIdx, r - pIdx); if (packetLength <= 0 || (pIdx + packetLength) > r) { - cemuLog_logDebug(LogType::Force, "PRUDP: Invalid packet length"); + cemuLog_log(LogType::Force, "[PRUDP] Invalid packet length"); break; } prudpIncomingPacket* incomingPacket = new prudpIncomingPacket(&streamSettings, receiveBuffer + pIdx, packetLength); - pIdx += packetLength; if (incomingPacket->hasError()) { - cemuLog_logDebug(LogType::Force, "PRUDP: Packet error"); + cemuLog_log(LogType::Force, "[PRUDP] Packet error"); delete incomingPacket; break; } - // sessionId validation is complicated and depends on specific flags and type combinations. It does not seem to cover all packet types - bool validateSessionId = serverSessionId != 0; - if((incomingPacket->type == prudpPacket::TYPE_PING && (incomingPacket->flags&prudpPacket::FLAG_ACK) != 0)) - validateSessionId = false; // PING + ack -> disable session id validation. Pretendo's friend server sends PING ack packets without setting the sessionId (it is 0) - if (validateSessionId && incomingPacket->sessionId != serverSessionId) + if (incomingPacket->type != prudpPacket::TYPE_CON && incomingPacket->sessionId != serverSessionId) { - cemuLog_logDebug(LogType::Force, "PRUDP: Invalid session id"); + cemuLog_log(LogType::PRUDP, "[PRUDP] Invalid session id"); delete incomingPacket; continue; // different session } @@ -816,13 +844,44 @@ bool prudpClient::update() } } // check if we need to send another ping - if (currentConnectionState == STATE_CONNECTED && (currentTimestamp - lastPingTimestamp) >= 20000) + if (currentConnectionState == STATE_CONNECTED) { - // send ping - prudpPacket* pingPacket = new prudpPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_PING, prudpPacket::FLAG_NEED_ACK | prudpPacket::FLAG_RELIABLE, this->clientSessionId, this->outgoingSequenceId, serverConnectionSignature); - this->outgoingSequenceId++; // increase since prudpPacket::FLAG_RELIABLE is set (note: official Wii U friends client sends ping packets without FLAG_RELIABLE) - queuePacket(pingPacket, dstIp, dstPort); - lastPingTimestamp = currentTimestamp; + if(m_unacknowledgedPingCount != 0) // counts how many times we sent a ping packet (for the current sequenceId) without receiving an ack + { + // we are waiting for the ack of the previous ping, but it hasn't arrived yet so send another ping packet + if((currentTimestamp - lastPingTimestamp) >= 1500) + { + cemuLog_log(LogType::PRUDP, "[PRUDP] Resending ping packet (no ack received)"); + if(m_unacknowledgedPingCount >= 10) + { + // too many unacknowledged pings, assume the connection is dead + currentConnectionState = STATE_DISCONNECTED; + cemuLog_log(LogType::PRUDP, "PRUDP: Connection did not receive a ping response in a while. Assuming disconnect"); + return false; + } + // resend the ping packet + prudpPacket* pingPacket = new prudpPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_PING, prudpPacket::FLAG_NEED_ACK, this->clientSessionId, this->m_outgoingSequenceId_ping, serverConnectionSignature); + directSendPacket(pingPacket, dstIp, dstPort); + m_unacknowledgedPingCount++; + delete pingPacket; + lastPingTimestamp = currentTimestamp; + } + } + else + { + if((currentTimestamp - lastPingTimestamp) >= 20000) + { + cemuLog_log(LogType::PRUDP, "[PRUDP] Sending new ping packet with sequenceId {}", this->m_outgoingSequenceId_ping+1); + // start a new ping packet with a new sequenceId. Note that ping packets have their own sequenceId and acknowledgement happens by manually comparing the incoming ping ACK against the last sent sequenceId + // only one unacknowledged ping packet can be in flight at a time. We will resend the same ping packet until we receive an ack + this->m_outgoingSequenceId_ping++; // increment before sending. The first ping has a sequenceId of 1 + prudpPacket* pingPacket = new prudpPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_PING, prudpPacket::FLAG_NEED_ACK, this->clientSessionId, this->m_outgoingSequenceId_ping, serverConnectionSignature); + directSendPacket(pingPacket, dstIp, dstPort); + m_unacknowledgedPingCount++; + delete pingPacket; + lastPingTimestamp = currentTimestamp; + } + } } return false; } @@ -844,6 +903,7 @@ void prudpClient::queuePacket(prudpPacket* packet, uint32 dstIp, uint16 dstPort) { if (packet->requiresAck()) { + cemu_assert_debug(packet->GetType() != prudpPacket::TYPE_PING); // ping packets use their own logic for acks, dont queue them // remember this packet until we receive the ack prudpAckRequired_t ackRequired = { 0 }; ackRequired.packet = packet; @@ -861,16 +921,18 @@ void prudpClient::queuePacket(prudpPacket* packet, uint32 dstIp, uint16 dstPort) void prudpClient::sendDatagram(uint8* input, sint32 length, bool reliable) { - if (reliable == false) + cemu_assert_debug(reliable); // non-reliable packets require testing + if(length >= 0x300) { - assert_dbg(); // todo + cemuLog_logOnce(LogType::Force, "PRUDP: Datagram too long"); } - if (length >= 0x300) - assert_dbg(); // too long, need to split into multiple fragments - // single fragment data packet - prudpPacket* packet = new prudpPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_DATA, prudpPacket::FLAG_NEED_ACK | prudpPacket::FLAG_RELIABLE, clientSessionId, outgoingSequenceId, 0); - outgoingSequenceId++; + uint16 flags = prudpPacket::FLAG_NEED_ACK; + if(reliable) + flags |= prudpPacket::FLAG_RELIABLE; + prudpPacket* packet = new prudpPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_DATA, flags, clientSessionId, outgoingSequenceId, 0); + if(reliable) + outgoingSequenceId++; packet->setFragmentIndex(0); packet->setData(input, length); queuePacket(packet, dstIp, dstPort); @@ -919,13 +981,7 @@ sint32 prudpClient::receiveDatagram(std::vector<uint8>& outputBuffer) } delete incomingPacket; // remove packet from queue - sint32 size = (sint32)queue_incomingPackets.size(); - size--; - for (sint32 i = 0; i < size; i++) - { - queue_incomingPackets[i] = queue_incomingPackets[i + 1]; - } - queue_incomingPackets.resize(size); + queue_incomingPackets.erase(queue_incomingPackets.begin()); // advance expected sequence id this->incomingSequenceId++; return datagramLen; @@ -975,13 +1031,7 @@ sint32 prudpClient::receiveDatagram(std::vector<uint8>& outputBuffer) delete incomingPacket; } // remove packets from queue - sint32 size = (sint32)queue_incomingPackets.size(); - size -= chainLength; - for (sint32 i = 0; i < size; i++) - { - queue_incomingPackets[i] = queue_incomingPackets[i + chainLength]; - } - queue_incomingPackets.resize(size); + queue_incomingPackets.erase(queue_incomingPackets.begin(), queue_incomingPackets.begin() + chainLength); this->incomingSequenceId += chainLength; return writeIndex; } diff --git a/src/Cemu/nex/prudp.h b/src/Cemu/nex/prudp.h index aa68f4f6..5ed5bcb1 100644 --- a/src/Cemu/nex/prudp.h +++ b/src/Cemu/nex/prudp.h @@ -71,15 +71,14 @@ public: void setData(uint8* data, sint32 length); void setFragmentIndex(uint8 fragmentIndex); sint32 buildData(uint8* output, sint32 maxLength); + uint8 GetType() const { return type; } + uint16 GetSequenceId() const { return m_sequenceId; } private: uint32 packetSignature(); uint8 calculateChecksum(uint8* data, sint32 length); -public: - uint16 sequenceId; - private: uint8 src; uint8 dst; @@ -91,6 +90,8 @@ private: prudpStreamSettings_t* streamSettings; std::vector<uint8> packetData; bool isEncrypted; + uint16 m_sequenceId{0}; + }; class prudpIncomingPacket @@ -186,6 +187,9 @@ private: uint16 outgoingSequenceId; uint16 incomingSequenceId; + uint16 m_outgoingSequenceId_ping{0}; + uint8 m_unacknowledgedPingCount{0}; + uint8 clientSessionId; uint8 serverSessionId; diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 4d2fb478..da57870c 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -2232,6 +2232,7 @@ void MainWindow::RecreateMenu() debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitThread), _("&Coreinit Thread API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitThread)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_NFP), _("&NN NFP"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NN_NFP)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_FP), _("&NN FP"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NN_FP)); + debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::PRUDP), _("&PRUDP (for NN FP)"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::PRUDP)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_BOSS), _("&NN BOSS"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NN_BOSS)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::GX2), _("&GX2 API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::GX2)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::SoundAPI), _("&Audio API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::SoundAPI)); From e2f972571906b909ad19cb922b1fa5549e3522da Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Thu, 18 Apr 2024 19:22:28 +0200 Subject: [PATCH 081/299] prudp: Code cleanup --- src/Cemu/nex/nex.cpp | 42 +-- src/Cemu/nex/prudp.cpp | 642 ++++++++++++++++++++--------------------- src/Cemu/nex/prudp.h | 148 +++++----- 3 files changed, 410 insertions(+), 422 deletions(-) diff --git a/src/Cemu/nex/nex.cpp b/src/Cemu/nex/nex.cpp index d0857507..973a4395 100644 --- a/src/Cemu/nex/nex.cpp +++ b/src/Cemu/nex/nex.cpp @@ -106,7 +106,7 @@ nexService::nexService() nexService::nexService(prudpClient* con) : nexService() { - if (con->isConnected() == false) + if (con->IsConnected() == false) cemu_assert_suspicious(); this->conNexService = con; bufferReceive = std::vector<uint8>(1024 * 4); @@ -191,7 +191,7 @@ void nexService::processQueuedRequest(queuedRequest_t* queuedRequest) uint32 callId = _currentCallId; _currentCallId++; // check state of connection - if (conNexService->getConnectionState() != prudpClient::STATE_CONNECTED) + if (conNexService->GetConnectionState() != prudpClient::ConnectionState::Connected) { nexServiceResponse_t response = { 0 }; response.isSuccessful = false; @@ -214,7 +214,7 @@ void nexService::processQueuedRequest(queuedRequest_t* queuedRequest) assert_dbg(); memcpy((packetBuffer + 0x0D), &queuedRequest->parameterData.front(), queuedRequest->parameterData.size()); sint32 length = 0xD + (sint32)queuedRequest->parameterData.size(); - conNexService->sendDatagram(packetBuffer, length, true); + conNexService->SendDatagram(packetBuffer, length, true); // remember request nexActiveRequestInfo_t requestInfo = { 0 }; requestInfo.callId = callId; @@ -299,13 +299,13 @@ void nexService::registerForAsyncProcessing() void nexService::updateTemporaryConnections() { // check for connection - conNexService->update(); - if (conNexService->isConnected()) + conNexService->Update(); + if (conNexService->IsConnected()) { if (connectionState == STATE_CONNECTING) connectionState = STATE_CONNECTED; } - if (conNexService->getConnectionState() == prudpClient::STATE_DISCONNECTED) + if (conNexService->GetConnectionState() == prudpClient::ConnectionState::Disconnected) connectionState = STATE_DISCONNECTED; } @@ -356,18 +356,18 @@ void nexService::sendRequestResponse(nexServiceRequest_t* request, uint32 errorC // update length field *(uint32*)response.getDataPtr() = response.getWriteIndex()-4; if(request->nex->conNexService) - request->nex->conNexService->sendDatagram(response.getDataPtr(), response.getWriteIndex(), true); + request->nex->conNexService->SendDatagram(response.getDataPtr(), response.getWriteIndex(), true); } void nexService::updateNexServiceConnection() { - if (conNexService->getConnectionState() == prudpClient::STATE_DISCONNECTED) + if (conNexService->GetConnectionState() == prudpClient::ConnectionState::Disconnected) { this->connectionState = STATE_DISCONNECTED; return; } - conNexService->update(); - sint32 datagramLen = conNexService->receiveDatagram(bufferReceive); + conNexService->Update(); + sint32 datagramLen = conNexService->ReceiveDatagram(bufferReceive); if (datagramLen > 0) { if (nexIsRequest(&bufferReceive[0], datagramLen)) @@ -454,12 +454,12 @@ bool _extractStationUrlParamValue(const char* urlStr, const char* paramName, cha return false; } -void nexServiceAuthentication_parseStationURL(char* urlStr, stationUrl_t* stationUrl) +void nexServiceAuthentication_parseStationURL(char* urlStr, prudpStationUrl* stationUrl) { // example: // prudps:/address=34.210.xxx.xxx;port=60181;CID=1;PID=2;sid=1;stream=10;type=2 - memset(stationUrl, 0, sizeof(stationUrl_t)); + memset(stationUrl, 0, sizeof(prudpStationUrl)); char optionValue[128]; if (_extractStationUrlParamValue(urlStr, "address", optionValue, sizeof(optionValue))) @@ -499,7 +499,7 @@ typedef struct sint32 kerberosTicketSize; uint8 kerberosTicket2[4096]; sint32 kerberosTicket2Size; - stationUrl_t server; + prudpStationUrl server; // progress info bool hasError; bool done; @@ -611,18 +611,18 @@ void nexServiceSecure_handleResponse_RegisterEx(nexService* nex, nexServiceRespo return; } -nexService* nex_secureLogin(authServerInfo_t* authServerInfo, const char* accessKey, const char* nexToken) +nexService* nex_secureLogin(prudpAuthServerInfo* authServerInfo, const char* accessKey, const char* nexToken) { prudpClient* prudpSecureSock = new prudpClient(authServerInfo->server.ip, authServerInfo->server.port, accessKey, authServerInfo); // wait until connected while (true) { - prudpSecureSock->update(); - if (prudpSecureSock->isConnected()) + prudpSecureSock->Update(); + if (prudpSecureSock->IsConnected()) { break; } - if (prudpSecureSock->getConnectionState() == prudpClient::STATE_DISCONNECTED) + if (prudpSecureSock->GetConnectionState() == prudpClient::ConnectionState::Disconnected) { // timeout or disconnected cemuLog_log(LogType::Force, "NEX: Secure login connection time-out"); @@ -638,7 +638,7 @@ nexService* nex_secureLogin(authServerInfo_t* authServerInfo, const char* access nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); char clientStationUrl[256]; - sprintf(clientStationUrl, "prudp:/port=%u;natf=0;natm=0;pmp=0;sid=15;type=2;upnp=0", (uint32)nex->getPRUDPConnection()->getSourcePort()); + sprintf(clientStationUrl, "prudp:/port=%u;natf=0;natm=0;pmp=0;sid=15;type=2;upnp=0", (uint32)nex->getPRUDPConnection()->GetSourcePort()); // station url list packetBuffer.writeU32(1); packetBuffer.writeString(clientStationUrl); @@ -737,9 +737,9 @@ nexService* nex_establishSecureConnection(uint32 authServerIp, uint16 authServer return nullptr; } // auth info - auto authServerInfo = std::make_unique<authServerInfo_t>(); + auto authServerInfo = std::make_unique<prudpAuthServerInfo>(); // decrypt ticket - RC4Ctx_t rc4Ticket; + RC4Ctx rc4Ticket; RC4_initCtx(&rc4Ticket, kerberosKey, 16); RC4_transform(&rc4Ticket, nexAuthService.kerberosTicket2, nexAuthService.kerberosTicket2Size - 16, nexAuthService.kerberosTicket2); nexPacketBuffer packetKerberosTicket(nexAuthService.kerberosTicket2, nexAuthService.kerberosTicket2Size - 16, false); @@ -756,7 +756,7 @@ nexService* nex_establishSecureConnection(uint32 authServerIp, uint16 authServer memcpy(authServerInfo->kerberosKey, kerberosKey, 16); memcpy(authServerInfo->secureKey, secureKey, 16); - memcpy(&authServerInfo->server, &nexAuthService.server, sizeof(stationUrl_t)); + memcpy(&authServerInfo->server, &nexAuthService.server, sizeof(prudpStationUrl)); authServerInfo->userPid = pid; return nex_secureLogin(authServerInfo.get(), accessKey, nexToken); diff --git a/src/Cemu/nex/prudp.cpp b/src/Cemu/nex/prudp.cpp index 7c01bec7..5c773fe7 100644 --- a/src/Cemu/nex/prudp.cpp +++ b/src/Cemu/nex/prudp.cpp @@ -1,72 +1,57 @@ #include "prudp.h" #include "util/crypto/md5.h" -#include<bitset> -#include<random> +#include <bitset> +#include <random> #include <boost/random/uniform_int.hpp> -void swap(unsigned char *a, unsigned char *b) +static void KSA(unsigned char* key, int keyLen, unsigned char* S) { - int tmp = *a; - *a = *b; - *b = tmp; -} - -void KSA(unsigned char *key, int keyLen, unsigned char *S) -{ - int j = 0; - for (int i = 0; i < RC4_N; i++) S[i] = i; - - for (int i = 0; i < RC4_N; i++) + int j = 0; + for (int i = 0; i < RC4_N; i++) { j = (j + S[i] + key[i % keyLen]) % RC4_N; - - swap(&S[i], &S[j]); + std::swap(S[i], S[j]); } } -void PRGA(unsigned char *S, unsigned char* input, int len, unsigned char* output) +static void PRGA(unsigned char* S, unsigned char* input, int len, unsigned char* output) { - int i = 0; - int j = 0; - - for (size_t n = 0; n < len; n++) + for (size_t n = 0; n < len; n++) { - i = (i + 1) % RC4_N; - j = (j + S[i]) % RC4_N; - - swap(&S[i], &S[j]); + int i = (i + 1) % RC4_N; + int j = (j + S[i]) % RC4_N; + std::swap(S[i], S[j]); int rnd = S[(S[i] + S[j]) % RC4_N]; - output[n] = rnd ^ input[n]; } } -void RC4(char* key, unsigned char* input, int len, unsigned char* output) +static void RC4(char* key, unsigned char* input, int len, unsigned char* output) { unsigned char S[RC4_N]; KSA((unsigned char*)key, (int)strlen(key), S); PRGA(S, input, len, output); } -void RC4_initCtx(RC4Ctx_t* rc4Ctx, const char* key) +void RC4_initCtx(RC4Ctx* rc4Ctx, const char* key) { rc4Ctx->i = 0; rc4Ctx->j = 0; KSA((unsigned char*)key, (int)strlen(key), rc4Ctx->S); } -void RC4_initCtx(RC4Ctx_t* rc4Ctx, unsigned char* key, int keyLen) +void RC4_initCtx(RC4Ctx* rc4Ctx, unsigned char* key, int keyLen) { rc4Ctx->i = 0; rc4Ctx->j = 0; KSA(key, keyLen, rc4Ctx->S); } -void RC4_transform(RC4Ctx_t* rc4Ctx, unsigned char* input, int len, unsigned char* output) +void RC4_transform(RC4Ctx* rc4Ctx, unsigned char* input, int len, unsigned char* output) { int i = rc4Ctx->i; int j = rc4Ctx->j; @@ -75,13 +60,10 @@ void RC4_transform(RC4Ctx_t* rc4Ctx, unsigned char* input, int len, unsigned cha { i = (i + 1) % RC4_N; j = (j + rc4Ctx->S[i]) % RC4_N; - - swap(&rc4Ctx->S[i], &rc4Ctx->S[j]); + std::swap(rc4Ctx->S[i], rc4Ctx->S[j]); int rnd = rc4Ctx->S[(rc4Ctx->S[i] + rc4Ctx->S[j]) % RC4_N]; - output[n] = rnd ^ input[n]; } - rc4Ctx->i = i; rc4Ctx->j = j; } @@ -91,34 +73,14 @@ uint32 prudpGetMSTimestamp() return GetTickCount(); } -std::bitset<10000> _portUsageMask; - -uint16 getRandomSrcPRUDPPort() -{ - while (true) - { - sint32 p = rand() % 10000; - if (_portUsageMask.test(p)) - continue; - _portUsageMask.set(p); - return 40000 + p; - } - return 0; -} - -void releasePRUDPPort(uint16 port) -{ - uint32 bitIndex = port - 40000; - _portUsageMask.reset(bitIndex); -} - std::mt19937_64 prudpRG(GetTickCount()); -// workaround for static asserts when using uniform_int_distribution -boost::random::uniform_int_distribution<int> prudpDis8(0, 0xFF); +// workaround for static asserts when using uniform_int_distribution (see https://github.com/cemu-project/Cemu/issues/48) +boost::random::uniform_int_distribution<int> prudpRandomDistribution8(0, 0xFF); +boost::random::uniform_int_distribution<int> prudpRandomDistributionPortGen(0, 10000); uint8 prudp_generateRandomU8() { - return prudpDis8(prudpRG); + return prudpRandomDistribution8(prudpRG); } uint32 prudp_generateRandomU32() @@ -133,7 +95,29 @@ uint32 prudp_generateRandomU32() return v; } -uint8 prudp_calculateChecksum(uint8 checksumBase, uint8* data, sint32 length) +std::bitset<10000> _portUsageMask; + +static uint16 AllocateRandomSrcPRUDPPort() +{ + while (true) + { + sint32 p = prudpRandomDistributionPortGen(prudpRG); + if (_portUsageMask.test(p)) + continue; + _portUsageMask.set(p); + return 40000 + p; + } +} + +static void ReleasePRUDPSrcPort(uint16 port) +{ + cemu_assert_debug(port >= 40000); + uint32 bitIndex = port - 40000; + cemu_assert_debug(_portUsageMask.test(bitIndex)); + _portUsageMask.reset(bitIndex); +} + +static uint8 prudp_calculateChecksum(uint8 checksumBase, uint8* data, sint32 length) { uint32 checksum32 = 0; for (sint32 i = 0; i < length / 4; i++) @@ -141,7 +125,7 @@ uint8 prudp_calculateChecksum(uint8 checksumBase, uint8* data, sint32 length) checksum32 += *(uint32*)(data + i * 4); } uint8 checksum = checksumBase; - for (sint32 i = length&(~3); i < length; i++) + for (sint32 i = length & (~3); i < length; i++) { checksum += data[i]; } @@ -161,16 +145,16 @@ sint32 prudpPacket::calculateSizeFromPacketData(uint8* data, sint32 length) return 0; // get flags fields uint16 typeAndFlags = *(uint16*)(data + 0x02); - uint16 type = (typeAndFlags&0xF); + uint16 type = (typeAndFlags & 0xF); uint16 flags = (typeAndFlags >> 4); - if ((flags&FLAG_HAS_SIZE) == 0) + if ((flags & FLAG_HAS_SIZE) == 0) return length; // without a size field, we cant calculate the length sint32 calculatedSize; if (type == TYPE_SYN) { if (length < (0xB + 0x4 + 2)) return 0; - uint16 payloadSize = *(uint16*)(data+0xB+0x4); + uint16 payloadSize = *(uint16*)(data + 0xB + 0x4); calculatedSize = 0xB + 0x4 + 2 + (sint32)payloadSize + 1; // base header + connection signature (SYN param) + payloadSize field + checksum after payload if (calculatedSize > length) return 0; @@ -212,7 +196,7 @@ sint32 prudpPacket::calculateSizeFromPacketData(uint8* data, sint32 length) return length; } -prudpPacket::prudpPacket(prudpStreamSettings_t* streamSettings, uint8 src, uint8 dst, uint8 type, uint16 flags, uint8 sessionId, uint16 sequenceId, uint32 packetSignature) +prudpPacket::prudpPacket(prudpStreamSettings* streamSettings, uint8 src, uint8 dst, uint8 type, uint16 flags, uint8 sessionId, uint16 sequenceId, uint32 packetSignature) { this->src = src; this->dst = dst; @@ -228,7 +212,7 @@ prudpPacket::prudpPacket(prudpStreamSettings_t* streamSettings, uint8 src, uint8 bool prudpPacket::requiresAck() { - return (flags&FLAG_NEED_ACK) != 0; + return (flags & FLAG_NEED_ACK) != 0; } sint32 prudpPacket::buildData(uint8* output, sint32 maxLength) @@ -352,7 +336,8 @@ prudpIncomingPacket::prudpIncomingPacket() streamSettings = nullptr; } -prudpIncomingPacket::prudpIncomingPacket(prudpStreamSettings_t* streamSettings, uint8* data, sint32 length) : prudpIncomingPacket() +prudpIncomingPacket::prudpIncomingPacket(prudpStreamSettings* streamSettings, uint8* data, sint32 length) + : prudpIncomingPacket() { if (length < 0xB + 1) { @@ -418,7 +403,7 @@ prudpIncomingPacket::prudpIncomingPacket(prudpStreamSettings_t* streamSettings, bool hasPayloadSize = (this->flags & prudpPacket::FLAG_HAS_SIZE) != 0; // verify length - if ((length-readIndex) < 1+(hasPayloadSize?2:0)) + if ((length - readIndex) < 1 + (hasPayloadSize ? 2 : 0)) { // too short isInvalid = true; @@ -475,57 +460,45 @@ void prudpIncomingPacket::decrypt() RC4_transform(&streamSettings->rc4Server, &packetData.front(), (int)packetData.size(), &packetData.front()); } -#define PRUDP_VPORT(__streamType, __port) (((__streamType)<<4) | (__port)) +#define PRUDP_VPORT(__streamType, __port) (((__streamType) << 4) | (__port)) prudpClient::prudpClient() { - currentConnectionState = STATE_CONNECTING; - serverConnectionSignature = 0; - clientConnectionSignature = 0; - hasSentCon = false; - outgoingSequenceId = 0; - incomingSequenceId = 0; + m_currentConnectionState = ConnectionState::Connecting; + m_serverConnectionSignature = 0; + m_clientConnectionSignature = 0; + m_incomingSequenceId = 0; - clientSessionId = 0; - serverSessionId = 0; - - isSecureConnection = false; + m_clientSessionId = 0; + m_serverSessionId = 0; } -prudpClient::~prudpClient() +prudpClient::prudpClient(uint32 dstIp, uint16 dstPort, const char* key) + : prudpClient() { - if (srcPort != 0) - { - releasePRUDPPort(srcPort); - closesocket(socketUdp); - } -} - -prudpClient::prudpClient(uint32 dstIp, uint16 dstPort, const char* key) : prudpClient() -{ - this->dstIp = dstIp; - this->dstPort = dstPort; + m_dstIp = dstIp; + m_dstPort = dstPort; // get unused random source port for (sint32 tries = 0; tries < 5; tries++) { - srcPort = getRandomSrcPRUDPPort(); + m_srcPort = AllocateRandomSrcPRUDPPort(); // create and bind udp socket - socketUdp = socket(AF_INET, SOCK_DGRAM, 0); + m_socketUdp = socket(AF_INET, SOCK_DGRAM, 0); struct sockaddr_in udpServer; udpServer.sin_family = AF_INET; udpServer.sin_addr.s_addr = INADDR_ANY; - udpServer.sin_port = htons(srcPort); - if (bind(socketUdp, (struct sockaddr *)&udpServer, sizeof(udpServer)) == SOCKET_ERROR) + udpServer.sin_port = htons(m_srcPort); + if (bind(m_socketUdp, (struct sockaddr*)&udpServer, sizeof(udpServer)) == SOCKET_ERROR) { + ReleasePRUDPSrcPort(m_srcPort); + m_srcPort = 0; if (tries == 4) { cemuLog_log(LogType::Force, "PRUDP: Failed to bind UDP socket"); - currentConnectionState = STATE_DISCONNECTED; - srcPort = 0; + m_currentConnectionState = ConnectionState::Disconnected; return; } - releasePRUDPPort(srcPort); - closesocket(socketUdp); + closesocket(m_socketUdp); continue; } else @@ -533,79 +506,77 @@ prudpClient::prudpClient(uint32 dstIp, uint16 dstPort, const char* key) : prudpC } // set socket to non-blocking mode #if BOOST_OS_WINDOWS - u_long nonBlockingMode = 1; // 1 to enable non-blocking socket - ioctlsocket(socketUdp, FIONBIO, &nonBlockingMode); + u_long nonBlockingMode = 1; // 1 to enable non-blocking socket + ioctlsocket(m_socketUdp, FIONBIO, &nonBlockingMode); #else int flags = fcntl(socketUdp, F_GETFL); fcntl(socketUdp, F_SETFL, flags | O_NONBLOCK); #endif // generate frequently used parameters - this->vport_src = PRUDP_VPORT(prudpPacket::STREAM_TYPE_SECURE, 0xF); - this->vport_dst = PRUDP_VPORT(prudpPacket::STREAM_TYPE_SECURE, 0x1); + this->m_srcVPort = PRUDP_VPORT(prudpPacket::STREAM_TYPE_SECURE, 0xF); + this->m_dstVPort = PRUDP_VPORT(prudpPacket::STREAM_TYPE_SECURE, 0x1); // set stream settings uint8 checksumBase = 0; for (sint32 i = 0; key[i] != '\0'; i++) { checksumBase += key[i]; } - streamSettings.checksumBase = checksumBase; + m_streamSettings.checksumBase = checksumBase; MD5_CTX md5Ctx; MD5_Init(&md5Ctx); MD5_Update(&md5Ctx, key, (int)strlen(key)); - MD5_Final(streamSettings.accessKeyDigest, &md5Ctx); + MD5_Final(m_streamSettings.accessKeyDigest, &md5Ctx); // init stream ciphers - RC4_initCtx(&streamSettings.rc4Server, "CD&ML"); - RC4_initCtx(&streamSettings.rc4Client, "CD&ML"); + RC4_initCtx(&m_streamSettings.rc4Server, "CD&ML"); + RC4_initCtx(&m_streamSettings.rc4Client, "CD&ML"); // send syn packet - prudpPacket* synPacket = new prudpPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_SYN, prudpPacket::FLAG_NEED_ACK, 0, 0, 0); - queuePacket(synPacket, dstIp, dstPort); - outgoingSequenceId++; + SendCurrentHandshakePacket(); // set incoming sequence id to 1 - incomingSequenceId = 1; + m_incomingSequenceId = 1; } -prudpClient::prudpClient(uint32 dstIp, uint16 dstPort, const char* key, authServerInfo_t* authInfo) : prudpClient(dstIp, dstPort, key) +prudpClient::prudpClient(uint32 dstIp, uint16 dstPort, const char* key, prudpAuthServerInfo* authInfo) + : prudpClient(dstIp, dstPort, key) { - RC4_initCtx(&streamSettings.rc4Server, authInfo->secureKey, 16); - RC4_initCtx(&streamSettings.rc4Client, authInfo->secureKey, 16); - this->isSecureConnection = true; - memcpy(&this->authInfo, authInfo, sizeof(authServerInfo_t)); + RC4_initCtx(&m_streamSettings.rc4Server, authInfo->secureKey, 16); + RC4_initCtx(&m_streamSettings.rc4Client, authInfo->secureKey, 16); + this->m_isSecureConnection = true; + memcpy(&this->m_authInfo, authInfo, sizeof(prudpAuthServerInfo)); } -bool prudpClient::isConnected() +prudpClient::~prudpClient() { - return currentConnectionState == STATE_CONNECTED; + if (m_srcPort != 0) + { + ReleasePRUDPSrcPort(m_srcPort); + closesocket(m_socketUdp); + } } -uint8 prudpClient::getConnectionState() +void prudpClient::AcknowledgePacket(uint16 sequenceId) { - return currentConnectionState; -} - -void prudpClient::acknowledgePacket(uint16 sequenceId) -{ - auto it = std::begin(list_packetsWithAckReq); - while (it != std::end(list_packetsWithAckReq)) + auto it = std::begin(m_dataPacketsWithAckReq); + while (it != std::end(m_dataPacketsWithAckReq)) { if (it->packet->GetSequenceId() == sequenceId) { delete it->packet; - list_packetsWithAckReq.erase(it); + m_dataPacketsWithAckReq.erase(it); return; } it++; } } -void prudpClient::sortIncomingDataPacket(prudpIncomingPacket* incomingPacket) +void prudpClient::SortIncomingDataPacket(std::unique_ptr<prudpIncomingPacket> incomingPacket) { uint16 sequenceIdIncomingPacket = incomingPacket->sequenceId; // find insert index sint32 insertIndex = 0; - while (insertIndex < queue_incomingPackets.size() ) + while (insertIndex < m_incomingPacketQueue.size()) { - uint16 seqDif = sequenceIdIncomingPacket - queue_incomingPackets[insertIndex]->sequenceId; - if (seqDif&0x8000) + uint16 seqDif = sequenceIdIncomingPacket - m_incomingPacketQueue[insertIndex]->sequenceId; + if (seqDif & 0x8000) break; // negative seqDif -> insert before current element #ifdef CEMU_DEBUG_ASSERT if (seqDif == 0) @@ -613,39 +584,83 @@ void prudpClient::sortIncomingDataPacket(prudpIncomingPacket* incomingPacket) #endif insertIndex++; } - // insert - sint32 currentSize = (sint32)queue_incomingPackets.size(); - queue_incomingPackets.resize(currentSize+1); - for(sint32 i=currentSize; i>insertIndex; i--) + m_incomingPacketQueue.insert(m_incomingPacketQueue.begin() + insertIndex, std::move(incomingPacket)); + // debug check if packets are really ordered by sequence id +#ifdef CEMU_DEBUG_ASSERT + for (sint32 i = 1; i < m_incomingPacketQueue.size(); i++) { - queue_incomingPackets[i] = queue_incomingPackets[i - 1]; + uint16 seqDif = m_incomingPacketQueue[i]->sequenceId - m_incomingPacketQueue[i - 1]->sequenceId; + if (seqDif & 0x8000) + seqDif = -seqDif; + if (seqDif >= 0x8000) + assert_dbg(); } - queue_incomingPackets[insertIndex] = incomingPacket; +#endif } -sint32 prudpClient::kerberosEncryptData(uint8* input, sint32 length, uint8* output) +sint32 prudpClient::KerberosEncryptData(uint8* input, sint32 length, uint8* output) { - RC4Ctx_t rc4Kerberos; - RC4_initCtx(&rc4Kerberos, this->authInfo.secureKey, 16); + RC4Ctx rc4Kerberos; + RC4_initCtx(&rc4Kerberos, this->m_authInfo.secureKey, 16); memcpy(output, input, length); RC4_transform(&rc4Kerberos, output, length, output); // calculate and append hmac - hmacMD5(this->authInfo.secureKey, 16, output, length, output+length); + hmacMD5(this->m_authInfo.secureKey, 16, output, length, output + length); return length + 16; } -void prudpClient::handleIncomingPacket(prudpIncomingPacket* incomingPacket) +// (re)sends either CON or SYN based on what stage of the login we are at +// the sequenceId for both is hardcoded for both because we'll never send anything in between +void prudpClient::SendCurrentHandshakePacket() { - if(incomingPacket->type == prudpPacket::TYPE_PING) + if (!m_hasSynAck) { - if (incomingPacket->flags&prudpPacket::FLAG_ACK) + // send syn (with a fixed sequenceId of 0) + prudpPacket synPacket(&m_streamSettings, m_srcVPort, m_dstVPort, prudpPacket::TYPE_SYN, prudpPacket::FLAG_NEED_ACK, 0, 0, 0); + DirectSendPacket(&synPacket); + } + else + { + // send con (with a fixed sequenceId of 1) + prudpPacket conPacket(&m_streamSettings, m_srcVPort, m_dstVPort, prudpPacket::TYPE_CON, prudpPacket::FLAG_NEED_ACK | prudpPacket::FLAG_RELIABLE, this->m_clientSessionId, 1, m_serverConnectionSignature); + if (this->m_isSecureConnection) + { + uint8 tempBuffer[512]; + nexPacketBuffer conData(tempBuffer, sizeof(tempBuffer), true); + conData.writeU32(this->m_clientConnectionSignature); + conData.writeBuffer(m_authInfo.secureTicket, m_authInfo.secureTicketLength); + // encrypted request data + uint8 requestData[4 * 3]; + uint8 requestDataEncrypted[4 * 3 + 0x10]; + *(uint32*)(requestData + 0x0) = m_authInfo.userPid; + *(uint32*)(requestData + 0x4) = m_authInfo.server.cid; + *(uint32*)(requestData + 0x8) = prudp_generateRandomU32(); // todo - check value + sint32 encryptedSize = KerberosEncryptData(requestData, sizeof(requestData), requestDataEncrypted); + conData.writeBuffer(requestDataEncrypted, encryptedSize); + conPacket.setData(conData.getDataPtr(), conData.getWriteIndex()); + } + else + { + conPacket.setData((uint8*)&this->m_clientConnectionSignature, sizeof(uint32)); + } + DirectSendPacket(&conPacket); + } + m_lastHandshakeTimestamp = prudpGetMSTimestamp(); + m_handshakeRetryCount++; +} + +void prudpClient::HandleIncomingPacket(std::unique_ptr<prudpIncomingPacket> incomingPacket) +{ + if (incomingPacket->type == prudpPacket::TYPE_PING) + { + if (incomingPacket->flags & prudpPacket::FLAG_ACK) { // ack for our ping packet - if(incomingPacket->flags&prudpPacket::FLAG_NEED_ACK) + if (incomingPacket->flags & prudpPacket::FLAG_NEED_ACK) cemuLog_log(LogType::PRUDP, "[PRUDP] Received unexpected ping packet with both ACK and NEED_ACK set"); - if(m_unacknowledgedPingCount > 0) + if (m_unacknowledgedPingCount > 0) { - if(incomingPacket->sequenceId == m_outgoingSequenceId_ping) + if (incomingPacket->sequenceId == m_outgoingSequenceId_ping) { cemuLog_log(LogType::PRUDP, "[PRUDP] Received ping packet ACK (unacknowledged count: {})", m_unacknowledgedPingCount); m_unacknowledgedPingCount = 0; @@ -660,140 +675,127 @@ void prudpClient::handleIncomingPacket(prudpIncomingPacket* incomingPacket) cemuLog_log(LogType::PRUDP, "[PRUDP] Received ping packet ACK which we dont need"); } } - else if (incomingPacket->flags&prudpPacket::FLAG_NEED_ACK) + else if (incomingPacket->flags & prudpPacket::FLAG_NEED_ACK) { // other side is asking for ping ack cemuLog_log(LogType::PRUDP, "[PRUDP] Received ping packet with NEED_ACK set. Sending ACK back"); - cemu_assert_debug(incomingPacket->packetData.empty()); // todo - echo data? - prudpPacket ackPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_PING, prudpPacket::FLAG_ACK, this->clientSessionId, incomingPacket->sequenceId, 0); - directSendPacket(&ackPacket, dstIp, dstPort); + prudpPacket ackPacket(&m_streamSettings, m_srcVPort, m_dstVPort, prudpPacket::TYPE_PING, prudpPacket::FLAG_ACK, this->m_clientSessionId, incomingPacket->sequenceId, 0); + if(!incomingPacket->packetData.empty()) + ackPacket.setData(incomingPacket->packetData.data(), incomingPacket->packetData.size()); + DirectSendPacket(&ackPacket); } - delete incomingPacket; return; } - // handle general packet ACK - if (incomingPacket->flags&prudpPacket::FLAG_ACK) + else if (incomingPacket->type == prudpPacket::TYPE_SYN) { - acknowledgePacket(incomingPacket->sequenceId); - } - // special cases - if (incomingPacket->type == prudpPacket::TYPE_SYN) - { - if (hasSentCon == false && incomingPacket->hasData && incomingPacket->packetData.size() == 4) + // syn packet from server is expected to have ACK set + if (!(incomingPacket->flags & prudpPacket::FLAG_ACK)) { - this->serverConnectionSignature = *(uint32*)&incomingPacket->packetData.front(); - this->clientSessionId = prudp_generateRandomU8(); - // generate client session id - this->clientConnectionSignature = prudp_generateRandomU32(); - // send con packet - prudpPacket* conPacket = new prudpPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_CON, prudpPacket::FLAG_NEED_ACK|prudpPacket::FLAG_RELIABLE, this->clientSessionId, outgoingSequenceId, serverConnectionSignature); - outgoingSequenceId++; - - if (this->isSecureConnection) - { - // set packet specific data (client connection signature) - uint8 tempBuffer[512]; - nexPacketBuffer conData(tempBuffer, sizeof(tempBuffer), true); - conData.writeU32(this->clientConnectionSignature); - conData.writeBuffer(authInfo.secureTicket, authInfo.secureTicketLength); - // encrypted request data - uint8 requestData[4 * 3]; - uint8 requestDataEncrypted[4 * 3 + 0x10]; - *(uint32*)(requestData + 0x0) = authInfo.userPid; - *(uint32*)(requestData + 0x4) = authInfo.server.cid; - *(uint32*)(requestData + 0x8) = prudp_generateRandomU32(); // todo - check value - sint32 encryptedSize = kerberosEncryptData(requestData, sizeof(requestData), requestDataEncrypted); - conData.writeBuffer(requestDataEncrypted, encryptedSize); - conPacket->setData(conData.getDataPtr(), conData.getWriteIndex()); - } - else - { - // set packet specific data (client connection signature) - conPacket->setData((uint8*)&this->clientConnectionSignature, sizeof(uint32)); - } - // send packet - queuePacket(conPacket, dstIp, dstPort); - // remember con packet as sent - hasSentCon = true; + cemuLog_log(LogType::Force, "[PRUDP] Received SYN packet without ACK flag set"); // always log this + return; } - delete incomingPacket; + if (m_hasSynAck || !incomingPacket->hasData || incomingPacket->packetData.size() != 4) + { + // syn already acked or not a valid syn packet + cemuLog_log(LogType::PRUDP, "[PRUDP] Received unexpected SYN packet"); + return; + } + m_hasSynAck = true; + this->m_serverConnectionSignature = *(uint32*)&incomingPacket->packetData.front(); + // generate client session id and connection signature + this->m_clientSessionId = prudp_generateRandomU8(); + this->m_clientConnectionSignature = prudp_generateRandomU32(); + // send con packet + m_handshakeRetryCount = 0; + SendCurrentHandshakePacket(); return; } else if (incomingPacket->type == prudpPacket::TYPE_CON) { - // connected! - if (currentConnectionState == STATE_CONNECTING) + if (!m_hasSynAck || m_hasConAck) { - lastPingTimestamp = prudpGetMSTimestamp(); - cemu_assert_debug(serverSessionId == 0); - serverSessionId = incomingPacket->sessionId; - currentConnectionState = STATE_CONNECTED; - cemuLog_log(LogType::PRUDP, "[PRUDP] Connection established. ClientSession {:02x} ServerSession {:02x}", clientSessionId, serverSessionId); + cemuLog_log(LogType::PRUDP, "[PRUDP] Received unexpected CON packet"); + return; } - delete incomingPacket; + // make sure the packet has the ACK flag set + if (!(incomingPacket->flags & prudpPacket::FLAG_ACK)) + { + cemuLog_log(LogType::Force, "[PRUDP] Received CON packet without ACK flag set"); + return; + } + m_hasConAck = true; + m_handshakeRetryCount = 0; + cemu_assert_debug(m_currentConnectionState == ConnectionState::Connecting); + // connected! + m_lastPingTimestamp = prudpGetMSTimestamp(); + cemu_assert_debug(m_serverSessionId == 0); + m_serverSessionId = incomingPacket->sessionId; + m_currentConnectionState = ConnectionState::Connected; + cemuLog_log(LogType::PRUDP, "[PRUDP] Connection established. ClientSession {:02x} ServerSession {:02x}", m_clientSessionId, m_serverSessionId); return; } else if (incomingPacket->type == prudpPacket::TYPE_DATA) { - // send ack back if requested - if (incomingPacket->flags&prudpPacket::FLAG_NEED_ACK) + // handle ACK + if (incomingPacket->flags & prudpPacket::FLAG_ACK) { - prudpPacket ackPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_DATA, prudpPacket::FLAG_ACK, this->clientSessionId, incomingPacket->sequenceId, 0); - directSendPacket(&ackPacket, dstIp, dstPort); + AcknowledgePacket(incomingPacket->sequenceId); + if(!incomingPacket->packetData.empty()) + cemuLog_log(LogType::PRUDP, "[PRUDP] Received ACK data packet with payload"); + return; + } + // send ack back if requested + if (incomingPacket->flags & prudpPacket::FLAG_NEED_ACK) + { + prudpPacket ackPacket(&m_streamSettings, m_srcVPort, m_dstVPort, prudpPacket::TYPE_DATA, prudpPacket::FLAG_ACK, this->m_clientSessionId, incomingPacket->sequenceId, 0); + DirectSendPacket(&ackPacket); } // skip data packets without payload if (incomingPacket->packetData.empty()) - { - delete incomingPacket; return; - } - // verify some values - uint16 seqDist = incomingPacket->sequenceId - incomingSequenceId; + // verify sequence id + uint16 seqDist = incomingPacket->sequenceId - m_incomingSequenceId; if (seqDist >= 0xC000) { // outdated - delete incomingPacket; return; } // check if packet is already queued - for (auto& it : queue_incomingPackets) + for (auto& it : m_incomingPacketQueue) { if (it->sequenceId == incomingPacket->sequenceId) { // already queued (should check other values too, like packet type?) cemuLog_log(LogType::PRUDP, "Duplicate PRUDP packet received"); - delete incomingPacket; return; } } // put into ordered receive queue - sortIncomingDataPacket(incomingPacket); + SortIncomingDataPacket(std::move(incomingPacket)); } else if (incomingPacket->type == prudpPacket::TYPE_DISCONNECT) { - currentConnectionState = STATE_DISCONNECTED; + m_currentConnectionState = ConnectionState::Disconnected; return; } else { - // ignore unknown packet - delete incomingPacket; - return; + cemuLog_log(LogType::PRUDP, "[PRUDP] Received unknown packet type"); } } -bool prudpClient::update() +bool prudpClient::Update() { - if (currentConnectionState == STATE_DISCONNECTED) + if (m_currentConnectionState == ConnectionState::Disconnected) return false; uint32 currentTimestamp = prudpGetMSTimestamp(); // check for incoming packets uint8 receiveBuffer[4096]; while (true) { - sockaddr receiveFrom = { 0 }; + sockaddr receiveFrom = {0}; socklen_t receiveFromLen = sizeof(receiveFrom); - sint32 r = recvfrom(socketUdp, (char*)receiveBuffer, sizeof(receiveBuffer), 0, &receiveFrom, &receiveFromLen); + sint32 r = recvfrom(m_socketUdp, (char*)receiveBuffer, sizeof(receiveBuffer), 0, &receiveFrom, &receiveFromLen); if (r >= 0) { // todo: Verify sender (receiveFrom) @@ -807,203 +809,195 @@ bool prudpClient::update() cemuLog_log(LogType::Force, "[PRUDP] Invalid packet length"); break; } - prudpIncomingPacket* incomingPacket = new prudpIncomingPacket(&streamSettings, receiveBuffer + pIdx, packetLength); + auto incomingPacket = std::make_unique<prudpIncomingPacket>(&m_streamSettings, receiveBuffer + pIdx, packetLength); pIdx += packetLength; if (incomingPacket->hasError()) { cemuLog_log(LogType::Force, "[PRUDP] Packet error"); - delete incomingPacket; break; } - if (incomingPacket->type != prudpPacket::TYPE_CON && incomingPacket->sessionId != serverSessionId) + if (incomingPacket->type != prudpPacket::TYPE_CON && incomingPacket->sessionId != m_serverSessionId) { cemuLog_log(LogType::PRUDP, "[PRUDP] Invalid session id"); - delete incomingPacket; continue; // different session } - handleIncomingPacket(incomingPacket); + HandleIncomingPacket(std::move(incomingPacket)); } } else break; } // check for ack timeouts - for (auto &it : list_packetsWithAckReq) + for (auto& it : m_dataPacketsWithAckReq) { if ((currentTimestamp - it.lastRetryTimestamp) >= 2300) { if (it.retryCount >= 7) { // after too many retries consider the connection dead - currentConnectionState = STATE_DISCONNECTED; + m_currentConnectionState = ConnectionState::Disconnected; } // resend - directSendPacket(it.packet, dstIp, dstPort); + DirectSendPacket(it.packet); it.lastRetryTimestamp = currentTimestamp; it.retryCount++; } } - // check if we need to send another ping - if (currentConnectionState == STATE_CONNECTED) + if (m_currentConnectionState == ConnectionState::Connecting) { - if(m_unacknowledgedPingCount != 0) // counts how many times we sent a ping packet (for the current sequenceId) without receiving an ack + // check if we need to resend SYN or CON + uint32 timeSinceLastHandshake = currentTimestamp - m_lastHandshakeTimestamp; + if (timeSinceLastHandshake >= 1200) + { + if (m_handshakeRetryCount >= 5) + { + // too many retries, assume the other side doesn't listen + m_currentConnectionState = ConnectionState::Disconnected; + cemuLog_log(LogType::PRUDP, "PRUDP: Failed to connect"); + return false; + } + SendCurrentHandshakePacket(); + } + } + else if (m_currentConnectionState == ConnectionState::Connected) + { + // handle pings + if (m_unacknowledgedPingCount != 0) // counts how many times we sent a ping packet (for the current sequenceId) without receiving an ack { // we are waiting for the ack of the previous ping, but it hasn't arrived yet so send another ping packet - if((currentTimestamp - lastPingTimestamp) >= 1500) + if ((currentTimestamp - m_lastPingTimestamp) >= 1500) { cemuLog_log(LogType::PRUDP, "[PRUDP] Resending ping packet (no ack received)"); - if(m_unacknowledgedPingCount >= 10) + if (m_unacknowledgedPingCount >= 10) { // too many unacknowledged pings, assume the connection is dead - currentConnectionState = STATE_DISCONNECTED; + m_currentConnectionState = ConnectionState::Disconnected; cemuLog_log(LogType::PRUDP, "PRUDP: Connection did not receive a ping response in a while. Assuming disconnect"); return false; } // resend the ping packet - prudpPacket* pingPacket = new prudpPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_PING, prudpPacket::FLAG_NEED_ACK, this->clientSessionId, this->m_outgoingSequenceId_ping, serverConnectionSignature); - directSendPacket(pingPacket, dstIp, dstPort); + prudpPacket pingPacket(&m_streamSettings, m_srcVPort, m_dstVPort, prudpPacket::TYPE_PING, prudpPacket::FLAG_NEED_ACK, this->m_clientSessionId, this->m_outgoingSequenceId_ping, m_serverConnectionSignature); + DirectSendPacket(&pingPacket); m_unacknowledgedPingCount++; - delete pingPacket; - lastPingTimestamp = currentTimestamp; + m_lastPingTimestamp = currentTimestamp; } } else { - if((currentTimestamp - lastPingTimestamp) >= 20000) + if ((currentTimestamp - m_lastPingTimestamp) >= 20000) { - cemuLog_log(LogType::PRUDP, "[PRUDP] Sending new ping packet with sequenceId {}", this->m_outgoingSequenceId_ping+1); + cemuLog_log(LogType::PRUDP, "[PRUDP] Sending new ping packet with sequenceId {}", this->m_outgoingSequenceId_ping + 1); // start a new ping packet with a new sequenceId. Note that ping packets have their own sequenceId and acknowledgement happens by manually comparing the incoming ping ACK against the last sent sequenceId // only one unacknowledged ping packet can be in flight at a time. We will resend the same ping packet until we receive an ack this->m_outgoingSequenceId_ping++; // increment before sending. The first ping has a sequenceId of 1 - prudpPacket* pingPacket = new prudpPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_PING, prudpPacket::FLAG_NEED_ACK, this->clientSessionId, this->m_outgoingSequenceId_ping, serverConnectionSignature); - directSendPacket(pingPacket, dstIp, dstPort); + prudpPacket pingPacket(&m_streamSettings, m_srcVPort, m_dstVPort, prudpPacket::TYPE_PING, prudpPacket::FLAG_NEED_ACK, this->m_clientSessionId, this->m_outgoingSequenceId_ping, m_serverConnectionSignature); + DirectSendPacket(&pingPacket); m_unacknowledgedPingCount++; - delete pingPacket; - lastPingTimestamp = currentTimestamp; + m_lastPingTimestamp = currentTimestamp; } } } return false; } -void prudpClient::directSendPacket(prudpPacket* packet, uint32 dstIp, uint16 dstPort) +void prudpClient::DirectSendPacket(prudpPacket* packet) { uint8 packetBuffer[prudpPacket::PACKET_RAW_SIZE_MAX]; - sint32 len = packet->buildData(packetBuffer, prudpPacket::PACKET_RAW_SIZE_MAX); - sockaddr_in destAddr; destAddr.sin_family = AF_INET; - destAddr.sin_port = htons(dstPort); - destAddr.sin_addr.s_addr = dstIp; - sendto(socketUdp, (const char*)packetBuffer, len, 0, (const sockaddr*)&destAddr, sizeof(destAddr)); + destAddr.sin_port = htons(m_dstPort); + destAddr.sin_addr.s_addr = m_dstIp; + sendto(m_socketUdp, (const char*)packetBuffer, len, 0, (const sockaddr*)&destAddr, sizeof(destAddr)); } -void prudpClient::queuePacket(prudpPacket* packet, uint32 dstIp, uint16 dstPort) +void prudpClient::QueuePacket(prudpPacket* packet) { + cemu_assert_debug(packet->GetType() == prudpPacket::TYPE_DATA); // only data packets should be queued if (packet->requiresAck()) { - cemu_assert_debug(packet->GetType() != prudpPacket::TYPE_PING); // ping packets use their own logic for acks, dont queue them // remember this packet until we receive the ack - prudpAckRequired_t ackRequired = { 0 }; - ackRequired.packet = packet; - ackRequired.initialSendTimestamp = prudpGetMSTimestamp(); - ackRequired.lastRetryTimestamp = ackRequired.initialSendTimestamp; - list_packetsWithAckReq.push_back(ackRequired); - directSendPacket(packet, dstIp, dstPort); + m_dataPacketsWithAckReq.emplace_back(packet, prudpGetMSTimestamp()); + DirectSendPacket(packet); } else { - directSendPacket(packet, dstIp, dstPort); + DirectSendPacket(packet); delete packet; } } -void prudpClient::sendDatagram(uint8* input, sint32 length, bool reliable) +void prudpClient::SendDatagram(uint8* input, sint32 length, bool reliable) { - cemu_assert_debug(reliable); // non-reliable packets require testing - if(length >= 0x300) + cemu_assert_debug(reliable); // non-reliable packets require correct sequenceId handling and testing + cemu_assert_debug(m_hasSynAck && m_hasConAck); // cant send data packets before we are connected + if (length >= 0x300) { - cemuLog_logOnce(LogType::Force, "PRUDP: Datagram too long"); + cemuLog_logOnce(LogType::Force, "PRUDP: Datagram too long. Fragmentation not implemented yet"); } // single fragment data packet uint16 flags = prudpPacket::FLAG_NEED_ACK; - if(reliable) + if (reliable) flags |= prudpPacket::FLAG_RELIABLE; - prudpPacket* packet = new prudpPacket(&streamSettings, vport_src, vport_dst, prudpPacket::TYPE_DATA, flags, clientSessionId, outgoingSequenceId, 0); - if(reliable) - outgoingSequenceId++; + prudpPacket* packet = new prudpPacket(&m_streamSettings, m_srcVPort, m_dstVPort, prudpPacket::TYPE_DATA, flags, m_clientSessionId, m_outgoingReliableSequenceId, 0); + if (reliable) + m_outgoingReliableSequenceId++; packet->setFragmentIndex(0); packet->setData(input, length); - queuePacket(packet, dstIp, dstPort); + QueuePacket(packet); } -uint16 prudpClient::getSourcePort() +sint32 prudpClient::ReceiveDatagram(std::vector<uint8>& outputBuffer) { - return this->srcPort; -} - -SOCKET prudpClient::getSocket() -{ - if (currentConnectionState == STATE_DISCONNECTED) - { - return INVALID_SOCKET; - } - return this->socketUdp; -} - -sint32 prudpClient::receiveDatagram(std::vector<uint8>& outputBuffer) -{ - if (queue_incomingPackets.empty()) + outputBuffer.clear(); + if (m_incomingPacketQueue.empty()) return -1; - prudpIncomingPacket* incomingPacket = queue_incomingPackets[0]; - if (incomingPacket->sequenceId != this->incomingSequenceId) + prudpIncomingPacket* frontPacket = m_incomingPacketQueue[0].get(); + if (frontPacket->sequenceId != this->m_incomingSequenceId) return -1; - - if (incomingPacket->fragmentIndex == 0) + if (frontPacket->fragmentIndex == 0) { // single-fragment packet // decrypt - incomingPacket->decrypt(); + frontPacket->decrypt(); // read data - sint32 datagramLen = (sint32)incomingPacket->packetData.size(); - if (datagramLen > 0) + if (!frontPacket->packetData.empty()) { - // resize buffer if necessary - if (datagramLen > outputBuffer.size()) - outputBuffer.resize(datagramLen); - // to conserve memory we will also shrink the buffer if it was previously extended beyond 64KB - constexpr size_t BUFFER_TARGET_SIZE = 1024 * 64; - if (datagramLen < BUFFER_TARGET_SIZE && outputBuffer.size() > BUFFER_TARGET_SIZE) + // to conserve memory we will also shrink the buffer if it was previously extended beyond 32KB + constexpr size_t BUFFER_TARGET_SIZE = 1024 * 32; + if (frontPacket->packetData.size() < BUFFER_TARGET_SIZE && outputBuffer.capacity() > BUFFER_TARGET_SIZE) + { outputBuffer.resize(BUFFER_TARGET_SIZE); - // copy datagram to buffer - memcpy(outputBuffer.data(), &incomingPacket->packetData.front(), datagramLen); + outputBuffer.shrink_to_fit(); + outputBuffer.clear(); + } + // write packet data to output buffer + cemu_assert_debug(outputBuffer.empty()); + outputBuffer.insert(outputBuffer.end(), frontPacket->packetData.begin(), frontPacket->packetData.end()); } - delete incomingPacket; - // remove packet from queue - queue_incomingPackets.erase(queue_incomingPackets.begin()); + m_incomingPacketQueue.erase(m_incomingPacketQueue.begin()); // advance expected sequence id - this->incomingSequenceId++; - return datagramLen; + this->m_incomingSequenceId++; + return (sint32)outputBuffer.size(); } else { // multi-fragment packet - if (incomingPacket->fragmentIndex != 1) + if (frontPacket->fragmentIndex != 1) return -1; // first packet of the chain not received yet // verify chain sint32 packetIndex = 1; sint32 chainLength = -1; // if full chain found, set to count of packets - for(sint32 i=1; i<queue_incomingPackets.size(); i++) + for (sint32 i = 1; i < m_incomingPacketQueue.size(); i++) { - uint8 itFragmentIndex = queue_incomingPackets[packetIndex]->fragmentIndex; + uint8 itFragmentIndex = m_incomingPacketQueue[packetIndex]->fragmentIndex; // sequence id must increase by 1 for every packet - if (queue_incomingPackets[packetIndex]->sequenceId != (this->incomingSequenceId+i) ) + if (m_incomingPacketQueue[packetIndex]->sequenceId != (m_incomingSequenceId + i)) return -1; // missing packets // last fragment in chain is marked by fragment index 0 if (itFragmentIndex == 0) { - chainLength = i+1; + chainLength = i + 1; break; } packetIndex++; @@ -1011,29 +1005,17 @@ sint32 prudpClient::receiveDatagram(std::vector<uint8>& outputBuffer) if (chainLength < 1) return -1; // chain not complete // extract data from packet chain - sint32 writeIndex = 0; + cemu_assert_debug(outputBuffer.empty()); for (sint32 i = 0; i < chainLength; i++) { - incomingPacket = queue_incomingPackets[i]; - // decrypt + prudpIncomingPacket* incomingPacket = m_incomingPacketQueue[i].get(); incomingPacket->decrypt(); - // extract data - sint32 datagramLen = (sint32)incomingPacket->packetData.size(); - if (datagramLen > 0) - { - // make sure output buffer can fit the data - if ((writeIndex + datagramLen) > outputBuffer.size()) - outputBuffer.resize(writeIndex + datagramLen + 4 * 1024); - memcpy(outputBuffer.data()+writeIndex, &incomingPacket->packetData.front(), datagramLen); - writeIndex += datagramLen; - } - // free packet memory - delete incomingPacket; + outputBuffer.insert(outputBuffer.end(), incomingPacket->packetData.begin(), incomingPacket->packetData.end()); } // remove packets from queue - queue_incomingPackets.erase(queue_incomingPackets.begin(), queue_incomingPackets.begin() + chainLength); - this->incomingSequenceId += chainLength; - return writeIndex; + m_incomingPacketQueue.erase(m_incomingPacketQueue.begin(), m_incomingPacketQueue.begin() + chainLength); + m_incomingSequenceId += chainLength; + return (sint32)outputBuffer.size(); } return -1; } diff --git a/src/Cemu/nex/prudp.h b/src/Cemu/nex/prudp.h index 5ed5bcb1..3192c833 100644 --- a/src/Cemu/nex/prudp.h +++ b/src/Cemu/nex/prudp.h @@ -4,26 +4,26 @@ #define RC4_N 256 -typedef struct +struct RC4Ctx { unsigned char S[RC4_N]; int i; int j; -}RC4Ctx_t; +}; -void RC4_initCtx(RC4Ctx_t* rc4Ctx, char *key); -void RC4_initCtx(RC4Ctx_t* rc4Ctx, unsigned char* key, int keyLen); -void RC4_transform(RC4Ctx_t* rc4Ctx, unsigned char* input, int len, unsigned char* output); +void RC4_initCtx(RC4Ctx* rc4Ctx, const char* key); +void RC4_initCtx(RC4Ctx* rc4Ctx, unsigned char* key, int keyLen); +void RC4_transform(RC4Ctx* rc4Ctx, unsigned char* input, int len, unsigned char* output); -typedef struct +struct prudpStreamSettings { uint8 checksumBase; // calculated from key uint8 accessKeyDigest[16]; // MD5 hash of key - RC4Ctx_t rc4Client; - RC4Ctx_t rc4Server; -}prudpStreamSettings_t; + RC4Ctx rc4Client; + RC4Ctx rc4Server; +}; -typedef struct +struct prudpStationUrl { uint32 ip; uint16 port; @@ -32,19 +32,17 @@ typedef struct sint32 sid; sint32 stream; sint32 type; -}stationUrl_t; +}; -typedef struct +struct prudpAuthServerInfo { uint32 userPid; uint8 secureKey[16]; uint8 kerberosKey[16]; uint8 secureTicket[1024]; sint32 secureTicketLength; - stationUrl_t server; -}authServerInfo_t; - -uint8 prudp_calculateChecksum(uint8 checksumBase, uint8* data, sint32 length); + prudpStationUrl server; +}; class prudpPacket { @@ -66,7 +64,7 @@ public: static sint32 calculateSizeFromPacketData(uint8* data, sint32 length); - prudpPacket(prudpStreamSettings_t* streamSettings, uint8 src, uint8 dst, uint8 type, uint16 flags, uint8 sessionId, uint16 sequenceId, uint32 packetSignature); + prudpPacket(prudpStreamSettings* streamSettings, uint8 src, uint8 dst, uint8 type, uint16 flags, uint8 sessionId, uint16 sequenceId, uint32 packetSignature); bool requiresAck(); void setData(uint8* data, sint32 length); void setFragmentIndex(uint8 fragmentIndex); @@ -87,7 +85,7 @@ private: uint16 flags; uint8 sessionId; uint32 specifiedPacketSignature; - prudpStreamSettings_t* streamSettings; + prudpStreamSettings* streamSettings; std::vector<uint8> packetData; bool isEncrypted; uint16 m_sequenceId{0}; @@ -97,7 +95,7 @@ private: class prudpIncomingPacket { public: - prudpIncomingPacket(prudpStreamSettings_t* streamSettings, uint8* data, sint32 length); + prudpIncomingPacket(prudpStreamSettings* streamSettings, uint8* data, sint32 length); bool hasError(); @@ -122,83 +120,91 @@ public: private: bool isInvalid = false; - prudpStreamSettings_t* streamSettings = nullptr; - + prudpStreamSettings* streamSettings = nullptr; }; -typedef struct -{ - prudpPacket* packet; - uint32 initialSendTimestamp; - uint32 lastRetryTimestamp; - sint32 retryCount; -}prudpAckRequired_t; - class prudpClient { + struct PacketWithAckRequired + { + PacketWithAckRequired(prudpPacket* packet, uint32 initialSendTimestamp) : + packet(packet), initialSendTimestamp(initialSendTimestamp), lastRetryTimestamp(initialSendTimestamp) { } + prudpPacket* packet; + uint32 initialSendTimestamp; + uint32 lastRetryTimestamp; + sint32 retryCount{0}; + }; public: - static const int STATE_CONNECTING = 0; - static const int STATE_CONNECTED = 1; - static const int STATE_DISCONNECTED = 2; + enum class ConnectionState : uint8 + { + Connecting, + Connected, + Disconnected + }; -public: prudpClient(uint32 dstIp, uint16 dstPort, const char* key); - prudpClient(uint32 dstIp, uint16 dstPort, const char* key, authServerInfo_t* authInfo); + prudpClient(uint32 dstIp, uint16 dstPort, const char* key, prudpAuthServerInfo* authInfo); ~prudpClient(); - bool isConnected(); + bool IsConnected() const { return m_currentConnectionState == ConnectionState::Connected; } + ConnectionState GetConnectionState() const { return m_currentConnectionState; } + uint16 GetSourcePort() const { return m_srcPort; } - uint8 getConnectionState(); - void acknowledgePacket(uint16 sequenceId); - void sortIncomingDataPacket(prudpIncomingPacket* incomingPacket); - void handleIncomingPacket(prudpIncomingPacket* incomingPacket); - bool update(); // check for new incoming packets, returns true if receiveDatagram() should be called + bool Update(); // update connection state and check for incoming packets. Returns true if ReceiveDatagram() should be called - sint32 receiveDatagram(std::vector<uint8>& outputBuffer); - void sendDatagram(uint8* input, sint32 length, bool reliable = true); - - uint16 getSourcePort(); - - SOCKET getSocket(); + sint32 ReceiveDatagram(std::vector<uint8>& outputBuffer); + void SendDatagram(uint8* input, sint32 length, bool reliable = true); private: prudpClient(); - void directSendPacket(prudpPacket* packet, uint32 dstIp, uint16 dstPort); - sint32 kerberosEncryptData(uint8* input, sint32 length, uint8* output); - void queuePacket(prudpPacket* packet, uint32 dstIp, uint16 dstPort); + + void HandleIncomingPacket(std::unique_ptr<prudpIncomingPacket> incomingPacket); + void DirectSendPacket(prudpPacket* packet); + sint32 KerberosEncryptData(uint8* input, sint32 length, uint8* output); + void QueuePacket(prudpPacket* packet); + + void AcknowledgePacket(uint16 sequenceId); + void SortIncomingDataPacket(std::unique_ptr<prudpIncomingPacket> incomingPacket); + + void SendCurrentHandshakePacket(); private: - uint16 srcPort; - uint32 dstIp; - uint16 dstPort; - uint8 vport_src; - uint8 vport_dst; - prudpStreamSettings_t streamSettings; - std::vector<prudpAckRequired_t> list_packetsWithAckReq; - std::vector<prudpIncomingPacket*> queue_incomingPackets; - - // connection - uint8 currentConnectionState; - uint32 serverConnectionSignature; - uint32 clientConnectionSignature; - bool hasSentCon; - uint32 lastPingTimestamp; + uint16 m_srcPort; + uint32 m_dstIp; + uint16 m_dstPort; + uint8 m_srcVPort; + uint8 m_dstVPort; + prudpStreamSettings m_streamSettings; + std::vector<PacketWithAckRequired> m_dataPacketsWithAckReq; + std::vector<std::unique_ptr<prudpIncomingPacket>> m_incomingPacketQueue; - uint16 outgoingSequenceId; - uint16 incomingSequenceId; + // connection handshake state + bool m_hasSynAck{false}; + bool m_hasConAck{false}; + uint32 m_lastHandshakeTimestamp{0}; + uint8 m_handshakeRetryCount{0}; + + // connection + ConnectionState m_currentConnectionState; + uint32 m_serverConnectionSignature; + uint32 m_clientConnectionSignature; + uint32 m_lastPingTimestamp; + + uint16 m_outgoingReliableSequenceId{2}; // 1 is reserved for CON + uint16 m_incomingSequenceId; uint16 m_outgoingSequenceId_ping{0}; uint8 m_unacknowledgedPingCount{0}; - uint8 clientSessionId; - uint8 serverSessionId; + uint8 m_clientSessionId; + uint8 m_serverSessionId; // secure - bool isSecureConnection; - authServerInfo_t authInfo; + bool m_isSecureConnection{false}; + prudpAuthServerInfo m_authInfo; // socket - SOCKET socketUdp; + SOCKET m_socketUdp; }; uint32 prudpGetMSTimestamp(); \ No newline at end of file From 989e2b8c8c14f2cebf86d97eeca5bf7877989c96 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Thu, 18 Apr 2024 23:11:19 +0200 Subject: [PATCH 082/299] prudp: More code cleanup + fix compile error --- src/Cemu/nex/prudp.cpp | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/Cemu/nex/prudp.cpp b/src/Cemu/nex/prudp.cpp index 5c773fe7..771fe097 100644 --- a/src/Cemu/nex/prudp.cpp +++ b/src/Cemu/nex/prudp.cpp @@ -288,7 +288,7 @@ uint32 prudpPacket::packetSignature() return specifiedPacketSignature; else if (type == TYPE_DATA) { - if (packetData.size() == 0) + if (packetData.empty()) return 0x12345678; HMACMD5Ctx ctx; @@ -307,8 +307,7 @@ uint32 prudpPacket::packetSignature() void prudpPacket::setData(uint8* data, sint32 length) { - packetData.resize(length); - memcpy(&packetData.front(), data, length); + packetData.assign(data, data + length); } void prudpPacket::setFragmentIndex(uint8 fragmentIndex) @@ -509,12 +508,12 @@ prudpClient::prudpClient(uint32 dstIp, uint16 dstPort, const char* key) u_long nonBlockingMode = 1; // 1 to enable non-blocking socket ioctlsocket(m_socketUdp, FIONBIO, &nonBlockingMode); #else - int flags = fcntl(socketUdp, F_GETFL); - fcntl(socketUdp, F_SETFL, flags | O_NONBLOCK); + int flags = fcntl(m_socketUdp, F_GETFL); + fcntl(m_socketUdp, F_SETFL, flags | O_NONBLOCK); #endif // generate frequently used parameters - this->m_srcVPort = PRUDP_VPORT(prudpPacket::STREAM_TYPE_SECURE, 0xF); - this->m_dstVPort = PRUDP_VPORT(prudpPacket::STREAM_TYPE_SECURE, 0x1); + m_srcVPort = PRUDP_VPORT(prudpPacket::STREAM_TYPE_SECURE, 0xF); + m_dstVPort = PRUDP_VPORT(prudpPacket::STREAM_TYPE_SECURE, 0x1); // set stream settings uint8 checksumBase = 0; for (sint32 i = 0; key[i] != '\0'; i++) @@ -540,8 +539,8 @@ prudpClient::prudpClient(uint32 dstIp, uint16 dstPort, const char* key, prudpAut { RC4_initCtx(&m_streamSettings.rc4Server, authInfo->secureKey, 16); RC4_initCtx(&m_streamSettings.rc4Client, authInfo->secureKey, 16); - this->m_isSecureConnection = true; - memcpy(&this->m_authInfo, authInfo, sizeof(prudpAuthServerInfo)); + m_isSecureConnection = true; + memcpy(&m_authInfo, authInfo, sizeof(prudpAuthServerInfo)); } prudpClient::~prudpClient() @@ -601,7 +600,7 @@ void prudpClient::SortIncomingDataPacket(std::unique_ptr<prudpIncomingPacket> in sint32 prudpClient::KerberosEncryptData(uint8* input, sint32 length, uint8* output) { RC4Ctx rc4Kerberos; - RC4_initCtx(&rc4Kerberos, this->m_authInfo.secureKey, 16); + RC4_initCtx(&rc4Kerberos, m_authInfo.secureKey, 16); memcpy(output, input, length); RC4_transform(&rc4Kerberos, output, length, output); // calculate and append hmac @@ -627,7 +626,7 @@ void prudpClient::SendCurrentHandshakePacket() { uint8 tempBuffer[512]; nexPacketBuffer conData(tempBuffer, sizeof(tempBuffer), true); - conData.writeU32(this->m_clientConnectionSignature); + conData.writeU32(m_clientConnectionSignature); conData.writeBuffer(m_authInfo.secureTicket, m_authInfo.secureTicketLength); // encrypted request data uint8 requestData[4 * 3]; @@ -641,7 +640,7 @@ void prudpClient::SendCurrentHandshakePacket() } else { - conPacket.setData((uint8*)&this->m_clientConnectionSignature, sizeof(uint32)); + conPacket.setData((uint8*)&m_clientConnectionSignature, sizeof(uint32)); } DirectSendPacket(&conPacket); } @@ -889,7 +888,7 @@ bool prudpClient::Update() cemuLog_log(LogType::PRUDP, "[PRUDP] Sending new ping packet with sequenceId {}", this->m_outgoingSequenceId_ping + 1); // start a new ping packet with a new sequenceId. Note that ping packets have their own sequenceId and acknowledgement happens by manually comparing the incoming ping ACK against the last sent sequenceId // only one unacknowledged ping packet can be in flight at a time. We will resend the same ping packet until we receive an ack - this->m_outgoingSequenceId_ping++; // increment before sending. The first ping has a sequenceId of 1 + m_outgoingSequenceId_ping++; // increment before sending. The first ping has a sequenceId of 1 prudpPacket pingPacket(&m_streamSettings, m_srcVPort, m_dstVPort, prudpPacket::TYPE_PING, prudpPacket::FLAG_NEED_ACK, this->m_clientSessionId, this->m_outgoingSequenceId_ping, m_serverConnectionSignature); DirectSendPacket(&pingPacket); m_unacknowledgedPingCount++; From efbbb817fe1cbe09ee132344b44a0f61f8b8ac96 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 20 Apr 2024 12:19:06 +0200 Subject: [PATCH 083/299] DownloadManager: Always use Nintendo servers + additional streamlining - Download manager now always uses Nintendo servers. Requires only a valid OTP and SEEPROM dump so you can use it in combination with a Pretendo setup even without a NNID - Account drop down removed from download manager since it's not required - Internally all our API requests now support overriding which service to use - Drop support for act-url and ecs-url command line parameters. Usage of network_services.xml ("custom" option in the UI) is preferred --- src/Cafe/IOSU/legacy/iosu_boss.cpp | 2 +- src/Cafe/IOSU/legacy/iosu_crypto.cpp | 10 - src/Cafe/IOSU/legacy/iosu_crypto.h | 1 - src/Cafe/IOSU/legacy/iosu_nim.cpp | 2 +- src/Cafe/OS/libs/nn_idbe/nn_idbe.cpp | 2 +- .../nn_olv/nn_olv_DownloadCommunityTypes.cpp | 2 +- .../OS/libs/nn_olv/nn_olv_InitializeTypes.cpp | 2 +- .../nn_olv/nn_olv_UploadCommunityTypes.cpp | 2 +- .../nn_olv/nn_olv_UploadFavoriteTypes.cpp | 2 +- .../Tools/DownloadManager/DownloadManager.cpp | 130 ++++------ .../Tools/DownloadManager/DownloadManager.h | 16 +- src/Cemu/napi/napi.h | 23 +- src/Cemu/napi/napi_act.cpp | 49 ++-- src/Cemu/napi/napi_ec.cpp | 229 +++++++++--------- src/Cemu/napi/napi_helper.cpp | 24 +- src/Cemu/napi/napi_helper.h | 4 +- src/Cemu/napi/napi_idbe.cpp | 10 +- src/Cemu/napi/napi_version.cpp | 6 +- src/Cemu/ncrypto/ncrypto.cpp | 18 +- src/Cemu/ncrypto/ncrypto.h | 4 + src/config/ActiveSettings.cpp | 1 - src/config/LaunchSettings.cpp | 37 +-- src/config/LaunchSettings.h | 10 - src/config/NetworkSettings.cpp | 3 - src/config/NetworkSettings.h | 27 ++- src/gui/CemuApp.cpp | 2 +- src/gui/GeneralSettings2.cpp | 8 +- src/gui/TitleManager.cpp | 33 ++- src/gui/TitleManager.h | 2 + 29 files changed, 323 insertions(+), 338 deletions(-) diff --git a/src/Cafe/IOSU/legacy/iosu_boss.cpp b/src/Cafe/IOSU/legacy/iosu_boss.cpp index c2c1eb51..760e5b66 100644 --- a/src/Cafe/IOSU/legacy/iosu_boss.cpp +++ b/src/Cafe/IOSU/legacy/iosu_boss.cpp @@ -498,7 +498,7 @@ namespace iosu curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, task_header_callback); curl_easy_setopt(curl, CURLOPT_HEADERDATA, &(*it)); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 0x3C); - if (GetNetworkConfig().disablesslver.GetValue() && ActiveSettings::GetNetworkService() == NetworkService::Custom || ActiveSettings::GetNetworkService() == NetworkService::Pretendo) // remove Pretendo Function once SSL is in the Service + if (IsNetworkServiceSSLDisabled(ActiveSettings::GetNetworkService())) { curl_easy_setopt(curl,CURLOPT_SSL_VERIFYPEER,0L); } diff --git a/src/Cafe/IOSU/legacy/iosu_crypto.cpp b/src/Cafe/IOSU/legacy/iosu_crypto.cpp index 80eb2f01..a4f75430 100644 --- a/src/Cafe/IOSU/legacy/iosu_crypto.cpp +++ b/src/Cafe/IOSU/legacy/iosu_crypto.cpp @@ -292,16 +292,6 @@ void iosuCrypto_generateDeviceCertificate() BN_CTX_free(context); } -bool iosuCrypto_hasAllDataForLogin() -{ - if (hasOtpMem == false) - return false; - if (hasSeepromMem == false) - return false; - // todo - check if certificates are available - return true; -} - sint32 iosuCrypto_getDeviceCertificateBase64Encoded(char* output) { iosuCrypto_base64Encode((uint8*)&g_wiiuDeviceCert, sizeof(g_wiiuDeviceCert), output); diff --git a/src/Cafe/IOSU/legacy/iosu_crypto.h b/src/Cafe/IOSU/legacy/iosu_crypto.h index 9f1429c7..d4fc49b9 100644 --- a/src/Cafe/IOSU/legacy/iosu_crypto.h +++ b/src/Cafe/IOSU/legacy/iosu_crypto.h @@ -2,7 +2,6 @@ void iosuCrypto_init(); -bool iosuCrypto_hasAllDataForLogin(); bool iosuCrypto_getDeviceId(uint32* deviceId); void iosuCrypto_getDeviceSerialString(char* serialString); diff --git a/src/Cafe/IOSU/legacy/iosu_nim.cpp b/src/Cafe/IOSU/legacy/iosu_nim.cpp index e7cf97ef..b529640d 100644 --- a/src/Cafe/IOSU/legacy/iosu_nim.cpp +++ b/src/Cafe/IOSU/legacy/iosu_nim.cpp @@ -228,7 +228,7 @@ namespace iosu } } - auto result = NAPI::IDBE_Request(titleId); + auto result = NAPI::IDBE_Request(ActiveSettings::GetNetworkService(), titleId); if (!result) { memset(idbeIconOutput, 0, sizeof(NAPI::IDBEIconDataV0)); diff --git a/src/Cafe/OS/libs/nn_idbe/nn_idbe.cpp b/src/Cafe/OS/libs/nn_idbe/nn_idbe.cpp index a78494cf..a69f32a3 100644 --- a/src/Cafe/OS/libs/nn_idbe/nn_idbe.cpp +++ b/src/Cafe/OS/libs/nn_idbe/nn_idbe.cpp @@ -42,7 +42,7 @@ namespace nn void asyncDownloadIconFile(uint64 titleId, nnIdbeEncryptedIcon_t* iconOut, OSThread_t* thread) { - std::vector<uint8> idbeData = NAPI::IDBE_RequestRawEncrypted(titleId); + std::vector<uint8> idbeData = NAPI::IDBE_RequestRawEncrypted(ActiveSettings::GetNetworkService(), titleId); if (idbeData.size() != sizeof(nnIdbeEncryptedIcon_t)) { // icon does not exist or has the wrong size diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.cpp index 1bf2b37d..db1885af 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.cpp +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.cpp @@ -43,7 +43,7 @@ namespace nn return res; CurlRequestHelper req; - req.initate(reqUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::OLIVE); + req.initate(ActiveSettings::GetNetworkService(), reqUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::OLIVE); InitializeOliveRequest(req); StackAllocator<coreinit::OSEvent> requestDoneEvent; diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_InitializeTypes.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv_InitializeTypes.cpp index 5e6dba7e..ba657ff7 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv_InitializeTypes.cpp +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_InitializeTypes.cpp @@ -195,7 +195,7 @@ namespace nn break; } - req.initate(requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::OLIVE); + req.initate(ActiveSettings::GetNetworkService(), requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::OLIVE); InitializeOliveRequest(req); StackAllocator<coreinit::OSEvent> requestDoneEvent; diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.cpp index 179d66bd..6f3c43b9 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.cpp +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.cpp @@ -50,7 +50,7 @@ namespace nn CurlRequestHelper req; - req.initate(requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::OLIVE); + req.initate(ActiveSettings::GetNetworkService(), requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::OLIVE); InitializeOliveRequest(req); StackAllocator<coreinit::OSEvent> requestDoneEvent; diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.cpp index 307004b9..1e2d40ab 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.cpp +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.cpp @@ -40,7 +40,7 @@ namespace nn snprintf(requestUrl, sizeof(requestUrl), "%s/v1/communities/%lu.favorite", g_DiscoveryResults.apiEndpoint, pParam->communityId.value()); CurlRequestHelper req; - req.initate(requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::OLIVE); + req.initate(ActiveSettings::GetNetworkService(), requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::OLIVE); InitializeOliveRequest(req); StackAllocator<coreinit::OSEvent> requestDoneEvent; diff --git a/src/Cemu/Tools/DownloadManager/DownloadManager.cpp b/src/Cemu/Tools/DownloadManager/DownloadManager.cpp index 807a4e72..9e683ed1 100644 --- a/src/Cemu/Tools/DownloadManager/DownloadManager.cpp +++ b/src/Cemu/Tools/DownloadManager/DownloadManager.cpp @@ -33,14 +33,7 @@ void DownloadManager::downloadTitleVersionList() { if (m_hasTitleVersionList) return; - NAPI::AuthInfo authInfo; - authInfo.accountId = m_authInfo.nnidAccountName; - authInfo.passwordHash = m_authInfo.passwordHash; - authInfo.deviceId = m_authInfo.deviceId; - authInfo.serial = m_authInfo.serial; - authInfo.country = m_authInfo.country; - authInfo.region = m_authInfo.region; - authInfo.deviceCertBase64 = m_authInfo.deviceCertBase64; + NAPI::AuthInfo authInfo = GetAuthInfo(false); auto versionListVersionResult = NAPI::TAG_GetVersionListVersion(authInfo); if (!versionListVersionResult.isValid) return; @@ -195,15 +188,7 @@ public: bool DownloadManager::_connect_refreshIASAccountIdAndDeviceToken() { - NAPI::AuthInfo authInfo; - authInfo.accountId = m_authInfo.nnidAccountName; - authInfo.passwordHash = m_authInfo.passwordHash; - authInfo.deviceId = m_authInfo.deviceId; - authInfo.serial = m_authInfo.serial; - authInfo.country = m_authInfo.country; - authInfo.region = m_authInfo.region; - authInfo.deviceCertBase64 = m_authInfo.deviceCertBase64; - + NAPI::AuthInfo authInfo = GetAuthInfo(false); // query IAS/ECS account id and device token (if not cached) auto rChallenge = NAPI::IAS_GetChallenge(authInfo); if (rChallenge.apiError != NAPI_RESULT::SUCCESS) @@ -211,7 +196,6 @@ bool DownloadManager::_connect_refreshIASAccountIdAndDeviceToken() auto rRegistrationInfo = NAPI::IAS_GetRegistrationInfo_QueryInfo(authInfo, rChallenge.challenge); if (rRegistrationInfo.apiError != NAPI_RESULT::SUCCESS) return false; - m_iasToken.serviceAccountId = rRegistrationInfo.accountId; m_iasToken.deviceToken = rRegistrationInfo.deviceToken; // store to cache @@ -221,24 +205,13 @@ bool DownloadManager::_connect_refreshIASAccountIdAndDeviceToken() std::vector<uint8> serializedData; if (!storedTokenInfo.serialize(serializedData)) return false; - s_nupFileCache->AddFileAsync({ fmt::format("{}/token_info", m_authInfo.nnidAccountName) }, serializedData.data(), serializedData.size()); + s_nupFileCache->AddFileAsync({ fmt::format("{}/token_info", m_authInfo.cachefileName) }, serializedData.data(), serializedData.size()); return true; } bool DownloadManager::_connect_queryAccountStatusAndServiceURLs() { - NAPI::AuthInfo authInfo; - authInfo.accountId = m_authInfo.nnidAccountName; - authInfo.passwordHash = m_authInfo.passwordHash; - authInfo.deviceId = m_authInfo.deviceId; - authInfo.serial = m_authInfo.serial; - authInfo.country = m_authInfo.country; - authInfo.region = m_authInfo.region; - authInfo.deviceCertBase64 = m_authInfo.deviceCertBase64; - - authInfo.IASToken.accountId = m_iasToken.serviceAccountId; - authInfo.IASToken.deviceToken = m_iasToken.deviceToken; - + NAPI::AuthInfo authInfo = GetAuthInfo(true); NAPI::NAPI_ECSGetAccountStatus_Result accountStatusResult = NAPI::ECS_GetAccountStatus(authInfo); if (accountStatusResult.apiError != NAPI_RESULT::SUCCESS) { @@ -291,7 +264,7 @@ void DownloadManager::loadTicketCache() m_ticketCache.clear(); cemu_assert_debug(m_ticketCache.empty()); std::vector<uint8> ticketCacheBlob; - if (!s_nupFileCache->GetFile({ fmt::format("{}/eticket_cache", m_authInfo.nnidAccountName) }, ticketCacheBlob)) + if (!s_nupFileCache->GetFile({ fmt::format("{}/eticket_cache", m_authInfo.cachefileName) }, ticketCacheBlob)) return; MemStreamReader memReader(ticketCacheBlob.data(), ticketCacheBlob.size()); uint8 version = memReader.readBE<uint8>(); @@ -343,23 +316,12 @@ void DownloadManager::storeTicketCache() memWriter.writePODVector(cert); } auto serializedBlob = memWriter.getResult(); - s_nupFileCache->AddFileAsync({ fmt::format("{}/eticket_cache", m_authInfo.nnidAccountName) }, serializedBlob.data(), serializedBlob.size()); + s_nupFileCache->AddFileAsync({ fmt::format("{}/eticket_cache", m_authInfo.cachefileName) }, serializedBlob.data(), serializedBlob.size()); } bool DownloadManager::syncAccountTickets() { - NAPI::AuthInfo authInfo; - authInfo.accountId = m_authInfo.nnidAccountName; - authInfo.passwordHash = m_authInfo.passwordHash; - authInfo.deviceId = m_authInfo.deviceId; - authInfo.serial = m_authInfo.serial; - authInfo.country = m_authInfo.country; - authInfo.region = m_authInfo.region; - authInfo.deviceCertBase64 = m_authInfo.deviceCertBase64; - - authInfo.IASToken.accountId = m_iasToken.serviceAccountId; - authInfo.IASToken.deviceToken = m_iasToken.deviceToken; - + NAPI::AuthInfo authInfo = GetAuthInfo(true); // query TIV list from server NAPI::NAPI_ECSAccountListETicketIds_Result resultTicketIds = NAPI::ECS_AccountListETicketIds(authInfo); if (!resultTicketIds.isValid()) @@ -425,19 +387,7 @@ bool DownloadManager::syncAccountTickets() bool DownloadManager::syncSystemTitleTickets() { setStatusMessage(_("Downloading system tickets...").utf8_string(), DLMGR_STATUS_CODE::CONNECTING); - // todo - add GetAuth() function - NAPI::AuthInfo authInfo; - authInfo.accountId = m_authInfo.nnidAccountName; - authInfo.passwordHash = m_authInfo.passwordHash; - authInfo.deviceId = m_authInfo.deviceId; - authInfo.serial = m_authInfo.serial; - authInfo.country = m_authInfo.country; - authInfo.region = m_authInfo.region; - authInfo.deviceCertBase64 = m_authInfo.deviceCertBase64; - - authInfo.IASToken.accountId = m_iasToken.serviceAccountId; - authInfo.IASToken.deviceToken = m_iasToken.deviceToken; - + NAPI::AuthInfo authInfo = GetAuthInfo(true); auto querySystemTitleTicket = [&](uint64 titleId) -> void { // check if cached already @@ -520,8 +470,7 @@ bool DownloadManager::syncUpdateTickets() if (findTicketByTitleIdAndVersion(itr.titleId, itr.availableTitleVersion)) continue; - NAPI::AuthInfo dummyAuth; - auto cetkResult = NAPI::CCS_GetCETK(dummyAuth, itr.titleId, itr.availableTitleVersion); + auto cetkResult = NAPI::CCS_GetCETK(GetDownloadMgrNetworkService(), itr.titleId, itr.availableTitleVersion); if (!cetkResult.isValid) continue; NCrypto::ETicketParser ticketParser; @@ -657,7 +606,7 @@ void DownloadManager::_handle_connect() if (s_nupFileCache) { std::vector<uint8> serializationBlob; - if (s_nupFileCache->GetFile({ fmt::format("{}/token_info", m_authInfo.nnidAccountName) }, serializationBlob)) + if (s_nupFileCache->GetFile({ fmt::format("{}/token_info", m_authInfo.cachefileName) }, serializationBlob)) { StoredTokenInfo storedTokenInfo; if (storedTokenInfo.deserialize(serializationBlob)) @@ -683,7 +632,7 @@ void DownloadManager::_handle_connect() if (!_connect_queryAccountStatusAndServiceURLs()) { m_connectState.store(CONNECT_STATE::FAILED); - setStatusMessage(_("Failed to query account status. Invalid account information?").utf8_string(), DLMGR_STATUS_CODE::FAILED); + setStatusMessage(_("Failed to query account status").utf8_string(), DLMGR_STATUS_CODE::FAILED); return; } // load ticket cache and sync @@ -692,7 +641,7 @@ void DownloadManager::_handle_connect() if (!syncTicketCache()) { m_connectState.store(CONNECT_STATE::FAILED); - setStatusMessage(_("Failed to request tickets (invalid NNID?)").utf8_string(), DLMGR_STATUS_CODE::FAILED); + setStatusMessage(_("Failed to request tickets").utf8_string(), DLMGR_STATUS_CODE::FAILED); return; } searchForIncompleteDownloads(); @@ -713,22 +662,10 @@ void DownloadManager::connect( std::string_view serial, std::string_view deviceCertBase64) { - if (nnidAccountName.empty()) - { - m_connectState.store(CONNECT_STATE::FAILED); - setStatusMessage(_("This account is not linked with an NNID").utf8_string(), DLMGR_STATUS_CODE::FAILED); - return; - } runManager(); m_authInfo.nnidAccountName = nnidAccountName; m_authInfo.passwordHash = passwordHash; - if (std::all_of(m_authInfo.passwordHash.begin(), m_authInfo.passwordHash.end(), [](uint8 v) { return v == 0; })) - { - cemuLog_log(LogType::Force, "DLMgr: Invalid password hash"); - m_connectState.store(CONNECT_STATE::FAILED); - setStatusMessage(_("Failed. Account does not have password set").utf8_string(), DLMGR_STATUS_CODE::FAILED); - return; - } + m_authInfo.cachefileName = nnidAccountName.empty() ? "DefaultName" : nnidAccountName; m_authInfo.region = region; m_authInfo.country = country; m_authInfo.deviceCertBase64 = deviceCertBase64; @@ -744,6 +681,31 @@ bool DownloadManager::IsConnected() const return m_connectState.load() != CONNECT_STATE::UNINITIALIZED; } +NetworkService DownloadManager::GetDownloadMgrNetworkService() +{ + return NetworkService::Nintendo; +} + +NAPI::AuthInfo DownloadManager::GetAuthInfo(bool withIasToken) +{ + NAPI::AuthInfo authInfo; + authInfo.serviceOverwrite = GetDownloadMgrNetworkService(); + authInfo.accountId = m_authInfo.nnidAccountName; + authInfo.passwordHash = m_authInfo.passwordHash; + authInfo.deviceId = m_authInfo.deviceId; + authInfo.serial = m_authInfo.serial; + authInfo.country = m_authInfo.country; + authInfo.region = m_authInfo.region; + authInfo.deviceCertBase64 = m_authInfo.deviceCertBase64; + if(withIasToken) + { + cemu_assert_debug(!m_iasToken.serviceAccountId.empty()); + authInfo.IASToken.accountId = m_iasToken.serviceAccountId; + authInfo.IASToken.deviceToken = m_iasToken.deviceToken; + } + return authInfo; +} + /* package / downloading */ // start/resume/retry download @@ -1022,17 +984,7 @@ void DownloadManager::reportPackageProgress(Package* package, uint32 currentProg void DownloadManager::asyncPackageDownloadTMD(Package* package) { - NAPI::AuthInfo authInfo; - authInfo.accountId = m_authInfo.nnidAccountName; - authInfo.passwordHash = m_authInfo.passwordHash; - authInfo.deviceId = m_authInfo.deviceId; - authInfo.serial = m_authInfo.serial; - authInfo.country = m_authInfo.country; - authInfo.region = m_authInfo.region; - authInfo.deviceCertBase64 = m_authInfo.deviceCertBase64; - authInfo.IASToken.accountId = m_iasToken.serviceAccountId; - authInfo.IASToken.deviceToken = m_iasToken.deviceToken; - + NAPI::AuthInfo authInfo = GetAuthInfo(true); TitleIdParser titleIdParser(package->titleId); NAPI::NAPI_CCSGetTMD_Result tmdResult; if (titleIdParser.GetType() == TitleIdParser::TITLE_TYPE::AOC) @@ -1196,7 +1148,7 @@ void DownloadManager::asyncPackageDownloadContentFile(Package* package, uint16 i setPackageError(package, _("Cannot create file").utf8_string()); return; } - if (!NAPI::CCS_GetContentFile(titleId, contentId, CallbackInfo::writeCallback, &callbackInfoData)) + if (!NAPI::CCS_GetContentFile(GetDownloadMgrNetworkService(), titleId, contentId, CallbackInfo::writeCallback, &callbackInfoData)) { setPackageError(package, _("Download failed").utf8_string()); delete callbackInfoData.fileOutput; @@ -1490,7 +1442,7 @@ void DownloadManager::prepareIDBE(uint64 titleId) if (s_nupFileCache->GetFile({ fmt::format("idbe/{0:016x}", titleId) }, idbeFile) && idbeFile.size() == sizeof(NAPI::IDBEIconDataV0)) return addToCache(titleId, (NAPI::IDBEIconDataV0*)(idbeFile.data())); // not cached, query from server - std::optional<NAPI::IDBEIconDataV0> iconData = NAPI::IDBE_Request(titleId); + std::optional<NAPI::IDBEIconDataV0> iconData = NAPI::IDBE_Request(GetDownloadMgrNetworkService(), titleId); if (!iconData) return; s_nupFileCache->AddFileAsync({ fmt::format("idbe/{0:016x}", titleId) }, (uint8*)&(*iconData), sizeof(NAPI::IDBEIconDataV0)); diff --git a/src/Cemu/Tools/DownloadManager/DownloadManager.h b/src/Cemu/Tools/DownloadManager/DownloadManager.h index 1693318c..8f693a3e 100644 --- a/src/Cemu/Tools/DownloadManager/DownloadManager.h +++ b/src/Cemu/Tools/DownloadManager/DownloadManager.h @@ -2,17 +2,14 @@ #include "util/helpers/Semaphore.h" #include "Cemu/ncrypto/ncrypto.h" #include "Cafe/TitleList/TitleId.h" - #include "util/helpers/ConcurrentQueue.h" +#include "config/NetworkSettings.h" -#include <functional> -#include <optional> - -#include <future> - +// forward declarations namespace NAPI { struct IDBEIconDataV0; + struct AuthInfo; } namespace NCrypto @@ -86,7 +83,6 @@ public: bool IsConnected() const; - private: /* connect / login */ @@ -101,6 +97,7 @@ private: struct { + std::string cachefileName; std::string nnidAccountName; std::array<uint8, 32> passwordHash; std::string deviceCertBase64; @@ -122,7 +119,10 @@ private: void _handle_connect(); bool _connect_refreshIASAccountIdAndDeviceToken(); bool _connect_queryAccountStatusAndServiceURLs(); - + + NetworkService GetDownloadMgrNetworkService(); + NAPI::AuthInfo GetAuthInfo(bool withIasToken); + /* idbe cache */ public: void prepareIDBE(uint64 titleId); diff --git a/src/Cemu/napi/napi.h b/src/Cemu/napi/napi.h index ab17a7b3..e1397d62 100644 --- a/src/Cemu/napi/napi.h +++ b/src/Cemu/napi/napi.h @@ -1,6 +1,7 @@ #pragma once -#include <optional> #include "config/CemuConfig.h" // for ConsoleLanguage +#include "config/NetworkSettings.h" // for NetworkService +#include "config/ActiveSettings.h" // for GetNetworkService() enum class NAPI_RESULT { @@ -16,8 +17,6 @@ namespace NAPI // common auth info structure shared by ACT, ECS and IAS service struct AuthInfo { - // todo - constructor for account name + raw password - // nnid std::string accountId; std::array<uint8, 32> passwordHash; @@ -41,9 +40,13 @@ namespace NAPI std::string deviceToken; }IASToken; - // ACT token (for account.nintendo.net requests) - + // service selection, if not set fall back to global setting + std::optional<NetworkService> serviceOverwrite; + NetworkService GetService() const + { + return serviceOverwrite.value_or(ActiveSettings::GetNetworkService()); + } }; bool NAPI_MakeAuthInfoFromCurrentAccount(AuthInfo& authInfo); // helper function. Returns false if online credentials/dumped files are not available @@ -232,9 +235,9 @@ namespace NAPI NAPI_CCSGetTMD_Result CCS_GetTMD(AuthInfo& authInfo, uint64 titleId, uint16 titleVersion); NAPI_CCSGetTMD_Result CCS_GetTMD(AuthInfo& authInfo, uint64 titleId); - NAPI_CCSGetETicket_Result CCS_GetCETK(AuthInfo& authInfo, uint64 titleId, uint16 titleVersion); - bool CCS_GetContentFile(uint64 titleId, uint32 contentId, bool(*cbWriteCallback)(void* userData, const void* ptr, size_t len, bool isLast), void* userData); - NAPI_CCSGetContentH3_Result CCS_GetContentH3File(uint64 titleId, uint32 contentId); + NAPI_CCSGetETicket_Result CCS_GetCETK(NetworkService service, uint64 titleId, uint16 titleVersion); + bool CCS_GetContentFile(NetworkService service, uint64 titleId, uint32 contentId, bool(*cbWriteCallback)(void* userData, const void* ptr, size_t len, bool isLast), void* userData); + NAPI_CCSGetContentH3_Result CCS_GetContentH3File(NetworkService service, uint64 titleId, uint32 contentId); /* IDBE */ @@ -286,8 +289,8 @@ namespace NAPI static_assert(sizeof(IDBEHeader) == 2+32); - std::optional<IDBEIconDataV0> IDBE_Request(uint64 titleId); - std::vector<uint8> IDBE_RequestRawEncrypted(uint64 titleId); // same as IDBE_Request but doesn't strip the header and decrypt the IDBE + std::optional<IDBEIconDataV0> IDBE_Request(NetworkService networkService, uint64 titleId); + std::vector<uint8> IDBE_RequestRawEncrypted(NetworkService networkService, uint64 titleId); // same as IDBE_Request but doesn't strip the header and decrypt the IDBE /* Version list */ diff --git a/src/Cemu/napi/napi_act.cpp b/src/Cemu/napi/napi_act.cpp index 9716c41e..c72d9f47 100644 --- a/src/Cemu/napi/napi_act.cpp +++ b/src/Cemu/napi/napi_act.cpp @@ -14,6 +14,21 @@ namespace NAPI { + std::string _getACTUrl(NetworkService service) + { + switch (service) + { + case NetworkService::Nintendo: + return NintendoURLs::ACTURL; + case NetworkService::Pretendo: + return PretendoURLs::ACTURL; + case NetworkService::Custom: + return GetNetworkConfig().urls.ACT.GetValue(); + default: + return NintendoURLs::ACTURL; + } + } + struct ACTOauthToken : public _NAPI_CommonResultACT { std::string token; @@ -91,7 +106,7 @@ namespace NAPI struct OAuthTokenCacheEntry { - OAuthTokenCacheEntry(std::string_view accountId, std::array<uint8, 32>& passwordHash, std::string_view token, std::string_view refreshToken, uint64 expiresIn) : accountId(accountId), passwordHash(passwordHash), token(token), refreshToken(refreshToken) + OAuthTokenCacheEntry(std::string_view accountId, std::array<uint8, 32>& passwordHash, std::string_view token, std::string_view refreshToken, uint64 expiresIn, NetworkService service) : accountId(accountId), passwordHash(passwordHash), token(token), refreshToken(refreshToken), service(service) { expires = HighResolutionTimer::now().getTickInSeconds() + expiresIn; }; @@ -107,10 +122,10 @@ namespace NAPI } std::string accountId; std::array<uint8, 32> passwordHash; - std::string token; std::string refreshToken; uint64 expires; + NetworkService service; }; std::vector<OAuthTokenCacheEntry> g_oauthTokenCache; @@ -122,11 +137,12 @@ namespace NAPI ACTOauthToken result{}; // check cache first + NetworkService service = authInfo.GetService(); g_oauthTokenCacheMtx.lock(); auto cacheItr = g_oauthTokenCache.begin(); while (cacheItr != g_oauthTokenCache.end()) { - if (cacheItr->CheckIfSameAccount(authInfo)) + if (cacheItr->CheckIfSameAccount(authInfo) && cacheItr->service == service) { if (cacheItr->CheckIfExpired()) { @@ -145,7 +161,7 @@ namespace NAPI // token not cached, request from server via oauth2 CurlRequestHelper req; - req.initate(fmt::format("{}/v1/api/oauth20/access_token/generate", LaunchSettings::GetActURLPrefix()), CurlRequestHelper::SERVER_SSL_CONTEXT::ACT); + req.initate(authInfo.GetService(), fmt::format("{}/v1/api/oauth20/access_token/generate", _getACTUrl(authInfo.GetService())), CurlRequestHelper::SERVER_SSL_CONTEXT::ACT); _ACTSetCommonHeaderParameters(req, authInfo); _ACTSetDeviceParameters(req, authInfo); _ACTSetRegionAndCountryParameters(req, authInfo); @@ -220,7 +236,7 @@ namespace NAPI if (expiration > 0) { g_oauthTokenCacheMtx.lock(); - g_oauthTokenCache.emplace_back(authInfo.accountId, authInfo.passwordHash, result.token, result.refreshToken, expiration); + g_oauthTokenCache.emplace_back(authInfo.accountId, authInfo.passwordHash, result.token, result.refreshToken, expiration, service); g_oauthTokenCacheMtx.unlock(); } return result; @@ -230,14 +246,13 @@ namespace NAPI { CurlRequestHelper req; - req.initate(fmt::format("{}/v1/api/people/@me/profile", LaunchSettings::GetActURLPrefix()), CurlRequestHelper::SERVER_SSL_CONTEXT::ACT); + req.initate(authInfo.GetService(), fmt::format("{}/v1/api/people/@me/profile", _getACTUrl(authInfo.GetService())), CurlRequestHelper::SERVER_SSL_CONTEXT::ACT); _ACTSetCommonHeaderParameters(req, authInfo); _ACTSetDeviceParameters(req, authInfo); // get oauth2 token ACTOauthToken oauthToken = ACT_GetOauthToken_WithCache(authInfo, 0x0005001010001C00, 0x0001C); - cemu_assert_unimplemented(); return true; @@ -245,15 +260,16 @@ namespace NAPI struct NexTokenCacheEntry { - NexTokenCacheEntry(std::string_view accountId, std::array<uint8, 32>& passwordHash, uint32 gameServerId, ACTNexToken& nexToken) : accountId(accountId), passwordHash(passwordHash), nexToken(nexToken), gameServerId(gameServerId) {}; + NexTokenCacheEntry(std::string_view accountId, std::array<uint8, 32>& passwordHash, NetworkService networkService, uint32 gameServerId, ACTNexToken& nexToken) : accountId(accountId), passwordHash(passwordHash), networkService(networkService), nexToken(nexToken), gameServerId(gameServerId) {}; bool IsMatch(const AuthInfo& authInfo, const uint32 gameServerId) const { - return authInfo.accountId == accountId && authInfo.passwordHash == passwordHash && this->gameServerId == gameServerId; + return authInfo.accountId == accountId && authInfo.passwordHash == passwordHash && authInfo.GetService() == networkService && this->gameServerId == gameServerId; } std::string accountId; std::array<uint8, 32> passwordHash; + NetworkService networkService; uint32 gameServerId; ACTNexToken nexToken; @@ -297,7 +313,7 @@ namespace NAPI } // do request CurlRequestHelper req; - req.initate(fmt::format("{}/v1/api/provider/nex_token/@me?game_server_id={:08X}", LaunchSettings::GetActURLPrefix(), serverId), CurlRequestHelper::SERVER_SSL_CONTEXT::ACT); + req.initate(authInfo.GetService(), fmt::format("{}/v1/api/provider/nex_token/@me?game_server_id={:08X}", _getACTUrl(authInfo.GetService()), serverId), CurlRequestHelper::SERVER_SSL_CONTEXT::ACT); _ACTSetCommonHeaderParameters(req, authInfo); _ACTSetDeviceParameters(req, authInfo); _ACTSetRegionAndCountryParameters(req, authInfo); @@ -374,21 +390,21 @@ namespace NAPI result.nexToken.port = (uint16)StringHelpers::ToInt(port); result.apiError = NAPI_RESULT::SUCCESS; g_nexTokenCacheMtx.lock(); - g_nexTokenCache.emplace_back(authInfo.accountId, authInfo.passwordHash, serverId, result.nexToken); + g_nexTokenCache.emplace_back(authInfo.accountId, authInfo.passwordHash, authInfo.GetService(), serverId, result.nexToken); g_nexTokenCacheMtx.unlock(); return result; } struct IndependentTokenCacheEntry { - IndependentTokenCacheEntry(std::string_view accountId, std::array<uint8, 32>& passwordHash, std::string_view clientId, std::string_view independentToken, sint64 expiresIn) : accountId(accountId), passwordHash(passwordHash), clientId(clientId), independentToken(independentToken) + IndependentTokenCacheEntry(std::string_view accountId, std::array<uint8, 32>& passwordHash, NetworkService networkService, std::string_view clientId, std::string_view independentToken, sint64 expiresIn) : accountId(accountId), passwordHash(passwordHash), networkService(networkService), clientId(clientId), independentToken(independentToken) { expires = HighResolutionTimer::now().getTickInSeconds() + expiresIn; }; bool IsMatch(const AuthInfo& authInfo, const std::string_view clientId) const { - return authInfo.accountId == accountId && authInfo.passwordHash == passwordHash && this->clientId == clientId; + return authInfo.accountId == accountId && authInfo.passwordHash == passwordHash && authInfo.GetService() == networkService && this->clientId == clientId; } bool CheckIfExpired() const @@ -398,6 +414,7 @@ namespace NAPI std::string accountId; std::array<uint8, 32> passwordHash; + NetworkService networkService; std::string clientId; sint64 expires; @@ -449,7 +466,7 @@ namespace NAPI } // do request CurlRequestHelper req; - req.initate(fmt::format("{}/v1/api/provider/service_token/@me?client_id={}", LaunchSettings::GetActURLPrefix(), clientId), CurlRequestHelper::SERVER_SSL_CONTEXT::ACT); + req.initate(authInfo.GetService(), fmt::format("{}/v1/api/provider/service_token/@me?client_id={}", _getACTUrl(authInfo.GetService()), clientId), CurlRequestHelper::SERVER_SSL_CONTEXT::ACT); _ACTSetCommonHeaderParameters(req, authInfo); _ACTSetDeviceParameters(req, authInfo); _ACTSetRegionAndCountryParameters(req, authInfo); @@ -494,7 +511,7 @@ namespace NAPI result.apiError = NAPI_RESULT::SUCCESS; g_IndependentTokenCacheMtx.lock(); - g_IndependentTokenCache.emplace_back(authInfo.accountId, authInfo.passwordHash, clientId, result.token, 3600); + g_IndependentTokenCache.emplace_back(authInfo.accountId, authInfo.passwordHash, authInfo.GetService(), clientId, result.token, 3600); g_IndependentTokenCacheMtx.unlock(); return result; } @@ -520,7 +537,7 @@ namespace NAPI } // do request CurlRequestHelper req; - req.initate(fmt::format("{}/v1/api/admin/mapped_ids?input_type=user_id&output_type=pid&input={}", LaunchSettings::GetActURLPrefix(), nnid), CurlRequestHelper::SERVER_SSL_CONTEXT::ACT); + req.initate(authInfo.GetService(), fmt::format("{}/v1/api/admin/mapped_ids?input_type=user_id&output_type=pid&input={}", _getACTUrl(authInfo.GetService()), nnid), CurlRequestHelper::SERVER_SSL_CONTEXT::ACT); _ACTSetCommonHeaderParameters(req, authInfo); _ACTSetDeviceParameters(req, authInfo); _ACTSetRegionAndCountryParameters(req, authInfo); diff --git a/src/Cemu/napi/napi_ec.cpp b/src/Cemu/napi/napi_ec.cpp index 9bc4bfbf..2c0812e4 100644 --- a/src/Cemu/napi/napi_ec.cpp +++ b/src/Cemu/napi/napi_ec.cpp @@ -16,103 +16,112 @@ namespace NAPI { /* Service URL manager */ - std::string s_serviceURL_ContentPrefixURL; - std::string s_serviceURL_UncachedContentPrefixURL; - std::string s_serviceURL_EcsURL; - std::string s_serviceURL_IasURL; - std::string s_serviceURL_CasURL; - std::string s_serviceURL_NusURL; - - std::string _getNUSUrl() + struct CachedServiceUrls { - if (!s_serviceURL_NusURL.empty()) - return s_serviceURL_NusURL; - switch (ActiveSettings::GetNetworkService()) - { - case NetworkService::Nintendo: - return NintendoURLs::NUSURL; - break; - case NetworkService::Pretendo: - return PretendoURLs::NUSURL; - break; - case NetworkService::Custom: - return GetNetworkConfig().urls.NUS; - break; - default: - return NintendoURLs::NUSURL; - break; - } + std::string s_serviceURL_ContentPrefixURL; + std::string s_serviceURL_UncachedContentPrefixURL; + std::string s_serviceURL_EcsURL; + std::string s_serviceURL_IasURL; + std::string s_serviceURL_CasURL; + std::string s_serviceURL_NusURL; + }; + + std::unordered_map<NetworkService, CachedServiceUrls> s_cachedServiceUrlsMap; + + CachedServiceUrls& GetCachedServiceUrls(NetworkService service) + { + return s_cachedServiceUrlsMap[service]; } - std::string _getIASUrl() + std::string _getNUSUrl(NetworkService service) { - if (!s_serviceURL_IasURL.empty()) - return s_serviceURL_IasURL; - switch (ActiveSettings::GetNetworkService()) - { - case NetworkService::Nintendo: - return NintendoURLs::IASURL; - break; - case NetworkService::Pretendo: - return PretendoURLs::IASURL; - break; - case NetworkService::Custom: - return GetNetworkConfig().urls.IAS; - break; - default: - return NintendoURLs::IASURL; - break; - } + auto& cachedServiceUrls = GetCachedServiceUrls(service); + if (!cachedServiceUrls.s_serviceURL_NusURL.empty()) + return cachedServiceUrls.s_serviceURL_NusURL; + switch (service) + { + case NetworkService::Nintendo: + return NintendoURLs::NUSURL; + case NetworkService::Pretendo: + return PretendoURLs::NUSURL; + case NetworkService::Custom: + return GetNetworkConfig().urls.NUS; + default: + return NintendoURLs::NUSURL; + } } - std::string _getECSUrl() + std::string _getIASUrl(NetworkService service) + { + auto& cachedServiceUrls = GetCachedServiceUrls(service); + if (!cachedServiceUrls.s_serviceURL_IasURL.empty()) + return cachedServiceUrls.s_serviceURL_IasURL; + switch (service) + { + case NetworkService::Nintendo: + return NintendoURLs::IASURL; + case NetworkService::Pretendo: + return PretendoURLs::IASURL; + case NetworkService::Custom: + return GetNetworkConfig().urls.IAS; + default: + return NintendoURLs::IASURL; + } + } + + std::string _getECSUrl(NetworkService service) { // this is the first url queried (GetAccountStatus). The others are dynamically set if provided by the server but will fallback to hardcoded defaults otherwise - if (!s_serviceURL_EcsURL.empty()) - return s_serviceURL_EcsURL; - return LaunchSettings::GetServiceURL_ecs(); // by default this is "https://ecs.wup.shop.nintendo.net/ecs/services/ECommerceSOAP" + auto& cachedServiceUrls = GetCachedServiceUrls(service); + if (!cachedServiceUrls.s_serviceURL_EcsURL.empty()) + return cachedServiceUrls.s_serviceURL_EcsURL; + switch (service) + { + case NetworkService::Nintendo: + return NintendoURLs::ECSURL; + case NetworkService::Pretendo: + return PretendoURLs::ECSURL; + case NetworkService::Custom: + return GetNetworkConfig().urls.ECS; + default: + return NintendoURLs::ECSURL; + } } - std::string _getCCSUncachedUrl() // used for TMD requests + std::string _getCCSUncachedUrl(NetworkService service) // used for TMD requests { - if (!s_serviceURL_UncachedContentPrefixURL.empty()) - return s_serviceURL_UncachedContentPrefixURL; - switch (ActiveSettings::GetNetworkService()) - { - case NetworkService::Nintendo: - return NintendoURLs::CCSUURL; - break; - case NetworkService::Pretendo: - return PretendoURLs::CCSUURL; - break; - case NetworkService::Custom: - return GetNetworkConfig().urls.CCSU; - break; - default: - return NintendoURLs::CCSUURL; - break; - } + auto& cachedServiceUrls = GetCachedServiceUrls(service); + if (!cachedServiceUrls.s_serviceURL_UncachedContentPrefixURL.empty()) + return cachedServiceUrls.s_serviceURL_UncachedContentPrefixURL; + switch (service) + { + case NetworkService::Nintendo: + return NintendoURLs::CCSUURL; + case NetworkService::Pretendo: + return PretendoURLs::CCSUURL; + case NetworkService::Custom: + return GetNetworkConfig().urls.CCSU; + default: + return NintendoURLs::CCSUURL; + } } - std::string _getCCSUrl() // used for game data downloads + std::string _getCCSUrl(NetworkService service) // used for game data downloads { - if (!s_serviceURL_ContentPrefixURL.empty()) - return s_serviceURL_ContentPrefixURL; - switch (ActiveSettings::GetNetworkService()) - { - case NetworkService::Nintendo: - return NintendoURLs::CCSURL; - break; - case NetworkService::Pretendo: - return PretendoURLs::CCSURL; - break; - case NetworkService::Custom: - return GetNetworkConfig().urls.CCS; - break; - default: - return NintendoURLs::CCSURL; - break; - } + auto& cachedServiceUrls = GetCachedServiceUrls(service); + if (!cachedServiceUrls.s_serviceURL_ContentPrefixURL.empty()) + return cachedServiceUrls.s_serviceURL_ContentPrefixURL; + switch (service) + { + case NetworkService::Nintendo: + return NintendoURLs::CCSURL; + case NetworkService::Pretendo: + return PretendoURLs::CCSURL; + case NetworkService::Custom: + return GetNetworkConfig().urls.CCS; + default: + return NintendoURLs::CCSURL; + } } /* NUS */ @@ -122,8 +131,8 @@ namespace NAPI { NAPI_NUSGetSystemCommonETicket_Result result{}; - CurlSOAPHelper soapHelper; - soapHelper.SOAP_initate("nus", _getNUSUrl(), "GetSystemCommonETicket", "1.0"); + CurlSOAPHelper soapHelper(authInfo.GetService()); + soapHelper.SOAP_initate("nus", _getNUSUrl(authInfo.GetService()), "GetSystemCommonETicket", "1.0"); soapHelper.SOAP_addRequestField("DeviceId", fmt::format("{}", authInfo.getDeviceIdWithPlatform())); soapHelper.SOAP_addRequestField("RegionId", NCrypto::GetRegionAsString(authInfo.region)); @@ -175,8 +184,8 @@ namespace NAPI { NAPI_IASGetChallenge_Result result{}; - CurlSOAPHelper soapHelper; - soapHelper.SOAP_initate("ias", _getIASUrl(), "GetChallenge", "2.0"); + CurlSOAPHelper soapHelper(authInfo.GetService()); + soapHelper.SOAP_initate("ias", _getIASUrl(authInfo.GetService()), "GetChallenge", "2.0"); soapHelper.SOAP_addRequestField("DeviceId", fmt::format("{}", authInfo.getDeviceIdWithPlatform())); // not validated but the generated Challenge is bound to this DeviceId soapHelper.SOAP_addRequestField("Region", NCrypto::GetRegionAsString(authInfo.region)); @@ -200,8 +209,8 @@ namespace NAPI NAPI_IASGetRegistrationInfo_Result IAS_GetRegistrationInfo_QueryInfo(AuthInfo& authInfo, std::string challenge) { NAPI_IASGetRegistrationInfo_Result result; - CurlSOAPHelper soapHelper; - soapHelper.SOAP_initate("ias", _getIASUrl(), "GetRegistrationInfo", "2.0"); + CurlSOAPHelper soapHelper(authInfo.GetService()); + soapHelper.SOAP_initate("ias", _getIASUrl(authInfo.GetService()), "GetRegistrationInfo", "2.0"); soapHelper.SOAP_addRequestField("DeviceId", fmt::format("{}", authInfo.getDeviceIdWithPlatform())); // this must match the DeviceId used to generate Challenge soapHelper.SOAP_addRequestField("Region", NCrypto::GetRegionAsString(authInfo.region)); @@ -301,8 +310,8 @@ namespace NAPI { NAPI_ECSGetAccountStatus_Result result{}; - CurlSOAPHelper soapHelper; - soapHelper.SOAP_initate("ecs", _getECSUrl(), "GetAccountStatus", "2.0"); + CurlSOAPHelper soapHelper(authInfo.GetService()); + soapHelper.SOAP_initate("ecs", _getECSUrl(authInfo.GetService()), "GetAccountStatus", "2.0"); soapHelper.SOAP_addRequestField("DeviceId", fmt::format("{}", authInfo.getDeviceIdWithPlatform())); soapHelper.SOAP_addRequestField("Region", NCrypto::GetRegionAsString(authInfo.region)); @@ -367,19 +376,19 @@ namespace NAPI } // assign service URLs + auto& cachedServiceUrls = GetCachedServiceUrls(authInfo.GetService()); if (!result.serviceURLs.ContentPrefixURL.empty()) - s_serviceURL_ContentPrefixURL = result.serviceURLs.ContentPrefixURL; + cachedServiceUrls.s_serviceURL_ContentPrefixURL = result.serviceURLs.ContentPrefixURL; if (!result.serviceURLs.UncachedContentPrefixURL.empty()) - s_serviceURL_UncachedContentPrefixURL = result.serviceURLs.UncachedContentPrefixURL; + cachedServiceUrls.s_serviceURL_UncachedContentPrefixURL = result.serviceURLs.UncachedContentPrefixURL; if (!result.serviceURLs.IasURL.empty()) - s_serviceURL_IasURL = result.serviceURLs.IasURL; + cachedServiceUrls.s_serviceURL_IasURL = result.serviceURLs.IasURL; if (!result.serviceURLs.CasURL.empty()) - s_serviceURL_CasURL = result.serviceURLs.CasURL; + cachedServiceUrls.s_serviceURL_CasURL = result.serviceURLs.CasURL; if (!result.serviceURLs.NusURL.empty()) - s_serviceURL_NusURL = result.serviceURLs.NusURL; + cachedServiceUrls.s_serviceURL_NusURL = result.serviceURLs.NusURL; if (!result.serviceURLs.EcsURL.empty()) - s_serviceURL_EcsURL = result.serviceURLs.EcsURL; - + cachedServiceUrls.s_serviceURL_EcsURL = result.serviceURLs.EcsURL; return result; } @@ -387,8 +396,8 @@ namespace NAPI { NAPI_ECSAccountListETicketIds_Result result{}; - CurlSOAPHelper soapHelper; - soapHelper.SOAP_initate("ecs", _getECSUrl(), "AccountListETicketIds", "2.0"); + CurlSOAPHelper soapHelper(authInfo.GetService()); + soapHelper.SOAP_initate("ecs", _getECSUrl(authInfo.GetService()), "AccountListETicketIds", "2.0"); soapHelper.SOAP_addRequestField("DeviceId", fmt::format("{}", authInfo.getDeviceIdWithPlatform())); soapHelper.SOAP_addRequestField("Region", NCrypto::GetRegionAsString(authInfo.region)); @@ -446,8 +455,8 @@ namespace NAPI { NAPI_ECSAccountGetETickets_Result result{}; - CurlSOAPHelper soapHelper; - soapHelper.SOAP_initate("ecs", _getECSUrl(), "AccountGetETickets", "2.0"); + CurlSOAPHelper soapHelper(authInfo.GetService()); + soapHelper.SOAP_initate("ecs", _getECSUrl(authInfo.GetService()), "AccountGetETickets", "2.0"); soapHelper.SOAP_addRequestField("DeviceId", fmt::format("{}", authInfo.getDeviceIdWithPlatform())); soapHelper.SOAP_addRequestField("Region", NCrypto::GetRegionAsString(authInfo.region)); @@ -512,7 +521,7 @@ namespace NAPI { NAPI_CCSGetTMD_Result result{}; CurlRequestHelper req; - req.initate(fmt::format("{}/{:016x}/tmd.{}?deviceId={}&accountId={}", _getCCSUncachedUrl(), titleId, titleVersion, authInfo.getDeviceIdWithPlatform(), authInfo.IASToken.accountId), CurlRequestHelper::SERVER_SSL_CONTEXT::CCS); + req.initate(authInfo.GetService(), fmt::format("{}/{:016x}/tmd.{}?deviceId={}&accountId={}", _getCCSUncachedUrl(authInfo.GetService()), titleId, titleVersion, authInfo.getDeviceIdWithPlatform(), authInfo.IASToken.accountId), CurlRequestHelper::SERVER_SSL_CONTEXT::CCS); req.setTimeout(180); if (!req.submitRequest(false)) { @@ -528,7 +537,7 @@ namespace NAPI { NAPI_CCSGetTMD_Result result{}; CurlRequestHelper req; - req.initate(fmt::format("{}/{:016x}/tmd?deviceId={}&accountId={}", _getCCSUncachedUrl(), titleId, authInfo.getDeviceIdWithPlatform(), authInfo.IASToken.accountId), CurlRequestHelper::SERVER_SSL_CONTEXT::CCS); + req.initate(authInfo.GetService(), fmt::format("{}/{:016x}/tmd?deviceId={}&accountId={}", _getCCSUncachedUrl(authInfo.GetService()), titleId, authInfo.getDeviceIdWithPlatform(), authInfo.IASToken.accountId), CurlRequestHelper::SERVER_SSL_CONTEXT::CCS); req.setTimeout(180); if (!req.submitRequest(false)) { @@ -540,11 +549,11 @@ namespace NAPI return result; } - NAPI_CCSGetETicket_Result CCS_GetCETK(AuthInfo& authInfo, uint64 titleId, uint16 titleVersion) + NAPI_CCSGetETicket_Result CCS_GetCETK(NetworkService service, uint64 titleId, uint16 titleVersion) { NAPI_CCSGetETicket_Result result{}; CurlRequestHelper req; - req.initate(fmt::format("{}/{:016x}/cetk", _getCCSUncachedUrl(), titleId), CurlRequestHelper::SERVER_SSL_CONTEXT::CCS); + req.initate(service, fmt::format("{}/{:016x}/cetk", _getCCSUncachedUrl(service), titleId), CurlRequestHelper::SERVER_SSL_CONTEXT::CCS); req.setTimeout(180); if (!req.submitRequest(false)) { @@ -556,10 +565,10 @@ namespace NAPI return result; } - bool CCS_GetContentFile(uint64 titleId, uint32 contentId, bool(*cbWriteCallback)(void* userData, const void* ptr, size_t len, bool isLast), void* userData) + bool CCS_GetContentFile(NetworkService service, uint64 titleId, uint32 contentId, bool(*cbWriteCallback)(void* userData, const void* ptr, size_t len, bool isLast), void* userData) { CurlRequestHelper req; - req.initate(fmt::format("{}/{:016x}/{:08x}", _getCCSUrl(), titleId, contentId), CurlRequestHelper::SERVER_SSL_CONTEXT::CCS); + req.initate(service, fmt::format("{}/{:016x}/{:08x}", _getCCSUrl(service), titleId, contentId), CurlRequestHelper::SERVER_SSL_CONTEXT::CCS); req.setWriteCallback(cbWriteCallback, userData); req.setTimeout(0); if (!req.submitRequest(false)) @@ -570,11 +579,11 @@ namespace NAPI return true; } - NAPI_CCSGetContentH3_Result CCS_GetContentH3File(uint64 titleId, uint32 contentId) + NAPI_CCSGetContentH3_Result CCS_GetContentH3File(NetworkService service, uint64 titleId, uint32 contentId) { NAPI_CCSGetContentH3_Result result{}; CurlRequestHelper req; - req.initate(fmt::format("{}/{:016x}/{:08x}.h3", _getCCSUrl(), titleId, contentId), CurlRequestHelper::SERVER_SSL_CONTEXT::CCS); + req.initate(service, fmt::format("{}/{:016x}/{:08x}.h3", _getCCSUrl(service), titleId, contentId), CurlRequestHelper::SERVER_SSL_CONTEXT::CCS); if (!req.submitRequest(false)) { cemuLog_log(LogType::Force, fmt::format("Failed to request content hash file {:08x}.h3 for title {:016X}", contentId, titleId)); diff --git a/src/Cemu/napi/napi_helper.cpp b/src/Cemu/napi/napi_helper.cpp index 776baf33..e498d07f 100644 --- a/src/Cemu/napi/napi_helper.cpp +++ b/src/Cemu/napi/napi_helper.cpp @@ -119,7 +119,7 @@ CurlRequestHelper::~CurlRequestHelper() curl_easy_cleanup(m_curl); } -void CurlRequestHelper::initate(std::string url, SERVER_SSL_CONTEXT sslContext) +void CurlRequestHelper::initate(NetworkService service, std::string url, SERVER_SSL_CONTEXT sslContext) { // reset parameters m_headerExtraFields.clear(); @@ -131,8 +131,10 @@ void CurlRequestHelper::initate(std::string url, SERVER_SSL_CONTEXT sslContext) curl_easy_setopt(m_curl, CURLOPT_TIMEOUT, 60); // SSL - if (GetNetworkConfig().disablesslver.GetValue() && ActiveSettings::GetNetworkService() == NetworkService::Custom || ActiveSettings::GetNetworkService() == NetworkService::Pretendo){ //Remove once Pretendo has SSL - curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYPEER, 1L); + if (IsNetworkServiceSSLDisabled(service)) + { + curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYPEER, 0L); } else if (sslContext == SERVER_SSL_CONTEXT::ACT || sslContext == SERVER_SSL_CONTEXT::TAGAYA) { @@ -256,18 +258,24 @@ bool CurlRequestHelper::submitRequest(bool isPost) return true; } -CurlSOAPHelper::CurlSOAPHelper() +CurlSOAPHelper::CurlSOAPHelper(NetworkService service) { m_curl = curl_easy_init(); curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, __curlWriteCallback); curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, this); // SSL - if (!GetNetworkConfig().disablesslver.GetValue() && ActiveSettings::GetNetworkService() != NetworkService::Pretendo && ActiveSettings::GetNetworkService() != NetworkService::Custom) { //Remove once Pretendo has SSL - curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_FUNCTION, _sslctx_function_SOAP); - curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_DATA, NULL); + if (!IsNetworkServiceSSLDisabled(service)) + { + curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_FUNCTION, _sslctx_function_SOAP); + curl_easy_setopt(m_curl, CURLOPT_SSL_CTX_DATA, NULL); + curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYPEER, 1L); } - if(GetConfig().proxy_server.GetValue() != "") + else + { + curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYPEER, 0L); + } + if (GetConfig().proxy_server.GetValue() != "") { curl_easy_setopt(m_curl, CURLOPT_PROXY, GetConfig().proxy_server.GetValue().c_str()); } diff --git a/src/Cemu/napi/napi_helper.h b/src/Cemu/napi/napi_helper.h index 6403f074..adfe7393 100644 --- a/src/Cemu/napi/napi_helper.h +++ b/src/Cemu/napi/napi_helper.h @@ -38,7 +38,7 @@ public: return m_curl; } - void initate(std::string url, SERVER_SSL_CONTEXT sslContext); + void initate(NetworkService service, std::string url, SERVER_SSL_CONTEXT sslContext); void addHeaderField(const char* fieldName, std::string_view value); void addPostField(const char* fieldName, std::string_view value); void setWriteCallback(bool(*cbWriteCallback)(void* userData, const void* ptr, size_t len, bool isLast), void* userData); @@ -74,7 +74,7 @@ private: class CurlSOAPHelper // todo - make this use CurlRequestHelper { public: - CurlSOAPHelper(); + CurlSOAPHelper(NetworkService service); ~CurlSOAPHelper(); CURL* getCURL() diff --git a/src/Cemu/napi/napi_idbe.cpp b/src/Cemu/napi/napi_idbe.cpp index acf7799c..db7fda20 100644 --- a/src/Cemu/napi/napi_idbe.cpp +++ b/src/Cemu/napi/napi_idbe.cpp @@ -54,11 +54,11 @@ namespace NAPI AES128_CBC_decrypt((uint8*)iconData, (uint8*)iconData, sizeof(IDBEIconDataV0), aesKey, iv); } - std::vector<uint8> IDBE_RequestRawEncrypted(uint64 titleId) + std::vector<uint8> IDBE_RequestRawEncrypted(NetworkService networkService, uint64 titleId) { CurlRequestHelper req; std::string requestUrl; - switch (ActiveSettings::GetNetworkService()) + switch (networkService) { case NetworkService::Pretendo: requestUrl = PretendoURLs::IDBEURL; @@ -72,7 +72,7 @@ namespace NAPI break; } requestUrl.append(fmt::format(fmt::runtime("/{0:02X}/{1:016X}.idbe"), (uint32)((titleId >> 8) & 0xFF), titleId)); - req.initate(requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::IDBE); + req.initate(networkService, requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::IDBE); if (!req.submitRequest(false)) { @@ -90,7 +90,7 @@ namespace NAPI return receivedData; } - std::optional<IDBEIconDataV0> IDBE_Request(uint64 titleId) + std::optional<IDBEIconDataV0> IDBE_Request(NetworkService networkService, uint64 titleId) { if (titleId == 0x000500301001500A || titleId == 0x000500301001510A || @@ -101,7 +101,7 @@ namespace NAPI return std::nullopt; } - std::vector<uint8> idbeData = IDBE_RequestRawEncrypted(titleId); + std::vector<uint8> idbeData = IDBE_RequestRawEncrypted(networkService, titleId); if (idbeData.size() < 0x22) return std::nullopt; if (idbeData[0] != 0) diff --git a/src/Cemu/napi/napi_version.cpp b/src/Cemu/napi/napi_version.cpp index 9fc71556..a1f5879c 100644 --- a/src/Cemu/napi/napi_version.cpp +++ b/src/Cemu/napi/napi_version.cpp @@ -18,7 +18,7 @@ namespace NAPI CurlRequestHelper req; std::string requestUrl; - switch (ActiveSettings::GetNetworkService()) + switch (authInfo.GetService()) { case NetworkService::Pretendo: requestUrl = PretendoURLs::TAGAYAURL; @@ -32,7 +32,7 @@ namespace NAPI break; } requestUrl.append(fmt::format(fmt::runtime("/{}/{}/latest_version"), NCrypto::GetRegionAsString(authInfo.region), authInfo.country)); - req.initate(requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::TAGAYA); + req.initate(authInfo.GetService(), requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::TAGAYA); if (!req.submitRequest(false)) { @@ -63,7 +63,7 @@ namespace NAPI { NAPI_VersionList_Result result; CurlRequestHelper req; - req.initate(fmt::format("https://{}/tagaya/versionlist/{}/{}/list/{}.versionlist", fqdnURL, NCrypto::GetRegionAsString(authInfo.region), authInfo.country, versionListVersion), CurlRequestHelper::SERVER_SSL_CONTEXT::TAGAYA); + req.initate(authInfo.GetService(), fmt::format("https://{}/tagaya/versionlist/{}/{}/list/{}.versionlist", fqdnURL, NCrypto::GetRegionAsString(authInfo.region), authInfo.country, versionListVersion), CurlRequestHelper::SERVER_SSL_CONTEXT::TAGAYA); if (!req.submitRequest(false)) { cemuLog_log(LogType::Force, fmt::format("Failed to request update list")); diff --git a/src/Cemu/ncrypto/ncrypto.cpp b/src/Cemu/ncrypto/ncrypto.cpp index 8a989d2c..bbda681f 100644 --- a/src/Cemu/ncrypto/ncrypto.cpp +++ b/src/Cemu/ncrypto/ncrypto.cpp @@ -20,7 +20,8 @@ void iosuCrypto_readOtpData(void* output, sint32 wordIndex, sint32 size); void iosuCrypto_readSeepromData(void* output, sint32 wordIndex, sint32 size); -extern bool hasSeepromMem; // remove later +extern bool hasSeepromMem; // remove later (migrate otp/seeprom loading & parsing to this class) +extern bool hasOtpMem; // remove later namespace NCrypto { @@ -792,6 +793,16 @@ namespace NCrypto return (CafeConsoleRegion)seepromRegionU32[3]; } + bool OTP_IsPresent() + { + return hasOtpMem; + } + + bool HasDataForConsoleCert() + { + return SEEPROM_IsPresent() && OTP_IsPresent(); + } + std::string GetRegionAsString(CafeConsoleRegion regionCode) { if (regionCode == CafeConsoleRegion::EUR) @@ -957,6 +968,11 @@ namespace NCrypto return it->second; } + size_t GetCountryCount() + { + return g_countryTable.size(); + } + void unitTests() { base64Tests(); diff --git a/src/Cemu/ncrypto/ncrypto.h b/src/Cemu/ncrypto/ncrypto.h index 51f3d9cb..5f399ad7 100644 --- a/src/Cemu/ncrypto/ncrypto.h +++ b/src/Cemu/ncrypto/ncrypto.h @@ -205,7 +205,11 @@ namespace NCrypto CafeConsoleRegion SEEPROM_GetRegion(); std::string GetRegionAsString(CafeConsoleRegion regionCode); const char* GetCountryAsString(sint32 index); // returns NN if index is not valid or known + size_t GetCountryCount(); bool SEEPROM_IsPresent(); + bool OTP_IsPresent(); + + bool HasDataForConsoleCert(); void unitTests(); } \ No newline at end of file diff --git a/src/config/ActiveSettings.cpp b/src/config/ActiveSettings.cpp index 2049bd65..81662ab5 100644 --- a/src/config/ActiveSettings.cpp +++ b/src/config/ActiveSettings.cpp @@ -39,7 +39,6 @@ ActiveSettings::LoadOnce( g_config.SetFilename(GetConfigPath("settings.xml").generic_wstring()); g_config.Load(); - LaunchSettings::ChangeNetworkServiceURL(GetConfig().account.active_service); std::string additionalErrorInfo; s_has_required_online_files = iosuCrypt_checkRequirementsForOnlineMode(additionalErrorInfo) == IOS_CRYPTO_ONLINE_REQ_OK; return failed_write_access; diff --git a/src/config/LaunchSettings.cpp b/src/config/LaunchSettings.cpp index b7a79a11..1731f500 100644 --- a/src/config/LaunchSettings.cpp +++ b/src/config/LaunchSettings.cpp @@ -69,11 +69,7 @@ bool LaunchSettings::HandleCommandline(const std::vector<std::wstring>& args) ("account,a", po::value<std::string>(), "Persistent id of account") ("force-interpreter", po::value<bool>()->implicit_value(true), "Force interpreter CPU emulation, disables recompiler") - ("enable-gdbstub", po::value<bool>()->implicit_value(true), "Enable GDB stub to debug executables inside Cemu using an external debugger") - - ("act-url", po::value<std::string>(), "URL prefix for account server") - ("ecs-url", po::value<std::string>(), "URL for ECS service"); - + ("enable-gdbstub", po::value<bool>()->implicit_value(true), "Enable GDB stub to debug executables inside Cemu using an external debugger"); po::options_description hidden{ "Hidden options" }; hidden.add_options() @@ -190,16 +186,6 @@ bool LaunchSettings::HandleCommandline(const std::vector<std::wstring>& args) if (vm.count("output")) log_path = vm["output"].as<std::wstring>(); - // urls - if (vm.count("act-url")) - { - serviceURL_ACT = vm["act-url"].as<std::string>(); - if (serviceURL_ACT.size() > 0 && serviceURL_ACT.back() == '/') - serviceURL_ACT.pop_back(); - } - if (vm.count("ecs-url")) - serviceURL_ECS = vm["ecs-url"].as<std::string>(); - if(!extract_path.empty()) { ExtractorTool(extract_path, output_path, log_path); @@ -280,24 +266,3 @@ bool LaunchSettings::ExtractorTool(std::wstring_view wud_path, std::string_view return true; } - - -void LaunchSettings::ChangeNetworkServiceURL(int ID){ - NetworkService Network = static_cast<NetworkService>(ID); - switch (Network) - { - case NetworkService::Pretendo: - serviceURL_ACT = PretendoURLs::ACTURL; - serviceURL_ECS = PretendoURLs::ECSURL; - break; - case NetworkService::Custom: - serviceURL_ACT = GetNetworkConfig().urls.ACT.GetValue(); - serviceURL_ECS = GetNetworkConfig().urls.ECS.GetValue(); - break; - case NetworkService::Nintendo: - default: - serviceURL_ACT = NintendoURLs::ACTURL; - serviceURL_ECS = NintendoURLs::ECSURL; - break; - } -} diff --git a/src/config/LaunchSettings.h b/src/config/LaunchSettings.h index be989e6a..b0f673a1 100644 --- a/src/config/LaunchSettings.h +++ b/src/config/LaunchSettings.h @@ -29,10 +29,6 @@ public: static std::optional<uint32> GetPersistentId() { return s_persistent_id; } - static std::string GetActURLPrefix() { return serviceURL_ACT; } - static std::string GetServiceURL_ecs() { return serviceURL_ECS; } - static void ChangeNetworkServiceURL(int ID); - private: inline static std::optional<fs::path> s_load_game_file{}; inline static std::optional<uint64> s_load_title_id{}; @@ -48,12 +44,6 @@ private: inline static std::optional<uint32> s_persistent_id{}; - // service URLS - inline static std::string serviceURL_ACT; - inline static std::string serviceURL_ECS; - // todo - npts and other boss urls - - static bool ExtractorTool(std::wstring_view wud_path, std::string_view output_path, std::wstring_view log_path); }; diff --git a/src/config/NetworkSettings.cpp b/src/config/NetworkSettings.cpp index 5cc66a91..b086d0ae 100644 --- a/src/config/NetworkSettings.cpp +++ b/src/config/NetworkSettings.cpp @@ -30,8 +30,6 @@ void NetworkConfig::Load(XMLConfigParser& parser) urls.BOSS = u.get("boss", NintendoURLs::BOSSURL); urls.TAGAYA = u.get("tagaya", NintendoURLs::TAGAYAURL); urls.OLV = u.get("olv", NintendoURLs::OLVURL); - if (static_cast<NetworkService>(GetConfig().account.active_service.GetValue()) == NetworkService::Custom) - LaunchSettings::ChangeNetworkServiceURL(2); } bool NetworkConfig::XMLExists() @@ -41,7 +39,6 @@ bool NetworkConfig::XMLExists() { if (static_cast<NetworkService>(GetConfig().account.active_service.GetValue()) == NetworkService::Custom) { - LaunchSettings::ChangeNetworkServiceURL(0); GetConfig().account.active_service = 0; } return false; diff --git a/src/config/NetworkSettings.h b/src/config/NetworkSettings.h index 26137cdd..be311182 100644 --- a/src/config/NetworkSettings.h +++ b/src/config/NetworkSettings.h @@ -3,13 +3,15 @@ #include "ConfigValue.h" #include "XMLConfig.h" - -enum class NetworkService { -Nintendo, -Pretendo, -Custom, +enum class NetworkService +{ + Nintendo, + Pretendo, + Custom }; -struct NetworkConfig { + +struct NetworkConfig +{ NetworkConfig() { @@ -69,4 +71,15 @@ struct PretendoURLs { typedef XMLDataConfig<NetworkConfig, &NetworkConfig::Load, &NetworkConfig::Save> XMLNetworkConfig_t; extern XMLNetworkConfig_t n_config; -inline NetworkConfig& GetNetworkConfig() { return n_config.data();}; \ No newline at end of file +inline NetworkConfig& GetNetworkConfig() { return n_config.data();}; + +inline bool IsNetworkServiceSSLDisabled(NetworkService service) +{ + if(service == NetworkService::Nintendo) + return false; + else if(service == NetworkService::Pretendo) + return true; + else if(service == NetworkService::Custom) + return GetNetworkConfig().disablesslver.GetValue(); + return false; +} \ No newline at end of file diff --git a/src/gui/CemuApp.cpp b/src/gui/CemuApp.cpp index 7f11d4c6..505a09c6 100644 --- a/src/gui/CemuApp.cpp +++ b/src/gui/CemuApp.cpp @@ -332,7 +332,7 @@ void CemuApp::CreateDefaultFiles(bool first_start) if (!fs::exists(countryFile)) { std::ofstream file(countryFile); - for (sint32 i = 0; i < 201; i++) + for (sint32 i = 0; i < NCrypto::GetCountryCount(); i++) { const char* countryCode = NCrypto::GetCountryAsString(i); if (boost::iequals(countryCode, "NN")) diff --git a/src/gui/GeneralSettings2.cpp b/src/gui/GeneralSettings2.cpp index e33cfbf6..27ce37fa 100644 --- a/src/gui/GeneralSettings2.cpp +++ b/src/gui/GeneralSettings2.cpp @@ -686,8 +686,10 @@ wxPanel* GeneralSettings2::AddAccountPage(wxNotebook* notebook) if (!NetworkConfig::XMLExists()) m_active_service->Enable(2, false); + m_active_service->SetItemToolTip(0, _("Connect to the official Nintendo Network Service")); + m_active_service->SetItemToolTip(1, _("Connect to the Pretendo Network Service")); + m_active_service->SetItemToolTip(2, _("Connect to a custom Network Service (configured via network_services.xml)")); - m_active_service->SetToolTip(_("Connect to which Network Service")); m_active_service->Bind(wxEVT_RADIOBOX, &GeneralSettings2::OnAccountServiceChanged,this); content->Add(m_active_service, 0, wxEXPAND | wxALL, 5); @@ -762,7 +764,7 @@ wxPanel* GeneralSettings2::AddAccountPage(wxNotebook* notebook) m_account_grid->Append(new wxStringProperty(_("Email"), kPropertyEmail)); wxPGChoices countries; - for (int i = 0; i < 195; ++i) + for (int i = 0; i < NCrypto::GetCountryCount(); ++i) { const auto country = NCrypto::GetCountryAsString(i); if (country && (i == 0 || !boost::equals(country, "NN"))) @@ -1948,8 +1950,6 @@ void GeneralSettings2::OnActiveAccountChanged(wxCommandEvent& event) void GeneralSettings2::OnAccountServiceChanged(wxCommandEvent& event) { - LaunchSettings::ChangeNetworkServiceURL(m_active_service->GetSelection()); - UpdateAccountInformation(); } diff --git a/src/gui/TitleManager.cpp b/src/gui/TitleManager.cpp index 669a1aaf..00e7992f 100644 --- a/src/gui/TitleManager.cpp +++ b/src/gui/TitleManager.cpp @@ -31,17 +31,15 @@ #include <wx/dirdlg.h> #include <wx/notebook.h> +#include "Cafe/IOSU/legacy/iosu_crypto.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" - -#include "resource/embedded/resources.h" - #include "Cafe/TitleList/SaveList.h" +#include "resource/embedded/resources.h" wxDEFINE_EVENT(wxEVT_TITLE_FOUND, wxCommandEvent); wxDEFINE_EVENT(wxEVT_TITLE_SEARCH_COMPLETE, wxCommandEvent); @@ -155,6 +153,7 @@ wxPanel* TitleManager::CreateDownloadManagerPage() { auto* row = new wxBoxSizer(wxHORIZONTAL); +#if DOWNLOADMGR_HAS_ACCOUNT_DROPDOWN m_account = new wxChoice(panel, wxID_ANY); m_account->SetMinSize({ 250,-1 }); auto accounts = Account::GetAccounts(); @@ -172,6 +171,7 @@ wxPanel* TitleManager::CreateDownloadManagerPage() } row->Add(m_account, 0, wxALL, 5); +#endif m_connect = new wxButton(panel, wxID_ANY, _("Connect")); m_connect->Bind(wxEVT_BUTTON, &TitleManager::OnConnect, this); @@ -180,7 +180,17 @@ wxPanel* TitleManager::CreateDownloadManagerPage() sizer->Add(row, 0, wxEXPAND, 5); } +#if DOWNLOADMGR_HAS_ACCOUNT_DROPDOWN m_status_text = new wxStaticText(panel, wxID_ANY, _("Select an account and press Connect")); +#else + if(!NCrypto::HasDataForConsoleCert()) + { + m_status_text = new wxStaticText(panel, wxID_ANY, _("Valid online files are required to download eShop titles. For more information, go to the Account tab in the General Settings.")); + m_connect->Enable(false); + } + else + m_status_text = new wxStaticText(panel, wxID_ANY, _("Click on Connect to load the list of downloadable titles")); +#endif this->Bind(wxEVT_SET_TEXT, &TitleManager::OnSetStatusText, this); sizer->Add(m_status_text, 0, wxALL, 5); @@ -720,9 +730,10 @@ void TitleManager::OnSaveImport(wxCommandEvent& event) void TitleManager::InitiateConnect() { // init connection to download manager if queued +#if DOWNLOADMGR_HAS_ACCOUNT_DROPDOWN uint32 persistentId = (uint32)(uintptr_t)m_account->GetClientData(m_account->GetSelection()); auto& account = Account::GetAccount(persistentId); - +#endif DownloadManager* dlMgr = DownloadManager::GetInstance(); dlMgr->reset(); m_download_list->SetCurrentDownloadMgr(dlMgr); @@ -742,7 +753,15 @@ void TitleManager::InitiateConnect() 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); + std::string accountName; + std::array<uint8, 32> accountPassword; + std::string accountCountry; +#if DOWNLOADMGR_HAS_ACCOUNT_DROPDOWN + accountName = account.GetAccountId(); + accountPassword = account.GetAccountPasswordCache(); + accountCountry.assign(NCrypto::GetCountryAsString(account.GetCountry())); +#endif + dlMgr->connect(accountName, accountPassword, NCrypto::SEEPROM_GetRegion(), accountCountry, NCrypto::GetDeviceId(), NCrypto::GetSerial(), deviceCertBase64); } void TitleManager::OnConnect(wxCommandEvent& event) @@ -787,7 +806,9 @@ void TitleManager::OnDisconnect(wxCommandEvent& event) void TitleManager::SetConnected(bool state) { +#if DOWNLOADMGR_HAS_ACCOUNT_DROPDOWN m_account->Enable(!state); +#endif m_connect->Enable(!state); m_show_titles->Enable(state); diff --git a/src/gui/TitleManager.h b/src/gui/TitleManager.h index 2973618f..ae284040 100644 --- a/src/gui/TitleManager.h +++ b/src/gui/TitleManager.h @@ -8,6 +8,8 @@ #include "Cemu/Tools/DownloadManager/DownloadManager.h" +#define DOWNLOADMGR_HAS_ACCOUNT_DROPDOWN 0 + class wxCheckBox; class wxStaticText; class wxListEvent; From 5be98da0ac5279a4e05eee22f24f3cb807ceb8f5 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Sat, 27 Apr 2024 15:49:49 +0200 Subject: [PATCH 084/299] OpenGL: Fix a crash when GL_VERSION is null (#1187) --- src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp index 28e91b8a..cf134a5d 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp @@ -386,7 +386,7 @@ void OpenGLRenderer::GetVendorInformation() cemuLog_log(LogType::Force, "GL_RENDERER: {}", glRendererString ? glRendererString : "unknown"); cemuLog_log(LogType::Force, "GL_VERSION: {}", glVersionString ? glVersionString : "unknown"); - if(boost::icontains(glVersionString, "Mesa")) + if(glVersionString && boost::icontains(glVersionString, "Mesa")) { m_vendor = GfxVendor::Mesa; return; From fdf239929ff923eb4b28a16fc4754202391cbc3f Mon Sep 17 00:00:00 2001 From: GaryOderNichts <12049776+GaryOderNichts@users.noreply.github.com> Date: Mon, 29 Apr 2024 00:24:43 +0200 Subject: [PATCH 085/299] nsysnet: Various improvements (#1188) - Do not raise an assert for unimplemented optnames - recvfrom: src_addr and addrlen can be NULL - getsockopt: Implement SO_TYPE --- src/Cafe/OS/libs/nsysnet/nsysnet.cpp | 86 ++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 17 deletions(-) diff --git a/src/Cafe/OS/libs/nsysnet/nsysnet.cpp b/src/Cafe/OS/libs/nsysnet/nsysnet.cpp index 128c19a5..88bca8af 100644 --- a/src/Cafe/OS/libs/nsysnet/nsysnet.cpp +++ b/src/Cafe/OS/libs/nsysnet/nsysnet.cpp @@ -36,15 +36,46 @@ #define WU_SO_REUSEADDR 0x0004 #define WU_SO_KEEPALIVE 0x0008 +#define WU_SO_DONTROUTE 0x0010 +#define WU_SO_BROADCAST 0x0020 +#define WU_SO_LINGER 0x0080 +#define WU_SO_OOBINLINE 0x0100 +#define WU_SO_TCPSACK 0x0200 #define WU_SO_WINSCALE 0x0400 #define WU_SO_SNDBUF 0x1001 #define WU_SO_RCVBUF 0x1002 +#define WU_SO_SNDLOWAT 0x1003 +#define WU_SO_RCVLOWAT 0x1004 #define WU_SO_LASTERROR 0x1007 +#define WU_SO_TYPE 0x1008 +#define WU_SO_HOPCNT 0x1009 +#define WU_SO_MAXMSG 0x1010 +#define WU_SO_RXDATA 0x1011 +#define WU_SO_TXDATA 0x1012 +#define WU_SO_MYADDR 0x1013 #define WU_SO_NBIO 0x1014 #define WU_SO_BIO 0x1015 #define WU_SO_NONBLOCK 0x1016 +#define WU_SO_UNKNOWN1019 0x1019 // tcp related +#define WU_SO_UNKNOWN101A 0x101A // tcp related +#define WU_SO_UNKNOWN101B 0x101B // tcp related +#define WU_SO_NOSLOWSTART 0x4000 +#define WU_SO_RUSRBUF 0x10000 -#define WU_TCP_NODELAY 0x2004 +#define WU_TCP_ACKDELAYTIME 0x2001 +#define WU_TCP_NOACKDELAY 0x2002 +#define WU_TCP_MAXSEG 0x2003 +#define WU_TCP_NODELAY 0x2004 +#define WU_TCP_UNKNOWN 0x2005 // amount of mss received before sending an ack + +#define WU_IP_TOS 3 +#define WU_IP_TTL 4 +#define WU_IP_MULTICAST_IF 9 +#define WU_IP_MULTICAST_TTL 10 +#define WU_IP_MULTICAST_LOOP 11 +#define WU_IP_ADD_MEMBERSHIP 12 +#define WU_IP_DROP_MEMBERSHIP 13 +#define WU_IP_UNKNOWN 14 #define WU_SOL_SOCKET -1 // this constant differs from Win32 socket API @@ -548,7 +579,7 @@ void nsysnetExport_setsockopt(PPCInterpreter_t* hCPU) } else { - cemuLog_logDebug(LogType::Force, "setsockopt(): Unsupported optname 0x{:08x}", optname); + cemuLog_logDebug(LogType::Force, "setsockopt(WU_SOL_SOCKET): Unsupported optname 0x{:08x}", optname); } } else if (level == WU_IPPROTO_TCP) @@ -564,18 +595,22 @@ void nsysnetExport_setsockopt(PPCInterpreter_t* hCPU) assert_dbg(); } else - assert_dbg(); + { + cemuLog_logDebug(LogType::Force, "setsockopt(WU_IPPROTO_TCP): Unsupported optname 0x{:08x}", optname); + } } else if (level == WU_IPPROTO_IP) { hostLevel = IPPROTO_IP; - if (optname == 0xC) + if (optname == WU_IP_MULTICAST_IF || optname == WU_IP_MULTICAST_TTL || + optname == WU_IP_MULTICAST_LOOP || optname == WU_IP_ADD_MEMBERSHIP || + optname == WU_IP_DROP_MEMBERSHIP) { - // unknown + cemuLog_logDebug(LogType::Socket, "todo: setsockopt() for multicast"); } - else if( optname == 0x4 ) + else if(optname == WU_IP_TTL || optname == WU_IP_TOS) { - cemuLog_logDebug(LogType::Force, "setsockopt with unsupported opname 4 for IPPROTO_IP"); + cemuLog_logDebug(LogType::Force, "setsockopt(WU_IPPROTO_IP): Unsupported optname 0x{:08x}", optname); } else assert_dbg(); @@ -649,6 +684,16 @@ void nsysnetExport_getsockopt(PPCInterpreter_t* hCPU) *(uint32*)optval = _swapEndianU32(optvalLE); // used by Lost Reavers after some loading screens } + else if (optname == WU_SO_TYPE) + { + if (memory_readU32(optlenMPTR) != 4) + assert_dbg(); + int optvalLE = 0; + socklen_t optlenLE = 4; + memory_writeU32(optlenMPTR, 4); + *(uint32*)optval = _swapEndianU32(vs->type); + r = WU_SO_SUCCESS; + } else if (optname == WU_SO_NONBLOCK) { if (memory_readU32(optlenMPTR) != 4) @@ -661,12 +706,12 @@ void nsysnetExport_getsockopt(PPCInterpreter_t* hCPU) } else { - cemu_assert_debug(false); + cemuLog_logDebug(LogType::Force, "getsockopt(WU_SOL_SOCKET): Unsupported optname 0x{:08x}", optname); } } else { - cemu_assert_debug(false); + cemuLog_logDebug(LogType::Force, "getsockopt(): Unsupported level 0x{:08x}", level); } osLib_returnFromFunction(hCPU, r); @@ -1533,7 +1578,7 @@ void nsysnetExport_getaddrinfo(PPCInterpreter_t* hCPU) void nsysnetExport_recvfrom(PPCInterpreter_t* hCPU) { - cemuLog_log(LogType::Socket, "recvfrom({},0x{:08x},{},0x{:x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]); + cemuLog_log(LogType::Socket, "recvfrom({},0x{:08x},{},0x{:x},0x{:x},0x{:x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7], hCPU->gpr[8]); ppcDefineParamS32(s, 0); ppcDefineParamStr(msg, 1); ppcDefineParamS32(len, 2); @@ -1562,8 +1607,8 @@ void nsysnetExport_recvfrom(PPCInterpreter_t* hCPU) if (vs->isNonBlocking) requestIsNonBlocking = vs->isNonBlocking; - socklen_t fromLenHost = *fromLen; sockaddr fromAddrHost; + socklen_t fromLenHost = sizeof(fromAddrHost); sint32 wsaError = 0; while( true ) @@ -1605,9 +1650,13 @@ void nsysnetExport_recvfrom(PPCInterpreter_t* hCPU) if (r < 0) cemu_assert_debug(false); cemuLog_logDebug(LogType::Force, "recvfrom returned {} bytes", r); - *fromLen = fromLenHost; - fromAddr->sa_family = _swapEndianU16(fromAddrHost.sa_family); - memcpy(fromAddr->sa_data, fromAddrHost.sa_data, 14); + + // fromAddr and fromLen can be NULL + if (fromAddr && fromLen) { + *fromLen = fromLenHost; + fromAddr->sa_family = _swapEndianU16(fromAddrHost.sa_family); + memcpy(fromAddr->sa_data, fromAddrHost.sa_data, 14); + } _setSockError(0); osLib_returnFromFunction(hCPU, r); @@ -1657,9 +1706,12 @@ void nsysnetExport_recvfrom(PPCInterpreter_t* hCPU) assert_dbg(); } - *fromLen = fromLenHost; - fromAddr->sa_family = _swapEndianU16(fromAddrHost.sa_family); - memcpy(fromAddr->sa_data, fromAddrHost.sa_data, 14); + // fromAddr and fromLen can be NULL + if (fromAddr && fromLen) { + *fromLen = fromLenHost; + fromAddr->sa_family = _swapEndianU16(fromAddrHost.sa_family); + memcpy(fromAddr->sa_data, fromAddrHost.sa_data, 14); + } _translateError(r <= 0 ? -1 : 0, wsaError); From b2be3c13df58cd6108d090b892d2801a615fd60d Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Fri, 26 Apr 2024 04:19:15 +0200 Subject: [PATCH 086/299] Add example network_services.xml --- dist/network_services.xml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 dist/network_services.xml diff --git a/dist/network_services.xml b/dist/network_services.xml new file mode 100644 index 00000000..0c0f2e3e --- /dev/null +++ b/dist/network_services.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<content> + <networkname>CustomExample</networkname> + <disablesslverification>0</disablesslverification> + <urls> + <act>https://account.nintendo.net</act> + <ecs>https://ecs.wup.shop.nintendo.net/ecs/services/ECommerceSOAP</ecs> + <nus>https://nus.wup.shop.nintendo.net/nus/services/NetUpdateSOAP</nus> + <ias>https://ias.wup.shop.nintendo.net/ias/services/IdentityAuthenticationSOAP</ias> + <ccsu>https://ccs.wup.shop.nintendo.net/ccs/download</ccsu> + <ccs>http://ccs.cdn.wup.shop.nintendo.net/ccs/download</ccs> + <idbe>https://idbe-wup.cdn.nintendo.net/icondata</idbe> + <boss>https://npts.app.nintendo.net/p01/tasksheet</boss> + <tagaya>https://tagaya.wup.shop.nintendo.net/tagaya/versionlist</tagaya> + <olv>https://discovery.olv.nintendo.net/v1/endpoint</olv> + </urls> +</content> \ No newline at end of file From c038e758aeac76ed55f8f92bbc22f4815cc7689a Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Fri, 26 Apr 2024 04:18:17 +0200 Subject: [PATCH 087/299] IOSU: Clean up resource on service shutdown Also set device-dependent thread name --- src/Cafe/IOSU/nn/iosu_nn_service.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Cafe/IOSU/nn/iosu_nn_service.cpp b/src/Cafe/IOSU/nn/iosu_nn_service.cpp index 1fb5c77a..c888b4fb 100644 --- a/src/Cafe/IOSU/nn/iosu_nn_service.cpp +++ b/src/Cafe/IOSU/nn/iosu_nn_service.cpp @@ -155,7 +155,9 @@ namespace iosu void IPCService::ServiceThread() { - SetThreadName("IPCService"); + std::string serviceName = m_devicePath.substr(m_devicePath.find_last_of('/') == std::string::npos ? 0 : m_devicePath.find_last_of('/') + 1); + serviceName.insert(0, "NNsvc_"); + SetThreadName(serviceName.c_str()); m_msgQueueId = IOS_CreateMessageQueue(_m_msgBuffer.GetPtr(), _m_msgBuffer.GetCount()); cemu_assert(!IOS_ResultIsError((IOS_ERROR)m_msgQueueId)); IOS_ERROR r = IOS_RegisterResourceManager(m_devicePath.c_str(), m_msgQueueId); @@ -208,6 +210,7 @@ namespace iosu IOS_ResourceReply(cmd, IOS_ERROR_INVALID); } } + IOS_DestroyMessageQueue(m_msgQueueId); m_threadInitialized = false; } }; From 1c73dc9e1b824f4618e60704b2c1e6682b749ee0 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Tue, 30 Apr 2024 23:09:00 +0200 Subject: [PATCH 088/299] Implement proc_ui.rpl + stub SYSSwitchToEManual() to avoid softlocks - Full reimplementation of proc_ui.rpl with all 19 exports - Foreground/Background messages now go to the coreinit system message queue as they should (instead of using a hack where proc_ui receives them directly) - Add missing coreinit API needed by proc_ui: OSGetPFID(), OSGetUPID(), OSGetTitleID(), __OSCreateThreadType() - Use big-endian types in OSMessage - Flesh out the stubs for OSDriver_Register and OSDriver_Unregister a bit more since we need to call it from proc_ui. Similiar small tweaks to other coreinit API - Stub sysapp SYSSwitchToEManual() and _SYSSwitchToEManual() in such a way that they will trigger the expected background/foreground transition, avoiding softlocks in games that call these functions --- .gitignore | 1 + src/Cafe/OS/common/OSCommon.cpp | 2 +- src/Cafe/OS/libs/coreinit/coreinit.cpp | 22 - src/Cafe/OS/libs/coreinit/coreinit_DynLoad.h | 6 + src/Cafe/OS/libs/coreinit/coreinit_FS.cpp | 2 +- src/Cafe/OS/libs/coreinit/coreinit_Memory.cpp | 2 +- src/Cafe/OS/libs/coreinit/coreinit_Memory.h | 5 + .../libs/coreinit/coreinit_MessageQueue.cpp | 7 + .../OS/libs/coreinit/coreinit_MessageQueue.h | 19 +- src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp | 96 ++ src/Cafe/OS/libs/coreinit/coreinit_Misc.h | 20 + src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp | 19 +- src/Cafe/OS/libs/coreinit/coreinit_Thread.h | 8 +- src/Cafe/OS/libs/coreinit/coreinit_Time.cpp | 7 +- src/Cafe/OS/libs/coreinit/coreinit_Time.h | 3 +- src/Cafe/OS/libs/gx2/GX2_Misc.h | 2 + src/Cafe/OS/libs/nn_olv/nn_olv.cpp | 3 +- src/Cafe/OS/libs/proc_ui/proc_ui.cpp | 944 +++++++++++++++++- src/Cafe/OS/libs/proc_ui/proc_ui.h | 45 +- src/Cafe/OS/libs/sysapp/sysapp.cpp | 23 + src/Common/CafeString.h | 5 + 21 files changed, 1146 insertions(+), 95 deletions(-) diff --git a/.gitignore b/.gitignore index c10b38da..67a268aa 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ bin/sdcard/* bin/screenshots/* bin/dump/* bin/cafeLibs/* +bin/portable/* bin/keys.txt !bin/shaderCache/info.txt diff --git a/src/Cafe/OS/common/OSCommon.cpp b/src/Cafe/OS/common/OSCommon.cpp index a4410028..5297f201 100644 --- a/src/Cafe/OS/common/OSCommon.cpp +++ b/src/Cafe/OS/common/OSCommon.cpp @@ -221,5 +221,5 @@ void osLib_load() nsyskbd::nsyskbd_load(); swkbd::load(); camera::load(); - procui_load(); + proc_ui::load(); } diff --git a/src/Cafe/OS/libs/coreinit/coreinit.cpp b/src/Cafe/OS/libs/coreinit/coreinit.cpp index 660f874f..e18d0e8d 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit.cpp @@ -179,27 +179,6 @@ void coreinitExport_OSGetSharedData(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, 1); } -typedef struct -{ - MPTR getDriverName; - MPTR ukn04; - MPTR onAcquiredForeground; - MPTR onReleaseForeground; - MPTR ukn10; -}OSDriverCallbacks_t; - -void coreinitExport_OSDriver_Register(PPCInterpreter_t* hCPU) -{ -#ifdef CEMU_DEBUG_ASSERT - cemuLog_log(LogType::Force, "OSDriver_Register(0x{:08x},0x{:08x},0x{:08x},0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7], hCPU->gpr[8]); -#endif - OSDriverCallbacks_t* driverCallbacks = (OSDriverCallbacks_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[5]); - - // todo - - osLib_returnFromFunction(hCPU, 0); -} - namespace coreinit { sint32 OSGetCoreId() @@ -379,7 +358,6 @@ void coreinit_load() coreinit::miscInit(); osLib_addFunction("coreinit", "OSGetSharedData", coreinitExport_OSGetSharedData); osLib_addFunction("coreinit", "UCReadSysConfig", coreinitExport_UCReadSysConfig); - osLib_addFunction("coreinit", "OSDriver_Register", coreinitExport_OSDriver_Register); // async callbacks InitializeAsyncCallback(); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_DynLoad.h b/src/Cafe/OS/libs/coreinit/coreinit_DynLoad.h index 0be8226c..2a3172c7 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_DynLoad.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_DynLoad.h @@ -2,6 +2,12 @@ namespace coreinit { + enum class RplEntryReason + { + Loaded = 1, + Unloaded = 2, + }; + uint32 OSDynLoad_SetAllocator(MPTR allocFunc, MPTR freeFunc); void OSDynLoad_SetTLSAllocator(MPTR allocFunc, MPTR freeFunc); uint32 OSDynLoad_GetAllocator(betype<MPTR>* funcAlloc, betype<MPTR>* funcFree); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp b/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp index a007f5ee..0ca8fb8e 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp @@ -837,7 +837,7 @@ namespace coreinit FSAsyncResult* FSGetAsyncResult(OSMessage* msg) { - return (FSAsyncResult*)memory_getPointerFromVirtualOffset(_swapEndianU32(msg->message)); + return (FSAsyncResult*)memory_getPointerFromVirtualOffset(msg->message); } sint32 __FSProcessAsyncResult(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, sint32 fsStatus, uint32 errHandling) diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Memory.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Memory.cpp index 4b147473..cff4ee2b 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Memory.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Memory.cpp @@ -126,7 +126,7 @@ namespace coreinit return physicalAddr; } - void OSMemoryBarrier(PPCInterpreter_t* hCPU) + void OSMemoryBarrier() { // no-op } diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Memory.h b/src/Cafe/OS/libs/coreinit/coreinit_Memory.h index cfb3ed06..0a212f61 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Memory.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_Memory.h @@ -5,4 +5,9 @@ namespace coreinit void InitializeMemory(); void OSGetMemBound(sint32 memType, MPTR* offsetOutput, uint32* sizeOutput); + + void* OSBlockMove(MEMPTR<void> dst, MEMPTR<void> src, uint32 size, bool flushDC); + void* OSBlockSet(MEMPTR<void> dst, uint32 value, uint32 size); + + void OSMemoryBarrier(); } \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MessageQueue.cpp b/src/Cafe/OS/libs/coreinit/coreinit_MessageQueue.cpp index 6e6a7bc1..cbcfa4d1 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_MessageQueue.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_MessageQueue.cpp @@ -3,6 +3,8 @@ namespace coreinit { + void UpdateSystemMessageQueue(); + void HandleReceivedSystemMessage(OSMessage* msg); SysAllocator<OSMessageQueue> g_systemMessageQueue; SysAllocator<OSMessage, 16> _systemMessageQueueArray; @@ -27,6 +29,9 @@ namespace coreinit bool OSReceiveMessage(OSMessageQueue* msgQueue, OSMessage* msg, uint32 flags) { + bool isSystemMessageQueue = (msgQueue == g_systemMessageQueue); + if(isSystemMessageQueue) + UpdateSystemMessageQueue(); __OSLockScheduler(msgQueue); while (msgQueue->usedCount == (uint32be)0) { @@ -50,6 +55,8 @@ namespace coreinit if (!msgQueue->threadQueueSend.isEmpty()) msgQueue->threadQueueSend.wakeupSingleThreadWaitQueue(true); __OSUnlockScheduler(msgQueue); + if(isSystemMessageQueue) + HandleReceivedSystemMessage(msg); return true; } diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MessageQueue.h b/src/Cafe/OS/libs/coreinit/coreinit_MessageQueue.h index 6741ab84..35fdc3e7 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_MessageQueue.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_MessageQueue.h @@ -3,12 +3,21 @@ namespace coreinit { + enum class SysMessageId : uint32 + { + MsgAcquireForeground = 0xFACEF000, + MsgReleaseForeground = 0xFACEBACC, + MsgExit = 0xD1E0D1E0, + HomeButtonDenied = 0xCCC0FFEE, + NetIoStartOrStop = 0xAAC0FFEE, + }; + struct OSMessage { - MPTR message; - uint32 data0; - uint32 data1; - uint32 data2; + uint32be message; + uint32be data0; + uint32be data1; + uint32be data2; }; struct OSMessageQueue @@ -36,5 +45,7 @@ namespace coreinit bool OSPeekMessage(OSMessageQueue* msgQueue, OSMessage* msg); sint32 OSSendMessage(OSMessageQueue* msgQueue, OSMessage* msg, uint32 flags); + OSMessageQueue* OSGetSystemMessageQueue(); + void InitializeMessageQueue(); }; \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp index 2d7468cf..e2b50661 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp @@ -1,5 +1,6 @@ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/coreinit/coreinit_Misc.h" +#include "Cafe/OS/libs/coreinit/coreinit_MessageQueue.h" #include "Cafe/CafeSystem.h" #include "Cafe/Filesystem/fsc.h" #include <pugixml.hpp> @@ -371,6 +372,23 @@ namespace coreinit return true; } + uint32 OSGetPFID() + { + return 15; // hardcoded as game + } + + uint32 OSGetUPID() + { + return OSGetPFID(); + } + + uint64 s_currentTitleId; + + uint64 OSGetTitleID() + { + return s_currentTitleId; + } + uint32 s_sdkVersion; uint32 __OSGetProcessSDKVersion() @@ -470,9 +488,78 @@ namespace coreinit return 0; } + void OSReleaseForeground() + { + cemuLog_logDebug(LogType::Force, "OSReleaseForeground not implemented"); + } + + bool s_transitionToBackground = false; + bool s_transitionToForeground = false; + + void StartBackgroundForegroundTransition() + { + s_transitionToBackground = true; + s_transitionToForeground = true; + } + + // called at the beginning of OSReceiveMessage if the queue is the system message queue + void UpdateSystemMessageQueue() + { + if(!OSIsInterruptEnabled()) + return; + cemu_assert_debug(!__OSHasSchedulerLock()); + // normally syscall 0x2E is used to get the next message + // for now we just have some preliminary logic here to allow a fake transition to background & foreground + if(s_transitionToBackground) + { + // add transition to background message + OSMessage msg{}; + msg.data0 = stdx::to_underlying(SysMessageId::MsgReleaseForeground); + msg.data1 = 0; // 1 -> System is shutting down 0 -> Begin transitioning to background + OSMessageQueue* systemMessageQueue = coreinit::OSGetSystemMessageQueue(); + if(OSSendMessage(systemMessageQueue, &msg, 0)) + s_transitionToBackground = false; + return; + } + if(s_transitionToForeground) + { + // add transition to foreground message + OSMessage msg{}; + msg.data0 = stdx::to_underlying(SysMessageId::MsgAcquireForeground); + msg.data1 = 1; // ? + msg.data2 = 1; // ? + OSMessageQueue* systemMessageQueue = coreinit::OSGetSystemMessageQueue(); + if(OSSendMessage(systemMessageQueue, &msg, 0)) + s_transitionToForeground = false; + return; + } + } + + // called when OSReceiveMessage returns a message from the system message queue + void HandleReceivedSystemMessage(OSMessage* msg) + { + cemu_assert_debug(!__OSHasSchedulerLock()); + cemuLog_log(LogType::Force, "Receiving message: {:08x}", (uint32)msg->data0); + } + + uint32 OSDriver_Register(uint32 moduleHandle, sint32 priority, OSDriverInterface* driverCallbacks, sint32 driverId, uint32be* outUkn1, uint32be* outUkn2, uint32be* outUkn3) + { + cemuLog_logDebug(LogType::Force, "OSDriver_Register stubbed"); + return 0; + } + + uint32 OSDriver_Deregister(uint32 moduleHandle, sint32 driverId) + { + cemuLog_logDebug(LogType::Force, "OSDriver_Deregister stubbed"); + return 0; + } + void miscInit() { + s_currentTitleId = CafeSystem::GetForegroundTitleId(); s_sdkVersion = CafeSystem::GetForegroundTitleSDKVersion(); + s_transitionToBackground = false; + s_transitionToForeground = false; cafeExportRegister("coreinit", __os_snprintf, LogType::Placeholder); cafeExportRegister("coreinit", OSReport, LogType::Placeholder); @@ -480,6 +567,10 @@ namespace coreinit cafeExportRegister("coreinit", COSWarn, LogType::Placeholder); cafeExportRegister("coreinit", OSLogPrintf, LogType::Placeholder); cafeExportRegister("coreinit", OSConsoleWrite, LogType::Placeholder); + + cafeExportRegister("coreinit", OSGetPFID, LogType::Placeholder); + cafeExportRegister("coreinit", OSGetUPID, LogType::Placeholder); + cafeExportRegister("coreinit", OSGetTitleID, LogType::Placeholder); cafeExportRegister("coreinit", __OSGetProcessSDKVersion, LogType::Placeholder); g_homeButtonMenuEnabled = true; // enabled by default @@ -489,6 +580,11 @@ namespace coreinit cafeExportRegister("coreinit", OSLaunchTitleByPathl, LogType::Placeholder); cafeExportRegister("coreinit", OSRestartGame, LogType::Placeholder); + + cafeExportRegister("coreinit", OSReleaseForeground, LogType::Placeholder); + + cafeExportRegister("coreinit", OSDriver_Register, LogType::Placeholder); + cafeExportRegister("coreinit", OSDriver_Deregister, LogType::Placeholder); } }; diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Misc.h b/src/Cafe/OS/libs/coreinit/coreinit_Misc.h index 4a74d490..7abba92f 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Misc.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_Misc.h @@ -2,9 +2,29 @@ namespace coreinit { + uint32 OSGetUPID(); + uint32 OSGetPFID(); + uint64 OSGetTitleID(); uint32 __OSGetProcessSDKVersion(); uint32 OSLaunchTitleByPathl(const char* path, uint32 pathLength, uint32 argc); uint32 OSRestartGame(uint32 argc, MEMPTR<char>* argv); + void OSReleaseForeground(); + + void StartBackgroundForegroundTransition(); + + struct OSDriverInterface + { + MEMPTR<void> getDriverName; + MEMPTR<void> init; + MEMPTR<void> onAcquireForeground; + MEMPTR<void> onReleaseForeground; + MEMPTR<void> done; + }; + static_assert(sizeof(OSDriverInterface) == 0x14); + + uint32 OSDriver_Register(uint32 moduleHandle, sint32 priority, OSDriverInterface* driverCallbacks, sint32 driverId, uint32be* outUkn1, uint32be* outUkn2, uint32be* outUkn3); + uint32 OSDriver_Deregister(uint32 moduleHandle, sint32 driverId); + void miscInit(); }; \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp index 809d7be4..654e57a8 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp @@ -294,9 +294,9 @@ namespace coreinit __OSUnlockScheduler(); } - bool OSCreateThreadType(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop2, sint32 stackSize, sint32 priority, uint32 attr, OSThread_t::THREAD_TYPE threadType) + bool OSCreateThreadType(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop, sint32 stackSize, sint32 priority, uint32 attr, OSThread_t::THREAD_TYPE threadType) { - OSCreateThreadInternal(thread, entryPoint, memory_getVirtualOffsetFromPointer(stackTop2) - stackSize, stackSize, attr, threadType); + OSCreateThreadInternal(thread, entryPoint, memory_getVirtualOffsetFromPointer(stackTop) - stackSize, stackSize, attr, threadType); thread->context.gpr[3] = _swapEndianU32(numParam); // num arguments thread->context.gpr[4] = _swapEndianU32(memory_getVirtualOffsetFromPointer(ptrParam)); // arguments pointer __OSSetThreadBasePriority(thread, priority); @@ -317,9 +317,15 @@ namespace coreinit return true; } - bool OSCreateThread(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop2, sint32 stackSize, sint32 priority, uint32 attr) + bool OSCreateThread(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop, sint32 stackSize, sint32 priority, uint32 attr) { - return OSCreateThreadType(thread, entryPoint, numParam, ptrParam, stackTop2, stackSize, priority, attr, OSThread_t::THREAD_TYPE::TYPE_APP); + return OSCreateThreadType(thread, entryPoint, numParam, ptrParam, stackTop, stackSize, priority, attr, OSThread_t::THREAD_TYPE::TYPE_APP); + } + + // alias to OSCreateThreadType, similar to OSCreateThread, but with an additional parameter for the thread type + bool __OSCreateThreadType(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop, sint32 stackSize, sint32 priority, uint32 attr, OSThread_t::THREAD_TYPE threadType) + { + return OSCreateThreadType(thread, entryPoint, numParam, ptrParam, stackTop, stackSize, priority, attr, threadType); } bool OSRunThread(OSThread_t* thread, MPTR funcAddress, sint32 numParam, void* ptrParam) @@ -445,12 +451,12 @@ namespace coreinit return currentThread->specificArray[index].GetPtr(); } - void OSSetThreadName(OSThread_t* thread, char* name) + void OSSetThreadName(OSThread_t* thread, const char* name) { thread->threadName = name; } - char* OSGetThreadName(OSThread_t* thread) + const char* OSGetThreadName(OSThread_t* thread) { return thread->threadName.GetPtr(); } @@ -1371,6 +1377,7 @@ namespace coreinit { cafeExportRegister("coreinit", OSCreateThreadType, LogType::CoreinitThread); cafeExportRegister("coreinit", OSCreateThread, LogType::CoreinitThread); + cafeExportRegister("coreinit", __OSCreateThreadType, LogType::CoreinitThread); cafeExportRegister("coreinit", OSExitThread, LogType::CoreinitThread); cafeExportRegister("coreinit", OSGetCurrentThread, LogType::CoreinitThread); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.h b/src/Cafe/OS/libs/coreinit/coreinit_Thread.h index e619d5b6..b401d96d 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.h @@ -449,7 +449,7 @@ struct OSThread_t /* +0x578 */ sint32 alarmRelatedUkn; /* +0x57C */ std::array<MEMPTR<void>, 16> specificArray; /* +0x5BC */ betype<THREAD_TYPE> type; - /* +0x5C0 */ MEMPTR<char> threadName; + /* +0x5C0 */ MEMPTR<const char> threadName; /* +0x5C4 */ MPTR waitAlarm; // used only by OSWaitEventWithTimeout/OSSignalEvent ? /* +0x5C8 */ uint32 userStackPointer; @@ -505,6 +505,7 @@ namespace coreinit void* OSGetDefaultThreadStack(sint32 coreIndex, uint32& size); bool OSCreateThreadType(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop2, sint32 stackSize, sint32 priority, uint32 attr, OSThread_t::THREAD_TYPE threadType); + bool __OSCreateThreadType(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop, sint32 stackSize, sint32 priority, uint32 attr, OSThread_t::THREAD_TYPE threadType); void OSCreateThreadInternal(OSThread_t* thread, uint32 entryPoint, MPTR stackLowerBaseAddr, uint32 stackSize, uint8 affinityMask, OSThread_t::THREAD_TYPE threadType); bool OSRunThread(OSThread_t* thread, MPTR funcAddress, sint32 numParam, void* ptrParam); void OSExitThread(sint32 exitValue); @@ -519,8 +520,8 @@ namespace coreinit bool OSSetThreadPriority(OSThread_t* thread, sint32 newPriority); uint32 OSGetThreadAffinity(OSThread_t* thread); - void OSSetThreadName(OSThread_t* thread, char* name); - char* OSGetThreadName(OSThread_t* thread); + void OSSetThreadName(OSThread_t* thread, const char* name); + const char* OSGetThreadName(OSThread_t* thread); sint32 __OSResumeThreadInternal(OSThread_t* thread, sint32 resumeCount); sint32 OSResumeThread(OSThread_t* thread); @@ -530,6 +531,7 @@ namespace coreinit void OSSuspendThread(OSThread_t* thread); void OSSleepThread(OSThreadQueue* threadQueue); void OSWakeupThread(OSThreadQueue* threadQueue); + bool OSJoinThread(OSThread_t* thread, uint32be* exitValue); void OSTestThreadCancelInternal(); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Time.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Time.cpp index 5a75b406..d6fc27b2 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Time.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Time.cpp @@ -22,10 +22,9 @@ namespace coreinit osLib_returnFromFunction(hCPU, (uint32)osTime); } - void export_OSGetTime(PPCInterpreter_t* hCPU) + uint64 OSGetTime() { - uint64 osTime = coreinit_getOSTime(); - osLib_returnFromFunction64(hCPU, osTime); + return coreinit_getOSTime(); } void export_OSGetSystemTime(PPCInterpreter_t* hCPU) @@ -360,7 +359,7 @@ namespace coreinit void InitializeTimeAndCalendar() { - osLib_addFunction("coreinit", "OSGetTime", export_OSGetTime); + cafeExportRegister("coreinit", OSGetTime, LogType::Placeholder); osLib_addFunction("coreinit", "OSGetSystemTime", export_OSGetSystemTime); osLib_addFunction("coreinit", "OSGetTick", export_OSGetTick); osLib_addFunction("coreinit", "OSGetSystemTick", export_OSGetSystemTick); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Time.h b/src/Cafe/OS/libs/coreinit/coreinit_Time.h index f5dcf22e..018e8eb7 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Time.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_Time.h @@ -45,7 +45,8 @@ namespace coreinit }; void OSTicksToCalendarTime(uint64 ticks, OSCalendarTime_t* calenderStruct); - + uint64 OSGetTime(); + uint64 coreinit_getOSTime(); uint64 coreinit_getTimerTick(); diff --git a/src/Cafe/OS/libs/gx2/GX2_Misc.h b/src/Cafe/OS/libs/gx2/GX2_Misc.h index e6ac8010..38a728c1 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Misc.h +++ b/src/Cafe/OS/libs/gx2/GX2_Misc.h @@ -19,5 +19,7 @@ namespace GX2 void GX2SetTVBuffer(void* imageBuffePtr, uint32 imageBufferSize, E_TVRES tvResolutionMode, uint32 surfaceFormat, E_TVBUFFERMODE bufferMode); void GX2SetTVGamma(float gamma); + void GX2Invalidate(uint32 invalidationFlags, MPTR invalidationAddr, uint32 invalidationSize); + void GX2MiscInit(); }; \ No newline at end of file diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv.cpp index 99c113c4..1916a18d 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv.cpp +++ b/src/Cafe/OS/libs/nn_olv/nn_olv.cpp @@ -9,6 +9,7 @@ #include "Cafe/OS/libs/proc_ui/proc_ui.h" #include "Cafe/OS/libs/coreinit/coreinit_Time.h" +#include "Cafe/OS/libs/coreinit/coreinit_Misc.h" namespace nn { @@ -37,7 +38,7 @@ namespace nn void StubPostAppReleaseBackground(PPCInterpreter_t* hCPU) { coreinit::OSSleepTicks(ESPRESSO_TIMER_CLOCK * 2); // Sleep 2s - ProcUI_SendForegroundMessage(); + coreinit::StartBackgroundForegroundTransition(); } sint32 StubPostApp(void* pAnyPostParam) diff --git a/src/Cafe/OS/libs/proc_ui/proc_ui.cpp b/src/Cafe/OS/libs/proc_ui/proc_ui.cpp index 7de8691a..91d15af4 100644 --- a/src/Cafe/OS/libs/proc_ui/proc_ui.cpp +++ b/src/Cafe/OS/libs/proc_ui/proc_ui.cpp @@ -1,57 +1,905 @@ #include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/libs/coreinit/coreinit_Alarm.h" +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" +#include "Cafe/OS/libs/coreinit/coreinit_MessageQueue.h" +#include "Cafe/OS/libs/coreinit/coreinit_Misc.h" +#include "Cafe/OS/libs/coreinit/coreinit_Memory.h" +#include "Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.h" +#include "Cafe/OS/libs/coreinit/coreinit_Time.h" +#include "Cafe/OS/libs/coreinit/coreinit_FG.h" +#include "Cafe/OS/libs/coreinit/coreinit_DynLoad.h" +#include "Cafe/OS/libs/gx2/GX2_Misc.h" +#include "Cafe/OS/RPL/rpl.h" +#include "Common/CafeString.h" #include "proc_ui.h" -#define PROCUI_STATUS_FOREGROUND 0 -#define PROCUI_STATUS_BACKGROUND 1 -#define PROCUI_STATUS_RELEASING 2 -#define PROCUI_STATUS_EXIT 3 +// proc_ui is a utility wrapper to help apps with the transition between foreground and background +// some games (like Xenoblades Chronicles X) bypass proc_ui.rpl and listen to OSGetSystemMessageQueue() directly -uint32 ProcUIProcessMessages() +using namespace coreinit; + +namespace proc_ui { - return PROCUI_STATUS_FOREGROUND; -} + enum class ProcUICoreThreadCommand + { + AcquireForeground = 0x0, + ReleaseForeground = 0x1, + Exit = 0x2, + NetIoStart = 0x3, + NetIoStop = 0x4, + HomeButtonDenied = 0x5, + Initial = 0x6 + }; + + struct ProcUIInternalCallbackEntry + { + coreinit::OSAlarm_t alarm; + uint64be tickDelay; + MEMPTR<void> funcPtr; + MEMPTR<void> userParam; + sint32be priority; + MEMPTR<ProcUIInternalCallbackEntry> next; + }; + static_assert(sizeof(ProcUIInternalCallbackEntry) == 0x70); + + struct ProcUICallbackList + { + MEMPTR<ProcUIInternalCallbackEntry> first; + }; + static_assert(sizeof(ProcUICallbackList) == 0x4); + + std::atomic_bool s_isInitialized; + bool s_isInForeground; + bool s_isInShutdown; + bool s_isForegroundProcess; + bool s_previouslyWasBlocking; + ProcUIStatus s_currentProcUIStatus; + MEMPTR<void> s_saveCallback; // no param and no return value, set by ProcUIInit() + MEMPTR<void> s_saveCallbackEx; // with custom param and return value, set by ProcUIInitEx() + MEMPTR<void> s_saveCallbackExUserParam; + MEMPTR<coreinit::OSMessageQueue> s_systemMessageQueuePtr; + SysAllocator<coreinit::OSEvent> s_eventStateMessageReceived; + SysAllocator<coreinit::OSEvent> s_eventWaitingBeforeReleaseForeground; + SysAllocator<coreinit::OSEvent> s_eventBackgroundThreadGotMessage; + // procUI core threads + uint32 s_coreThreadStackSize; + bool s_coreThreadsCreated; + std::atomic<ProcUICoreThreadCommand> s_commandForCoreThread; + SysAllocator<OSThread_t> s_coreThreadArray[Espresso::CORE_COUNT]; + MEMPTR<void> s_coreThreadStackPerCore[Espresso::CORE_COUNT]; + SysAllocator<CafeString<22>> s_coreThread0NameBuffer; + SysAllocator<CafeString<22>> s_coreThread1NameBuffer; + SysAllocator<CafeString<22>> s_coreThread2NameBuffer; + SysAllocator<coreinit::OSEvent> s_eventCoreThreadsNewCommandReady; + SysAllocator<coreinit::OSEvent> s_eventCoreThreadsCommandDone; + SysAllocator<coreinit::OSRendezvous> s_coreThreadRendezvousA; + SysAllocator<coreinit::OSRendezvous> s_coreThreadRendezvousB; + SysAllocator<coreinit::OSRendezvous> s_coreThreadRendezvousC; + // background thread + MEMPTR<void> s_backgroundThreadStack; + SysAllocator<OSThread_t> s_backgroundThread; + // user defined heap + MEMPTR<void> s_memoryPoolHeapPtr; + MEMPTR<void> s_memAllocPtr; + MEMPTR<void> s_memFreePtr; + // draw done release + bool s_drawDoneReleaseCalled; + // memory storage + MEMPTR<void> s_bucketStorageBasePtr; + MEMPTR<void> s_mem1StorageBasePtr; + // callbacks + ProcUICallbackList s_callbacksType0_AcquireForeground[Espresso::CORE_COUNT]; + ProcUICallbackList s_callbacksType1_ReleaseForeground[Espresso::CORE_COUNT]; + ProcUICallbackList s_callbacksType2_Exit[Espresso::CORE_COUNT]; + ProcUICallbackList s_callbacksType3_NetIoStart[Espresso::CORE_COUNT]; + ProcUICallbackList s_callbacksType4_NetIoStop[Espresso::CORE_COUNT]; + ProcUICallbackList s_callbacksType5_HomeButtonDenied[Espresso::CORE_COUNT]; + ProcUICallbackList* const s_CallbackTables[stdx::to_underlying(ProcUICallbackId::COUNT)] = + {s_callbacksType0_AcquireForeground, s_callbacksType1_ReleaseForeground, s_callbacksType2_Exit, s_callbacksType3_NetIoStart, s_callbacksType4_NetIoStop, s_callbacksType5_HomeButtonDenied}; + ProcUICallbackList s_backgroundCallbackList; + // driver + bool s_driverIsActive; + uint32be s_driverArgUkn1; + uint32be s_driverArgUkn2; + bool s_driverInBackground; + SysAllocator<OSDriverInterface> s_ProcUIDriver; + SysAllocator<CafeString<16>> s_ProcUIDriverName; -uint32 ProcUIInForeground(PPCInterpreter_t* hCPU) -{ - return 1; // true means application is in foreground -} + void* _AllocMem(uint32 size) + { + MEMPTR<void> r{PPCCoreCallback(s_memAllocPtr, size)}; + return r.GetPtr(); + } -struct ProcUICallback -{ - MPTR callback; - void* data; - sint32 priority; + void _FreeMem(void* ptr) + { + PPCCoreCallback(s_memFreePtr.GetMPTR(), ptr); + } + + void ClearCallbacksWithoutMemFree() + { + for (sint32 coreIndex = 0; coreIndex < Espresso::CORE_COUNT; coreIndex++) + { + for (sint32 i = 0; i < stdx::to_underlying(ProcUICallbackId::COUNT); i++) + s_CallbackTables[i][coreIndex].first = nullptr; + } + s_backgroundCallbackList.first = nullptr; + } + + void ShutdownThreads() + { + if ( !s_coreThreadsCreated) + return; + s_commandForCoreThread = ProcUICoreThreadCommand::Initial; + coreinit::OSMemoryBarrier(); + OSSignalEvent(&s_eventCoreThreadsNewCommandReady); + for (sint32 coreIndex = 0; coreIndex < Espresso::CORE_COUNT; coreIndex++) + { + coreinit::OSJoinThread(&s_coreThreadArray[coreIndex], nullptr); + for (sint32 i = 0; i < stdx::to_underlying(ProcUICallbackId::COUNT); i++) + { + s_CallbackTables[i][coreIndex].first = nullptr; // memory is not cleanly released? + } + } + OSResetEvent(&s_eventCoreThreadsNewCommandReady); + for (sint32 coreIndex = 0; coreIndex < Espresso::CORE_COUNT; coreIndex++) + { + _FreeMem(s_coreThreadStackPerCore[coreIndex]); + s_coreThreadStackPerCore[coreIndex] = nullptr; + } + _FreeMem(s_backgroundThreadStack); + s_backgroundThreadStack = nullptr; + s_backgroundCallbackList.first = nullptr; // memory is not cleanly released? + s_coreThreadsCreated = false; + } + + void DoCallbackChain(ProcUIInternalCallbackEntry* entry) + { + while (entry) + { + uint32 r = PPCCoreCallback(entry->funcPtr, entry->userParam); + if ( r ) + cemuLog_log(LogType::APIErrors, "ProcUI: Callback returned error {}\n", r); + entry = entry->next; + } + } + + void AlarmDoBackgroundCallback(PPCInterpreter_t* hCPU) + { + coreinit::OSAlarm_t* arg = MEMPTR<coreinit::OSAlarm_t>(hCPU->gpr[3]); + ProcUIInternalCallbackEntry* entry = (ProcUIInternalCallbackEntry*)arg; + uint32 r = PPCCoreCallback(entry->funcPtr, entry->userParam); + if ( r ) + cemuLog_log(LogType::APIErrors, "ProcUI: Background callback returned error {}\n", r); + osLib_returnFromFunction(hCPU, 0); // return type is void + } + + void StartBackgroundAlarms() + { + ProcUIInternalCallbackEntry* cb = s_backgroundCallbackList.first; + while(cb) + { + coreinit::OSCreateAlarm(&cb->alarm); + uint64 currentTime = coreinit::OSGetTime(); + coreinit::OSSetPeriodicAlarm(&cb->alarm, currentTime, cb->tickDelay, RPLLoader_MakePPCCallable(AlarmDoBackgroundCallback)); + cb = cb->next; + } + } + + void CancelBackgroundAlarms() + { + ProcUIInternalCallbackEntry* entry = s_backgroundCallbackList.first; + while (entry) + { + OSCancelAlarm(&entry->alarm); + entry = entry->next; + } + } + + void ProcUICoreThread(PPCInterpreter_t* hCPU) + { + uint32 coreIndex = hCPU->gpr[3]; + cemu_assert_debug(coreIndex == OSGetCoreId()); + while (true) + { + OSWaitEvent(&s_eventCoreThreadsNewCommandReady); + ProcUIInternalCallbackEntry* cbChain = nullptr; + cemuLog_logDebug(LogType::Force, "ProcUI: Core {} got command {}", coreIndex, (uint32)s_commandForCoreThread.load()); + auto cmd = s_commandForCoreThread.load(); + switch(cmd) + { + case ProcUICoreThreadCommand::Initial: + { + // signal to shut down thread + osLib_returnFromFunction(hCPU, 0); + return; + } + case ProcUICoreThreadCommand::AcquireForeground: + cbChain = s_callbacksType0_AcquireForeground[coreIndex].first; + break; + case ProcUICoreThreadCommand::ReleaseForeground: + cbChain = s_callbacksType1_ReleaseForeground[coreIndex].first; + break; + case ProcUICoreThreadCommand::Exit: + cbChain = s_callbacksType2_Exit[coreIndex].first; + break; + case ProcUICoreThreadCommand::NetIoStart: + cbChain = s_callbacksType3_NetIoStart[coreIndex].first; + break; + case ProcUICoreThreadCommand::NetIoStop: + cbChain = s_callbacksType4_NetIoStop[coreIndex].first; + break; + case ProcUICoreThreadCommand::HomeButtonDenied: + cbChain = s_callbacksType5_HomeButtonDenied[coreIndex].first; + break; + default: + cemu_assert_suspicious(); // invalid command + } + if(cmd == ProcUICoreThreadCommand::AcquireForeground) + { + if (coreIndex == 2) + CancelBackgroundAlarms(); + cbChain = s_callbacksType0_AcquireForeground[coreIndex].first; + } + else if(cmd == ProcUICoreThreadCommand::ReleaseForeground) + { + if (coreIndex == 2) + StartBackgroundAlarms(); + cbChain = s_callbacksType1_ReleaseForeground[coreIndex].first; + } + DoCallbackChain(cbChain); + OSWaitRendezvous(&s_coreThreadRendezvousA, 7); + if ( !coreIndex ) + { + OSInitRendezvous(&s_coreThreadRendezvousC); + OSResetEvent(&s_eventCoreThreadsNewCommandReady); + } + OSWaitRendezvous(&s_coreThreadRendezvousB, 7); + if ( !coreIndex ) + { + OSInitRendezvous(&s_coreThreadRendezvousA); + OSSignalEvent(&s_eventCoreThreadsCommandDone); + } + OSWaitRendezvous(&s_coreThreadRendezvousC, 7); + if ( !coreIndex ) + OSInitRendezvous(&s_coreThreadRendezvousB); + if (cmd == ProcUICoreThreadCommand::ReleaseForeground) + { + OSWaitEvent(&s_eventWaitingBeforeReleaseForeground); + OSReleaseForeground(); + } + } + osLib_returnFromFunction(hCPU, 0); + } + + void RecreateProcUICoreThreads() + { + ShutdownThreads(); + for (sint32 coreIndex = 0; coreIndex < Espresso::CORE_COUNT; coreIndex++) + { + s_coreThreadStackPerCore[coreIndex] = _AllocMem(s_coreThreadStackSize); + } + s_backgroundThreadStack = _AllocMem(s_coreThreadStackSize); + for (sint32 coreIndex = 0; coreIndex < Espresso::CORE_COUNT; coreIndex++) + { + __OSCreateThreadType(&s_coreThreadArray[coreIndex], RPLLoader_MakePPCCallable(ProcUICoreThread), coreIndex, nullptr, + (uint8*)s_coreThreadStackPerCore[coreIndex].GetPtr() + s_coreThreadStackSize, s_coreThreadStackSize, 16, + (1<<coreIndex), OSThread_t::THREAD_TYPE::TYPE_DRIVER); + OSResumeThread(&s_coreThreadArray[coreIndex]); + } + s_coreThread0NameBuffer->assign("{SYS ProcUI Core 0}"); + s_coreThread1NameBuffer->assign("{SYS ProcUI Core 1}"); + s_coreThread2NameBuffer->assign("{SYS ProcUI Core 2}"); + OSSetThreadName(&s_coreThreadArray[0], s_coreThread0NameBuffer->c_str()); + OSSetThreadName(&s_coreThreadArray[1], s_coreThread1NameBuffer->c_str()); + OSSetThreadName(&s_coreThreadArray[2], s_coreThread2NameBuffer->c_str()); + s_coreThreadsCreated = true; + } + + void _SubmitCommandToCoreThreads(ProcUICoreThreadCommand cmd) + { + s_commandForCoreThread = cmd; + OSMemoryBarrier(); + OSResetEvent(&s_eventCoreThreadsCommandDone); + OSSignalEvent(&s_eventCoreThreadsNewCommandReady); + OSWaitEvent(&s_eventCoreThreadsCommandDone); + } + + void ProcUIInitInternal() + { + if( s_isInitialized.exchange(true) ) + return; + if (!s_memoryPoolHeapPtr) + { + // user didn't specify a custom heap, use default heap instead + s_memAllocPtr = gCoreinitData->MEMAllocFromDefaultHeap.GetMPTR(); + s_memFreePtr = gCoreinitData->MEMFreeToDefaultHeap.GetMPTR(); + } + OSInitEvent(&s_eventStateMessageReceived, OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, OSEvent::EVENT_MODE::MODE_MANUAL); + OSInitEvent(&s_eventCoreThreadsNewCommandReady, OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, OSEvent::EVENT_MODE::MODE_MANUAL); + OSInitEvent(&s_eventWaitingBeforeReleaseForeground, OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, OSEvent::EVENT_MODE::MODE_MANUAL); + OSInitEvent(&s_eventCoreThreadsCommandDone, OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, OSEvent::EVENT_MODE::MODE_MANUAL); + OSInitRendezvous(&s_coreThreadRendezvousA); + OSInitRendezvous(&s_coreThreadRendezvousB); + OSInitRendezvous(&s_coreThreadRendezvousC); + OSInitEvent(&s_eventBackgroundThreadGotMessage, OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, OSEvent::EVENT_MODE::MODE_MANUAL); + s_currentProcUIStatus = ProcUIStatus::Foreground; + s_drawDoneReleaseCalled = false; + s_isInForeground = true; + s_coreThreadStackSize = 0x2000; + s_systemMessageQueuePtr = coreinit::OSGetSystemMessageQueue(); + uint32 upid = coreinit::OSGetUPID(); + s_isForegroundProcess = upid == 2 || upid == 15; // either Wii U Menu or game title, both are RAMPID 7 (foreground process) + RecreateProcUICoreThreads(); + ClearCallbacksWithoutMemFree(); + } + + void ProcUIInit(MEMPTR<void> callbackReadyToRelease) + { + s_saveCallback = callbackReadyToRelease; + s_saveCallbackEx = nullptr; + s_saveCallbackExUserParam = nullptr; + ProcUIInitInternal(); + } + + void ProcUIInitEx(MEMPTR<void> callbackReadyToReleaseEx, MEMPTR<void> userParam) + { + s_saveCallback = nullptr; + s_saveCallbackEx = callbackReadyToReleaseEx; + s_saveCallbackExUserParam = userParam; + ProcUIInitInternal(); + } + + void ProcUIShutdown() + { + if (!s_isInitialized.exchange(false)) + return; + if ( !s_isInForeground ) + CancelBackgroundAlarms(); + for (sint32 i = 0; i < Espresso::CORE_COUNT; i++) + OSSetThreadPriority(&s_coreThreadArray[i], 0); + _SubmitCommandToCoreThreads(ProcUICoreThreadCommand::Exit); + ProcUIClearCallbacks(); + ShutdownThreads(); + } + + bool ProcUIIsRunning() + { + return s_isInitialized; + } + + bool ProcUIInForeground() + { + return s_isInForeground; + } + + bool ProcUIInShutdown() + { + return s_isInShutdown; + } + + void AddCallbackInternal(void* funcPtr, void* userParam, uint64 tickDelay, sint32 priority, ProcUICallbackList& callbackList) + { + if ( __OSGetProcessSDKVersion() < 21102 ) + { + // in earlier COS versions it was possible/allowed to register a callback before initializing ProcUI + s_memAllocPtr = gCoreinitData->MEMAllocFromDefaultHeap.GetMPTR(); + s_memFreePtr = gCoreinitData->MEMFreeToDefaultHeap.GetMPTR(); + } + else if ( !s_isInitialized ) + { + cemuLog_log(LogType::Force, "ProcUI: Trying to register callback before init"); + cemu_assert_suspicious(); + } + ProcUIInternalCallbackEntry* entry = (ProcUIInternalCallbackEntry*)_AllocMem(sizeof(ProcUIInternalCallbackEntry)); + entry->funcPtr = funcPtr; + entry->userParam = userParam; + entry->tickDelay = tickDelay; + entry->priority = priority; + ProcUIInternalCallbackEntry* cur = callbackList.first; + cur = callbackList.first; + if (!cur || cur->priority > priority) + { + // insert as the first element + entry->next = cur; + callbackList.first = entry; + } + else + { + // find the correct position to insert + while (cur->next && cur->next->priority < priority) + cur = cur->next; + entry->next = cur->next; + cur->next = entry; + } + } + + void ProcUIRegisterCallbackCore(ProcUICallbackId callbackType, void* funcPtr, void* userParam, sint32 priority, uint32 coreIndex) + { + if(callbackType >= ProcUICallbackId::COUNT) + { + cemuLog_log(LogType::Force, "ProcUIRegisterCallback: Invalid callback type {}", stdx::to_underlying(callbackType)); + return; + } + if(callbackType != ProcUICallbackId::AcquireForeground) + priority = -priority; + AddCallbackInternal(funcPtr, userParam, priority, 0, s_CallbackTables[stdx::to_underlying(callbackType)][coreIndex]); + } + + void ProcUIRegisterCallback(ProcUICallbackId callbackType, void* funcPtr, void* userParam, sint32 priority) + { + ProcUIRegisterCallbackCore(callbackType, funcPtr, userParam, priority, OSGetCoreId()); + } + + void ProcUIRegisterBackgroundCallback(void* funcPtr, void* userParam, uint64 tickDelay) + { + AddCallbackInternal(funcPtr, userParam, 0, tickDelay, s_backgroundCallbackList); + } + + void FreeCallbackChain(ProcUICallbackList& callbackList) + { + ProcUIInternalCallbackEntry* entry = callbackList.first; + while (entry) + { + ProcUIInternalCallbackEntry* next = entry->next; + _FreeMem(entry); + entry = next; + } + callbackList.first = nullptr; + } + + void ProcUIClearCallbacks() + { + for (sint32 coreIndex = 0; coreIndex < Espresso::CORE_COUNT; coreIndex++) + { + for (sint32 i = 0; i < stdx::to_underlying(ProcUICallbackId::COUNT); i++) + { + FreeCallbackChain(s_CallbackTables[i][coreIndex]); + } + } + if (!s_isInForeground) + CancelBackgroundAlarms(); + FreeCallbackChain(s_backgroundCallbackList); + } + + void ProcUISetSaveCallback(void* funcPtr, void* userParam) + { + s_saveCallback = nullptr; + s_saveCallbackEx = funcPtr; + s_saveCallbackExUserParam = userParam; + } + + void ProcUISetCallbackStackSize(uint32 newStackSize) + { + s_coreThreadStackSize = newStackSize; + if( s_isInitialized ) + RecreateProcUICoreThreads(); + } + + uint32 ProcUICalcMemorySize(uint32 numCallbacks) + { + // each callback entry is 0x70 bytes with 0x14 bytes of allocator overhead (for ExpHeap). But for some reason proc_ui on 5.5.5 seems to reserve 0x8C0 bytes per callback? + uint32 stackReserveSize = (Espresso::CORE_COUNT + 1) * s_coreThreadStackSize; // 3 core threads + 1 message receive thread + uint32 callbackReserveSize = 0x8C0 * numCallbacks; + return stackReserveSize + callbackReserveSize + 100; + } + + void _MemAllocFromMemoryPool(PPCInterpreter_t* hCPU) + { + uint32 size = hCPU->gpr[3]; + MEMPTR<void> r = MEMAllocFromExpHeapEx((MEMHeapHandle)s_memoryPoolHeapPtr.GetPtr(), size, 4); + osLib_returnFromFunction(hCPU, r.GetMPTR()); + } + + void _FreeToMemoryPoolExpHeap(PPCInterpreter_t* hCPU) + { + MEMPTR<void> mem{hCPU->gpr[3]}; + MEMFreeToExpHeap((MEMHeapHandle)s_memoryPoolHeapPtr.GetPtr(), mem.GetPtr()); + osLib_returnFromFunction(hCPU, 0); + } + + sint32 ProcUISetMemoryPool(void* memBase, uint32 size) + { + s_memAllocPtr = RPLLoader_MakePPCCallable(_MemAllocFromMemoryPool); + s_memFreePtr = RPLLoader_MakePPCCallable(_FreeToMemoryPoolExpHeap); + s_memoryPoolHeapPtr = MEMCreateExpHeapEx(memBase, size, MEM_HEAP_OPTION_THREADSAFE); + return s_memoryPoolHeapPtr ? 0 : -1; + } + + void ProcUISetBucketStorage(void* memBase, uint32 size) + { + MEMPTR<void> fgBase; + uint32be fgFreeSize; + OSGetForegroundBucketFreeArea((MPTR*)&fgBase, (MPTR*)&fgFreeSize); + if(fgFreeSize < size) + cemuLog_log(LogType::Force, "ProcUISetBucketStorage: Buffer size too small"); + s_bucketStorageBasePtr = memBase; + } + + void ProcUISetMEM1Storage(void* memBase, uint32 size) + { + MEMPTR<void> memBound; + uint32be memBoundSize; + OSGetMemBound(1, (MPTR*)memBound.GetBEPtr(), (uint32*)&memBoundSize); + if(memBoundSize < size) + cemuLog_log(LogType::Force, "ProcUISetMEM1Storage: Buffer size too small"); + s_mem1StorageBasePtr = memBase; + } + + void ProcUIDrawDoneRelease() + { + s_drawDoneReleaseCalled = true; + } + + OSMessage g_lastMsg; + + void ProcUI_BackgroundThread_ReceiveSingleMessage(PPCInterpreter_t* hCPU) + { + // the background thread receives messages in a loop until the title is either exited or foreground is acquired + while ( true ) + { + OSReceiveMessage(s_systemMessageQueuePtr, &g_lastMsg, OS_MESSAGE_BLOCK); // blocking receive + SysMessageId lastMsgId = static_cast<SysMessageId>((uint32)g_lastMsg.data0); + if(lastMsgId == SysMessageId::MsgExit || lastMsgId == SysMessageId::MsgAcquireForeground) + break; + else if (lastMsgId == SysMessageId::HomeButtonDenied) + { + cemu_assert_suspicious(); // Home button denied should not be sent to background app + } + else if ( lastMsgId == SysMessageId::NetIoStartOrStop ) + { + if (g_lastMsg.data1 ) + { + // NetIo start message + for (sint32 i = 0; i < Espresso::CORE_COUNT; i++) + DoCallbackChain(s_callbacksType3_NetIoStart[i].first); + } + else + { + // NetIo stop message + for (sint32 i = 0; i < Espresso::CORE_COUNT; i++) + DoCallbackChain(s_callbacksType4_NetIoStop[i].first); + } + } + else + { + cemuLog_log(LogType::Force, "ProcUI: BackgroundThread received invalid message 0x{:08x}", lastMsgId); + } + } + OSSignalEvent(&s_eventBackgroundThreadGotMessage); + osLib_returnFromFunction(hCPU, 0); + } + + // handle received message + // if the message is Exit this function returns false, in all other cases it returns true + bool ProcessSysMessage(OSMessage* msg) + { + SysMessageId lastMsgId = static_cast<SysMessageId>((uint32)msg->data0); + if ( lastMsgId == SysMessageId::MsgAcquireForeground ) + { + cemuLog_logDebug(LogType::Force, "ProcUI: Received Acquire Foreground message"); + s_isInShutdown = false; + _SubmitCommandToCoreThreads(ProcUICoreThreadCommand::AcquireForeground); + s_currentProcUIStatus = ProcUIStatus::Foreground; + s_isInForeground = true; + OSMemoryBarrier(); + OSSignalEvent(&s_eventStateMessageReceived); + return true; + } + else if (lastMsgId == SysMessageId::MsgExit) + { + cemuLog_logDebug(LogType::Force, "ProcUI: Received Exit message"); + s_isInShutdown = true; + _SubmitCommandToCoreThreads(ProcUICoreThreadCommand::Exit); + for (sint32 i = 0; i < Espresso::CORE_COUNT; i++) + FreeCallbackChain(s_callbacksType2_Exit[i]); + s_currentProcUIStatus = ProcUIStatus::Exit; + OSMemoryBarrier(); + OSSignalEvent(&s_eventStateMessageReceived); + return 0; + } + if (lastMsgId == SysMessageId::MsgReleaseForeground) + { + if (msg->data1 != 0) + { + cemuLog_logDebug(LogType::Force, "ProcUI: Received Release Foreground message as part of shutdown initiation"); + s_isInShutdown = true; + } + else + { + cemuLog_logDebug(LogType::Force, "ProcUI: Received Release Foreground message"); + } + s_currentProcUIStatus = ProcUIStatus::Releasing; + OSResetEvent(&s_eventStateMessageReceived); + // dont submit a command for the core threads yet, we need to wait for ProcUIDrawDoneRelease() + } + else if (lastMsgId == SysMessageId::HomeButtonDenied) + { + cemuLog_logDebug(LogType::Force, "ProcUI: Received Home Button Denied message"); + _SubmitCommandToCoreThreads(ProcUICoreThreadCommand::HomeButtonDenied); + } + else if ( lastMsgId == SysMessageId::NetIoStartOrStop ) + { + if (msg->data1 != 0) + { + cemuLog_logDebug(LogType::Force, "ProcUI: Received Net IO Start message"); + _SubmitCommandToCoreThreads(ProcUICoreThreadCommand::NetIoStart); + } + else + { + cemuLog_logDebug(LogType::Force, "ProcUI: Received Net IO Stop message"); + _SubmitCommandToCoreThreads(ProcUICoreThreadCommand::NetIoStop); + } + } + else + { + cemuLog_log(LogType::Force, "ProcUI: Received unknown message 0x{:08x}", (uint32)lastMsgId); + } + return true; + } + + ProcUIStatus ProcUIProcessMessages(bool isBlockingInBackground) + { + OSMessage msg; + if (!s_isInitialized) + { + cemuLog_logOnce(LogType::Force, "ProcUIProcessMessages: ProcUI not initialized"); + cemu_assert_suspicious(); + return ProcUIStatus::Foreground; + } + if ( !isBlockingInBackground && OSGetCoreId() != 2 ) + { + cemuLog_logOnce(LogType::Force, "ProcUIProcessMessages: Non-blocking call must run on core 2"); + } + if (s_previouslyWasBlocking && isBlockingInBackground ) + { + cemuLog_logOnce(LogType::Force, "ProcUIProcessMessages: Cannot switch to blocking mode when in background"); + } + s_currentProcUIStatus = s_isInForeground ? ProcUIStatus::Foreground : ProcUIStatus::Background; + if (s_drawDoneReleaseCalled) + { + s_isInForeground = false; + s_currentProcUIStatus = ProcUIStatus::Background; + _SubmitCommandToCoreThreads(ProcUICoreThreadCommand::ReleaseForeground); + OSResetEvent(&s_eventWaitingBeforeReleaseForeground); + if(s_saveCallback) + PPCCoreCallback(s_saveCallback); + if(s_saveCallbackEx) + PPCCoreCallback(s_saveCallbackEx, s_saveCallbackExUserParam); + if (s_isForegroundProcess && isBlockingInBackground) + { + // start background thread + __OSCreateThreadType(&s_backgroundThread, RPLLoader_MakePPCCallable(ProcUI_BackgroundThread_ReceiveSingleMessage), + 0, nullptr, (uint8*)s_backgroundThreadStack.GetPtr() + s_coreThreadStackSize, s_coreThreadStackSize, + 16, (1<<2), OSThread_t::THREAD_TYPE::TYPE_DRIVER); + OSResumeThread(&s_backgroundThread); + s_previouslyWasBlocking = true; + } + cemuLog_logDebug(LogType::Force, "ProcUI: Releasing foreground"); + OSSignalEvent(&s_eventWaitingBeforeReleaseForeground); + s_drawDoneReleaseCalled = false; + } + if (s_isInForeground || !isBlockingInBackground) + { + // non-blocking mode + if ( OSReceiveMessage(s_systemMessageQueuePtr, &msg, 0) ) + { + s_previouslyWasBlocking = false; + if ( !ProcessSysMessage(&msg) ) + return s_currentProcUIStatus; + // continue below, if we are now in background then ProcUIProcessMessages enters blocking mode + } + } + // blocking mode (if in background and param is true) + while (!s_isInForeground && isBlockingInBackground) + { + if ( !s_isForegroundProcess) + { + OSReceiveMessage(s_systemMessageQueuePtr, &msg, OS_MESSAGE_BLOCK); + s_previouslyWasBlocking = false; + if ( !ProcessSysMessage(&msg) ) + return s_currentProcUIStatus; + } + // this code should only run if the background thread was started? Maybe rearrange the code to make this more clear + OSWaitEvent(&s_eventBackgroundThreadGotMessage); + OSResetEvent(&s_eventBackgroundThreadGotMessage); + OSJoinThread(&s_backgroundThread, nullptr); + msg = g_lastMsg; // g_lastMsg is set by the background thread + s_previouslyWasBlocking = false; + if ( !ProcessSysMessage(&msg) ) + return s_currentProcUIStatus; + } + return s_currentProcUIStatus; + } + + ProcUIStatus ProcUISubProcessMessages(bool isBlockingInBackground) + { + if (isBlockingInBackground) + { + while (s_currentProcUIStatus == ProcUIStatus::Background) + OSWaitEvent(&s_eventStateMessageReceived); + } + return s_currentProcUIStatus; + } + + const char* ProcUIDriver_GetName() + { + s_ProcUIDriverName->assign("ProcUI"); + return s_ProcUIDriverName->c_str(); + } + + void ProcUIDriver_Init(/* parameters unknown */) + { + s_driverIsActive = true; + OSMemoryBarrier(); + } + + void ProcUIDriver_OnDone(/* parameters unknown */) + { + if (s_driverIsActive) + { + ProcUIShutdown(); + s_driverIsActive = false; + OSMemoryBarrier(); + } + } + + void StoreMEM1AndFGBucket() + { + if (s_mem1StorageBasePtr) + { + MEMPTR<void> memBound; + uint32be memBoundSize; + OSGetMemBound(1, (MPTR*)memBound.GetBEPtr(), (uint32*)&memBoundSize); + OSBlockMove(s_mem1StorageBasePtr.GetPtr(), memBound.GetPtr(), memBoundSize, true); + } + if (s_bucketStorageBasePtr) + { + MEMPTR<void> memBound; + uint32be memBoundSize; + OSGetForegroundBucketFreeArea((MPTR*)memBound.GetBEPtr(), (MPTR*)&memBoundSize); + OSBlockMove(s_bucketStorageBasePtr.GetPtr(), memBound.GetPtr(), memBoundSize, true); + } + } + + void RestoreMEM1AndFGBucket() + { + if (s_mem1StorageBasePtr) + { + MEMPTR<void> memBound; + uint32be memBoundSize; + OSGetMemBound(1, (MPTR*)memBound.GetBEPtr(), (uint32*)&memBoundSize); + OSBlockMove(memBound.GetPtr(), s_mem1StorageBasePtr, memBoundSize, true); + GX2::GX2Invalidate(0x40, s_mem1StorageBasePtr.GetMPTR(), memBoundSize); + } + if (s_bucketStorageBasePtr) + { + MEMPTR<void> memBound; + uint32be memBoundSize; + OSGetForegroundBucketFreeArea((MPTR*)memBound.GetBEPtr(), (MPTR*)&memBoundSize); + OSBlockMove(memBound.GetPtr(), s_bucketStorageBasePtr, memBoundSize, true); + GX2::GX2Invalidate(0x40, memBound.GetMPTR(), memBoundSize); + } + } + + void ProcUIDriver_OnAcquiredForeground(/* parameters unknown */) + { + if (s_driverInBackground) + { + ProcUIDriver_Init(); + s_driverInBackground = false; + } + else + { + RestoreMEM1AndFGBucket(); + s_driverIsActive = true; + OSMemoryBarrier(); + } + } + + void ProcUIDriver_OnReleaseForeground(/* parameters unknown */) + { + StoreMEM1AndFGBucket(); + s_driverIsActive = false; + OSMemoryBarrier(); + } + + sint32 rpl_entry(uint32 moduleHandle, RplEntryReason reason) + { + if ( reason == RplEntryReason::Loaded ) + { + s_ProcUIDriver->getDriverName = RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) {MEMPTR<const char> namePtr(ProcUIDriver_GetName()); osLib_returnFromFunction(hCPU, namePtr.GetMPTR()); }); + s_ProcUIDriver->init = RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) {ProcUIDriver_Init(); osLib_returnFromFunction(hCPU, 0); }); + s_ProcUIDriver->onAcquireForeground = RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) {ProcUIDriver_OnAcquiredForeground(); osLib_returnFromFunction(hCPU, 0); }); + s_ProcUIDriver->onReleaseForeground = RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) {ProcUIDriver_OnReleaseForeground(); osLib_returnFromFunction(hCPU, 0); }); + s_ProcUIDriver->done = RPLLoader_MakePPCCallable([](PPCInterpreter_t* hCPU) {ProcUIDriver_OnDone(); osLib_returnFromFunction(hCPU, 0); }); + + s_driverIsActive = false; + s_driverArgUkn1 = 0; + s_driverArgUkn2 = 0; + s_driverInBackground = false; + uint32be ukn3; + OSDriver_Register(moduleHandle, 200, &s_ProcUIDriver, 0, &s_driverArgUkn1, &s_driverArgUkn2, &ukn3); + if ( ukn3 ) + { + if ( OSGetForegroundBucket(nullptr, nullptr) ) + { + ProcUIDriver_Init(); + OSMemoryBarrier(); + return 0; + } + s_driverInBackground = true; + } + OSMemoryBarrier(); + } + else if ( reason == RplEntryReason::Unloaded ) + { + ProcUIDriver_OnDone(); + OSDriver_Deregister(moduleHandle, 0); + } + return 0; + } + + void reset() + { + // set variables to their initial state as if the RPL was just loaded + s_isInitialized = false; + s_isInShutdown = false; + s_isInForeground = false; // ProcUIInForeground returns false until ProcUIInit(Ex) is called + s_isForegroundProcess = true; + s_saveCallback = nullptr; + s_saveCallbackEx = nullptr; + s_systemMessageQueuePtr = nullptr; + ClearCallbacksWithoutMemFree(); + s_currentProcUIStatus = ProcUIStatus::Foreground; + s_bucketStorageBasePtr = nullptr; + s_mem1StorageBasePtr = nullptr; + s_drawDoneReleaseCalled = false; + s_previouslyWasBlocking = false; + // core threads + s_coreThreadStackSize = 0; + s_coreThreadsCreated = false; + s_commandForCoreThread = ProcUICoreThreadCommand::Initial; + // background thread + s_backgroundThreadStack = nullptr; + // user defined heap + s_memoryPoolHeapPtr = nullptr; + s_memAllocPtr = nullptr; + s_memFreePtr = nullptr; + // driver + s_driverIsActive = false; + s_driverInBackground = false; + } + + void load() + { + reset(); + + cafeExportRegister("proc_ui", ProcUIInit, LogType::ProcUi); + cafeExportRegister("proc_ui", ProcUIInitEx, LogType::ProcUi); + cafeExportRegister("proc_ui", ProcUIShutdown, LogType::ProcUi); + cafeExportRegister("proc_ui", ProcUIIsRunning, LogType::ProcUi); + cafeExportRegister("proc_ui", ProcUIInForeground, LogType::ProcUi); + cafeExportRegister("proc_ui", ProcUIInShutdown, LogType::ProcUi); + + cafeExportRegister("proc_ui", ProcUIRegisterCallbackCore, LogType::ProcUi); + cafeExportRegister("proc_ui", ProcUIRegisterCallback, LogType::ProcUi); + cafeExportRegister("proc_ui", ProcUIRegisterBackgroundCallback, LogType::ProcUi); + cafeExportRegister("proc_ui", ProcUIClearCallbacks, LogType::ProcUi); + cafeExportRegister("proc_ui", ProcUISetSaveCallback, LogType::ProcUi); + + cafeExportRegister("proc_ui", ProcUISetCallbackStackSize, LogType::ProcUi); + cafeExportRegister("proc_ui", ProcUICalcMemorySize, LogType::ProcUi); + cafeExportRegister("proc_ui", ProcUISetMemoryPool, LogType::ProcUi); + cafeExportRegister("proc_ui", ProcUISetBucketStorage, LogType::ProcUi); + cafeExportRegister("proc_ui", ProcUISetMEM1Storage, LogType::ProcUi); + + cafeExportRegister("proc_ui", ProcUIDrawDoneRelease, LogType::ProcUi); + cafeExportRegister("proc_ui", ProcUIProcessMessages, LogType::ProcUi); + cafeExportRegister("proc_ui", ProcUISubProcessMessages, LogType::ProcUi); + + // manually call rpl_entry for now + rpl_entry(-1, RplEntryReason::Loaded); + } }; -std::unordered_map<uint32, ProcUICallback> g_Callbacks; - -uint32 ProcUIRegisterCallback(uint32 message, MPTR callback, void* data, sint32 priority) -{ - g_Callbacks.insert_or_assign(message, ProcUICallback{ .callback = callback, .data = data, .priority = priority }); - return 0; -} - -void ProcUI_SendBackgroundMessage() -{ - if (g_Callbacks.contains(PROCUI_STATUS_BACKGROUND)) - { - ProcUICallback& callback = g_Callbacks[PROCUI_STATUS_BACKGROUND]; - PPCCoreCallback(callback.callback, callback.data); - } -} - -void ProcUI_SendForegroundMessage() -{ - if (g_Callbacks.contains(PROCUI_STATUS_FOREGROUND)) - { - ProcUICallback& callback = g_Callbacks[PROCUI_STATUS_FOREGROUND]; - PPCCoreCallback(callback.callback, callback.data); - } -} - -void procui_load() -{ - cafeExportRegister("proc_ui", ProcUIRegisterCallback, LogType::ProcUi); - cafeExportRegister("proc_ui", ProcUIProcessMessages, LogType::ProcUi); - cafeExportRegister("proc_ui", ProcUIInForeground, LogType::ProcUi); -} \ No newline at end of file diff --git a/src/Cafe/OS/libs/proc_ui/proc_ui.h b/src/Cafe/OS/libs/proc_ui/proc_ui.h index 1cd04fb1..8de7bb4d 100644 --- a/src/Cafe/OS/libs/proc_ui/proc_ui.h +++ b/src/Cafe/OS/libs/proc_ui/proc_ui.h @@ -1,5 +1,44 @@ -void procui_load(); +namespace proc_ui +{ + enum class ProcUIStatus + { + Foreground = 0, + Background = 1, + Releasing = 2, + Exit = 3 + }; -void ProcUI_SendForegroundMessage(); -void ProcUI_SendBackgroundMessage(); \ No newline at end of file + enum class ProcUICallbackId + { + AcquireForeground = 0, + ReleaseForeground = 1, + Exit = 2, + NetIoStart = 3, + NetIoStop = 4, + HomeButtonDenied = 5, + COUNT = 6 + }; + + void ProcUIInit(MEMPTR<void> callbackReadyToRelease); + void ProcUIInitEx(MEMPTR<void> callbackReadyToReleaseEx, MEMPTR<void> userParam); + void ProcUIShutdown(); + bool ProcUIIsRunning(); + bool ProcUIInForeground(); + bool ProcUIInShutdown(); + void ProcUIRegisterCallback(ProcUICallbackId callbackType, void* funcPtr, void* userParam, sint32 priority); + void ProcUIRegisterCallbackCore(ProcUICallbackId callbackType, void* funcPtr, void* userParam, sint32 priority, uint32 coreIndex); + void ProcUIRegisterBackgroundCallback(void* funcPtr, void* userParam, uint64 tickDelay); + void ProcUIClearCallbacks(); + void ProcUISetSaveCallback(void* funcPtr, void* userParam); + void ProcUISetCallbackStackSize(uint32 newStackSize); + uint32 ProcUICalcMemorySize(uint32 numCallbacks); + sint32 ProcUISetMemoryPool(void* memBase, uint32 size); + void ProcUISetBucketStorage(void* memBase, uint32 size); + void ProcUISetMEM1Storage(void* memBase, uint32 size); + void ProcUIDrawDoneRelease(); + ProcUIStatus ProcUIProcessMessages(bool isBlockingInBackground); + ProcUIStatus ProcUISubProcessMessages(bool isBlockingInBackground); + + void load(); +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/sysapp/sysapp.cpp b/src/Cafe/OS/libs/sysapp/sysapp.cpp index 413d535a..ecaa940a 100644 --- a/src/Cafe/OS/libs/sysapp/sysapp.cpp +++ b/src/Cafe/OS/libs/sysapp/sysapp.cpp @@ -639,11 +639,34 @@ namespace sysapp return coreinit::OSRestartGame(argc, argv); } + struct EManualArgs + { + sysStandardArguments_t stdArgs; + uint64be titleId; + }; + static_assert(sizeof(EManualArgs) == 0x10); + + void _SYSSwitchToEManual(EManualArgs* args) + { + // the struct has the titleId at offset 8 and standard args at 0 (total size is most likely 0x10) + cemuLog_log(LogType::Force, "SYSSwitchToEManual called. Opening the manual is not supported"); + coreinit::StartBackgroundForegroundTransition(); + } + + void SYSSwitchToEManual() + { + EManualArgs args{}; + args.titleId = coreinit::OSGetTitleID(); + _SYSSwitchToEManual(&args); + } + void load() { cafeExportRegisterFunc(SYSClearSysArgs, "sysapp", "SYSClearSysArgs", LogType::Placeholder); cafeExportRegisterFunc(_SYSLaunchTitleByPathFromLauncher, "sysapp", "_SYSLaunchTitleByPathFromLauncher", LogType::Placeholder); cafeExportRegisterFunc(SYSRelaunchTitle, "sysapp", "SYSRelaunchTitle", LogType::Placeholder); + cafeExportRegister("sysapp", _SYSSwitchToEManual, LogType::Placeholder); + cafeExportRegister("sysapp", SYSSwitchToEManual, LogType::Placeholder); } } diff --git a/src/Common/CafeString.h b/src/Common/CafeString.h index 45a515b1..d902d721 100644 --- a/src/Common/CafeString.h +++ b/src/Common/CafeString.h @@ -20,6 +20,11 @@ class CafeString // fixed buffer size, null-terminated, PPC char return true; } + const char* c_str() + { + return (const char*)data; + } + uint8be data[N]; }; From e7c6862e19a277d0d8828c99a6874e69eedbd802 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 1 May 2024 01:55:55 +0200 Subject: [PATCH 089/299] DownloadManager: Fix missing updates --- src/Cemu/napi/napi_version.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Cemu/napi/napi_version.cpp b/src/Cemu/napi/napi_version.cpp index a1f5879c..5a85dde3 100644 --- a/src/Cemu/napi/napi_version.cpp +++ b/src/Cemu/napi/napi_version.cpp @@ -31,7 +31,7 @@ namespace NAPI requestUrl = NintendoURLs::TAGAYAURL; break; } - requestUrl.append(fmt::format(fmt::runtime("/{}/{}/latest_version"), NCrypto::GetRegionAsString(authInfo.region), authInfo.country)); + requestUrl.append(fmt::format(fmt::runtime("/{}/{}/latest_version"), NCrypto::GetRegionAsString(authInfo.region), authInfo.country.empty() ? "NN" : authInfo.country)); req.initate(authInfo.GetService(), requestUrl, CurlRequestHelper::SERVER_SSL_CONTEXT::TAGAYA); if (!req.submitRequest(false)) @@ -63,7 +63,7 @@ namespace NAPI { NAPI_VersionList_Result result; CurlRequestHelper req; - req.initate(authInfo.GetService(), fmt::format("https://{}/tagaya/versionlist/{}/{}/list/{}.versionlist", fqdnURL, NCrypto::GetRegionAsString(authInfo.region), authInfo.country, versionListVersion), CurlRequestHelper::SERVER_SSL_CONTEXT::TAGAYA); + req.initate(authInfo.GetService(), fmt::format("https://{}/tagaya/versionlist/{}/{}/list/{}.versionlist", fqdnURL, NCrypto::GetRegionAsString(authInfo.region), authInfo.country.empty() ? "NN" : authInfo.country, versionListVersion), CurlRequestHelper::SERVER_SSL_CONTEXT::TAGAYA); if (!req.submitRequest(false)) { cemuLog_log(LogType::Force, fmt::format("Failed to request update list")); From 379950d185852b3c2da14b40e30a872809ad0ac2 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 1 May 2024 05:06:50 +0200 Subject: [PATCH 090/299] coreinit+nn_save: Cleanup some legacy code --- src/Cafe/OS/libs/coreinit/coreinit_FG.cpp | 20 +- src/Cafe/OS/libs/coreinit/coreinit_FG.h | 2 +- src/Cafe/OS/libs/coreinit/coreinit_FS.cpp | 243 +++++++------ src/Cafe/OS/libs/coreinit/coreinit_FS.h | 84 ++--- src/Cafe/OS/libs/coreinit/coreinit_MEM.cpp | 10 +- src/Cafe/OS/libs/coreinit/coreinit_Memory.cpp | 6 +- src/Cafe/OS/libs/coreinit/coreinit_Memory.h | 2 +- src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp | 6 +- src/Cafe/OS/libs/nn_save/nn_save.cpp | 339 ++++++------------ src/Cafe/OS/libs/proc_ui/proc_ui.cpp | 12 +- src/Common/MemPtr.h | 6 - src/Common/StackAllocator.h | 1 - 12 files changed, 282 insertions(+), 449 deletions(-) diff --git a/src/Cafe/OS/libs/coreinit/coreinit_FG.cpp b/src/Cafe/OS/libs/coreinit/coreinit_FG.cpp index 15dcd6da..b751a8fd 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_FG.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_FG.cpp @@ -55,19 +55,18 @@ namespace coreinit { // return full size of foreground bucket area if (offset) - *offset = MEMPTR<void>{ (uint32)MEMORY_FGBUCKET_AREA_ADDR }; + *offset = { (MPTR)MEMORY_FGBUCKET_AREA_ADDR }; if (size) *size = MEMORY_FGBUCKET_AREA_SIZE; // return true if in foreground return true; } - bool OSGetForegroundBucketFreeArea(MPTR* offset, MPTR* size) + bool OSGetForegroundBucketFreeArea(MEMPTR<void>* offset, uint32be* size) { uint8* freeAreaAddr = GetFGMemByArea(FG_BUCKET_AREA_FREE).GetPtr(); - - *offset = _swapEndianU32(memory_getVirtualOffsetFromPointer(freeAreaAddr)); - *size = _swapEndianU32(FG_BUCKET_AREA_FREE_SIZE); + *offset = freeAreaAddr; + *size = FG_BUCKET_AREA_FREE_SIZE; // return true if in foreground return (fgAddr != nullptr); } @@ -82,15 +81,6 @@ namespace coreinit osLib_returnFromFunction(hCPU, r ? 1 : 0); } - void coreinitExport_OSGetForegroundBucketFreeArea(PPCInterpreter_t* hCPU) - { - debug_printf("OSGetForegroundBucketFreeArea(0x%x,0x%x)\n", hCPU->gpr[3], hCPU->gpr[4]); - ppcDefineParamMPTR(areaOutput, 0); - ppcDefineParamMPTR(areaSize, 1); - bool r = OSGetForegroundBucketFreeArea((MPTR*)memory_getPointerFromVirtualOffsetAllowNull(areaOutput), (MPTR*)memory_getPointerFromVirtualOffsetAllowNull(areaSize)); - osLib_returnFromFunction(hCPU, r ? 1 : 0); - } - void InitForegroundBucket() { uint32be fgSize; @@ -194,7 +184,7 @@ namespace coreinit void InitializeFG() { osLib_addFunction("coreinit", "OSGetForegroundBucket", coreinitExport_OSGetForegroundBucket); - osLib_addFunction("coreinit", "OSGetForegroundBucketFreeArea", coreinitExport_OSGetForegroundBucketFreeArea); + cafeExportRegister("coreinit", OSGetForegroundBucket, LogType::CoreinitMem); osLib_addFunction("coreinit", "OSCopyFromClipboard", coreinitExport_OSCopyFromClipboard); } } diff --git a/src/Cafe/OS/libs/coreinit/coreinit_FG.h b/src/Cafe/OS/libs/coreinit/coreinit_FG.h index 846001b9..0c2a3ee3 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_FG.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_FG.h @@ -9,7 +9,7 @@ namespace coreinit bool __OSResizeCopyData(sint32 length); bool OSGetForegroundBucket(MEMPTR<void>* offset, uint32be* size); - bool OSGetForegroundBucketFreeArea(MPTR* offset, MPTR* size); + bool OSGetForegroundBucketFreeArea(MEMPTR<void>* offset, uint32be* size); void InitForegroundBucket(); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp b/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp index 0ca8fb8e..0fc8912f 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp @@ -56,7 +56,7 @@ namespace coreinit OSUnlockMutex(&s_fsGlobalMutex); } - void _debugVerifyCommand(const char* stage, FSCmdBlockBody_t* fsCmdBlockBody); + void _debugVerifyCommand(const char* stage, FSCmdBlockBody* fsCmdBlockBody); bool sFSInitialized = true; // this should be false but it seems like some games rely on FSInit being called before main()? Twilight Princess for example reads files before it calls FSInit bool sFSShutdown = false; @@ -194,12 +194,12 @@ namespace coreinit } // return the aligned FSClientBody struct inside a FSClient struct - FSCmdBlockBody_t* __FSGetCmdBlockBody(FSCmdBlock_t* fsCmdBlock) + FSCmdBlockBody* __FSGetCmdBlockBody(FSCmdBlock_t* fsCmdBlock) { // align pointer to 64 bytes if (fsCmdBlock == nullptr) return nullptr; - FSCmdBlockBody_t* fsCmdBlockBody = (FSCmdBlockBody_t*)(((uintptr_t)fsCmdBlock + 0x3F) & ~0x3F); + FSCmdBlockBody* fsCmdBlockBody = (FSCmdBlockBody*)(((uintptr_t)fsCmdBlock + 0x3F) & ~0x3F); fsCmdBlockBody->selfCmdBlock = fsCmdBlock; return fsCmdBlockBody; } @@ -261,8 +261,8 @@ namespace coreinit fsCmdQueueBE->numCommandsInFlight = 0; fsCmdQueueBE->numMaxCommandsInFlight = numMaxCommandsInFlight; coreinit::OSFastMutex_Init(&fsCmdQueueBE->fastMutex, nullptr); - fsCmdQueueBE->firstMPTR = _swapEndianU32(0); - fsCmdQueueBE->lastMPTR = _swapEndianU32(0); + fsCmdQueueBE->first = nullptr; + fsCmdQueueBE->last = nullptr; } void FSInit() @@ -382,74 +382,71 @@ namespace coreinit Semaphore g_semaphoreQueuedCmds; - void __FSQueueCmdByPriority(FSCmdQueue* fsCmdQueueBE, FSCmdBlockBody_t* fsCmdBlockBody, bool stopAtEqualPriority) + void __FSQueueCmdByPriority(FSCmdQueue* fsCmdQueueBE, FSCmdBlockBody* fsCmdBlockBody, bool stopAtEqualPriority) { - MPTR fsCmdBlockBodyMPTR = memory_getVirtualOffsetFromPointer(fsCmdBlockBody); - if (_swapEndianU32(fsCmdQueueBE->firstMPTR) == MPTR_NULL) + if (!fsCmdQueueBE->first) { // queue is currently empty - cemu_assert(fsCmdQueueBE->lastMPTR == MPTR_NULL); - fsCmdQueueBE->firstMPTR = _swapEndianU32(fsCmdBlockBodyMPTR); - fsCmdQueueBE->lastMPTR = _swapEndianU32(fsCmdBlockBodyMPTR); - fsCmdBlockBody->nextMPTR = _swapEndianU32(MPTR_NULL); - fsCmdBlockBody->previousMPTR = _swapEndianU32(MPTR_NULL); + cemu_assert(!fsCmdQueueBE->last); + fsCmdQueueBE->first = fsCmdBlockBody; + fsCmdQueueBE->last = fsCmdBlockBody; + fsCmdBlockBody->next = nullptr; + fsCmdBlockBody->previous = nullptr; return; } // iterate from last to first element as long as iterated priority is lower - FSCmdBlockBody_t* fsCmdBlockBodyItrPrev = NULL; - FSCmdBlockBody_t* fsCmdBlockBodyItr = (FSCmdBlockBody_t*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(fsCmdQueueBE->lastMPTR)); + FSCmdBlockBody* fsCmdBlockBodyItrPrev = nullptr; + FSCmdBlockBody* fsCmdBlockBodyItr = fsCmdQueueBE->last; while (true) { - if (fsCmdBlockBodyItr == NULL) + if (!fsCmdBlockBodyItr) { // insert at the head of the list - fsCmdQueueBE->firstMPTR = _swapEndianU32(fsCmdBlockBodyMPTR); - fsCmdBlockBody->nextMPTR = _swapEndianU32(memory_getVirtualOffsetFromPointer(fsCmdBlockBodyItrPrev)); - fsCmdBlockBody->previousMPTR = _swapEndianU32(MPTR_NULL); - fsCmdBlockBodyItrPrev->previousMPTR = _swapEndianU32(memory_getVirtualOffsetFromPointer(fsCmdBlockBody)); + fsCmdQueueBE->first = fsCmdBlockBody; + fsCmdBlockBody->next = fsCmdBlockBodyItrPrev; + fsCmdBlockBody->previous = nullptr; + fsCmdBlockBodyItrPrev->previous = fsCmdBlockBody; return; } // compare priority if ((stopAtEqualPriority && fsCmdBlockBodyItr->priority >= fsCmdBlockBody->priority) || (stopAtEqualPriority == false && fsCmdBlockBodyItr->priority > fsCmdBlockBody->priority)) { // insert cmd here - if (fsCmdBlockBodyItrPrev != NULL) + if (fsCmdBlockBodyItrPrev) { - fsCmdBlockBody->nextMPTR = _swapEndianU32(memory_getVirtualOffsetFromPointer(fsCmdBlockBodyItrPrev)); + fsCmdBlockBody->next = fsCmdBlockBodyItrPrev; } else { - fsCmdBlockBody->nextMPTR = _swapEndianU32(MPTR_NULL); - fsCmdQueueBE->lastMPTR = _swapEndianU32(fsCmdBlockBodyMPTR); + fsCmdBlockBody->next = nullptr; + fsCmdQueueBE->last = fsCmdBlockBody; } - fsCmdBlockBody->previousMPTR = _swapEndianU32(memory_getVirtualOffsetFromPointer(fsCmdBlockBodyItr)); + fsCmdBlockBody->previous = fsCmdBlockBodyItr; if (fsCmdBlockBodyItrPrev) - fsCmdBlockBodyItrPrev->previousMPTR = _swapEndianU32(memory_getVirtualOffsetFromPointer(fsCmdBlockBody)); - fsCmdBlockBodyItr->nextMPTR = _swapEndianU32(memory_getVirtualOffsetFromPointer(fsCmdBlockBody)); + fsCmdBlockBodyItrPrev->previous = fsCmdBlockBody; + fsCmdBlockBodyItr->next = fsCmdBlockBody; return; } // next fsCmdBlockBodyItrPrev = fsCmdBlockBodyItr; - fsCmdBlockBodyItr = (FSCmdBlockBody_t*)memory_getPointerFromVirtualOffsetAllowNull(_swapEndianU32(fsCmdBlockBodyItr->previousMPTR)); + fsCmdBlockBodyItr = fsCmdBlockBodyItr->previous; } } - FSCmdBlockBody_t* __FSTakeCommandFromQueue(FSCmdQueue* cmdQueue) + FSCmdBlockBody* __FSTakeCommandFromQueue(FSCmdQueue* cmdQueue) { - FSCmdBlockBody_t* dequeuedCmd = nullptr; - if (_swapEndianU32(cmdQueue->firstMPTR) != MPTR_NULL) + if (!cmdQueue->first) + return nullptr; + // dequeue cmd + FSCmdBlockBody* dequeuedCmd = cmdQueue->first; + if (cmdQueue->first == cmdQueue->last) + cmdQueue->last = nullptr; + cmdQueue->first = dequeuedCmd->next; + dequeuedCmd->next = nullptr; + if (dequeuedCmd->next) { - dequeuedCmd = (FSCmdBlockBody_t*)memory_getPointerFromVirtualOffset(_swapEndianU32(cmdQueue->firstMPTR)); - // dequeue cmd - if (cmdQueue->firstMPTR == cmdQueue->lastMPTR) - cmdQueue->lastMPTR = _swapEndianU32(MPTR_NULL); - cmdQueue->firstMPTR = dequeuedCmd->nextMPTR; - dequeuedCmd->nextMPTR = _swapEndianU32(MPTR_NULL); - if (_swapEndianU32(dequeuedCmd->nextMPTR) != MPTR_NULL) - { - FSCmdBlockBody_t* fsCmdBodyNext = (FSCmdBlockBody_t*)memory_getPointerFromVirtualOffset(_swapEndianU32(dequeuedCmd->nextMPTR)); - fsCmdBodyNext->previousMPTR = _swapEndianU32(MPTR_NULL); - } + FSCmdBlockBody* fsCmdBodyNext = dequeuedCmd->next; + fsCmdBodyNext->previous = nullptr; } return dequeuedCmd; } @@ -499,7 +496,7 @@ namespace coreinit FSLockMutex(); if (cmdQueue->numCommandsInFlight < cmdQueue->numMaxCommandsInFlight) { - FSCmdBlockBody_t* dequeuedCommand = __FSTakeCommandFromQueue(cmdQueue); + FSCmdBlockBody* dequeuedCommand = __FSTakeCommandFromQueue(cmdQueue); if (dequeuedCommand) { cmdQueue->numCommandsInFlight += 1; @@ -512,7 +509,7 @@ namespace coreinit FSUnlockMutex(); } - void __FSQueueDefaultFinishFunc(FSCmdBlockBody_t* fsCmdBlockBody, FS_RESULT result) + void __FSQueueDefaultFinishFunc(FSCmdBlockBody* fsCmdBlockBody, FS_RESULT result) { switch ((FSA_CMD_OPERATION_TYPE)fsCmdBlockBody->fsaShimBuffer.operationType.value()) { @@ -594,13 +591,13 @@ namespace coreinit void export___FSQueueDefaultFinishFunc(PPCInterpreter_t* hCPU) { - ppcDefineParamPtr(cmd, FSCmdBlockBody_t, 0); + ppcDefineParamPtr(cmd, FSCmdBlockBody, 0); FS_RESULT result = (FS_RESULT)PPCInterpreter_getCallParamU32(hCPU, 1); __FSQueueDefaultFinishFunc(cmd, static_cast<FS_RESULT>(result)); osLib_returnFromFunction(hCPU, 0); } - void __FSQueueCmd(FSCmdQueue* cmdQueue, FSCmdBlockBody_t* fsCmdBlockBody, MPTR finishCmdFunc) + void __FSQueueCmd(FSCmdQueue* cmdQueue, FSCmdBlockBody* fsCmdBlockBody, MPTR finishCmdFunc) { fsCmdBlockBody->cmdFinishFuncMPTR = finishCmdFunc; FSLockMutex(); @@ -676,7 +673,7 @@ namespace coreinit return FS_RESULT::FATAL_ERROR; } - void __FSCmdSubmitResult(FSCmdBlockBody_t* fsCmdBlockBody, FS_RESULT result) + void __FSCmdSubmitResult(FSCmdBlockBody* fsCmdBlockBody, FS_RESULT result) { _debugVerifyCommand("FSCmdSubmitResult", fsCmdBlockBody); @@ -720,7 +717,7 @@ namespace coreinit void __FSAIoctlResponseCallback(PPCInterpreter_t* hCPU) { ppcDefineParamU32(iosResult, 0); - ppcDefineParamPtr(cmd, FSCmdBlockBody_t, 1); + ppcDefineParamPtr(cmd, FSCmdBlockBody, 1); FSA_RESULT fsaStatus = _FSIosErrorToFSAStatus((IOS_ERROR)iosResult); @@ -754,25 +751,25 @@ namespace coreinit void FSInitCmdBlock(FSCmdBlock_t* fsCmdBlock) { memset(fsCmdBlock, 0x00, sizeof(FSCmdBlock_t)); - FSCmdBlockBody_t* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); + FSCmdBlockBody* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); fsCmdBlockBody->statusCode = _swapEndianU32(FSA_CMD_STATUS_CODE_D900A21); fsCmdBlockBody->priority = 0x10; } - void __FSAsyncToSyncInit(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSAsyncParamsNew_t* asyncParams) + void __FSAsyncToSyncInit(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSAsyncParams* asyncParams) { if (fsClient == nullptr || fsCmdBlock == nullptr || asyncParams == nullptr) assert_dbg(); - FSCmdBlockBody_t* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); + FSCmdBlockBody* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); coreinit::OSInitMessageQueue(&fsCmdBlockBody->syncTaskMsgQueue, fsCmdBlockBody->_syncTaskMsg, 1); asyncParams->userCallback = nullptr; asyncParams->userContext = nullptr; asyncParams->ioMsgQueue = &fsCmdBlockBody->syncTaskMsgQueue; } - void __FSPrepareCmdAsyncResult(FSClientBody_t* fsClientBody, FSCmdBlockBody_t* fsCmdBlockBody, FSAsyncResult* fsCmdBlockAsyncResult, FSAsyncParamsNew_t* fsAsyncParams) + void __FSPrepareCmdAsyncResult(FSClientBody_t* fsClientBody, FSCmdBlockBody* fsCmdBlockBody, FSAsyncResult* fsCmdBlockAsyncResult, FSAsyncParams* fsAsyncParams) { - memcpy(&fsCmdBlockAsyncResult->fsAsyncParamsNew, fsAsyncParams, sizeof(FSAsyncParamsNew_t)); + memcpy(&fsCmdBlockAsyncResult->fsAsyncParamsNew, fsAsyncParams, sizeof(FSAsyncParams)); fsCmdBlockAsyncResult->fsClient = fsClientBody->selfClient; fsCmdBlockAsyncResult->fsCmdBlock = fsCmdBlockBody->selfCmdBlock; @@ -781,7 +778,7 @@ namespace coreinit fsCmdBlockAsyncResult->msgUnion.fsMsg.commandType = _swapEndianU32(8); } - sint32 __FSPrepareCmd(FSClientBody_t* fsClientBody, FSCmdBlockBody_t* fsCmdBlockBody, uint32 errHandling, FSAsyncParamsNew_t* fsAsyncParams) + sint32 __FSPrepareCmd(FSClientBody_t* fsClientBody, FSCmdBlockBody* fsCmdBlockBody, uint32 errHandling, FSAsyncParams* fsAsyncParams) { if (sFSInitialized == false || sFSShutdown == true) return -0x400; @@ -813,18 +810,18 @@ namespace coreinit #define _FSCmdIntro() \ FSClientBody_t* fsClientBody = __FSGetClientBody(fsClient); \ - FSCmdBlockBody_t* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); \ + FSCmdBlockBody* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); \ sint32 fsError = __FSPrepareCmd(fsClientBody, fsCmdBlockBody, errorMask, fsAsyncParams); \ if (fsError != 0) \ return fsError; - void _debugVerifyCommand(const char* stage, FSCmdBlockBody_t* fsCmdBlockBody) + void _debugVerifyCommand(const char* stage, FSCmdBlockBody* fsCmdBlockBody) { if (fsCmdBlockBody->asyncResult.msgUnion.fsMsg.commandType != _swapEndianU32(8)) { cemuLog_log(LogType::Force, "Corrupted FS command detected in stage {}", stage); cemuLog_log(LogType::Force, "Printing CMD block: "); - for (uint32 i = 0; i < (sizeof(FSCmdBlockBody_t) + 31) / 32; i++) + for (uint32 i = 0; i < (sizeof(FSCmdBlockBody) + 31) / 32; i++) { uint8* p = ((uint8*)fsCmdBlockBody) + i * 32; cemuLog_log(LogType::Force, "{:04x}: {:02x} {:02x} {:02x} {:02x} - {:02x} {:02x} {:02x} {:02x} - {:02x} {:02x} {:02x} {:02x} - {:02x} {:02x} {:02x} {:02x} | {:02x} {:02x} {:02x} {:02x} - {:02x} {:02x} {:02x} {:02x} - {:02x} {:02x} {:02x} {:02x} - {:02x} {:02x} {:02x} {:02x}", @@ -845,7 +842,7 @@ namespace coreinit // a positive result (or zero) means success. Most operations return zero in case of success. Read and write operations return the number of transferred units if (fsStatus >= 0) { - FSCmdBlockBody_t* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); + FSCmdBlockBody* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); OSMessage msg; OSReceiveMessage(&fsCmdBlockBody->syncTaskMsgQueue, &msg, OS_MESSAGE_BLOCK); _debugVerifyCommand("handleAsyncResult", fsCmdBlockBody); @@ -906,12 +903,12 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSOpenFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, FSFileHandleDepr_t* outFileHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSOpenFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, FSFileHandlePtr outFileHandle, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); if (outFileHandle == nullptr || path == nullptr || mode == nullptr) return -0x400; - fsCmdBlockBody->returnValues.cmdOpenFile.handlePtr = &outFileHandle->fileHandle; + fsCmdBlockBody->returnValues.cmdOpenFile.handlePtr = outFileHandle; fsError = (FSStatus)__FSPrepareCmd_OpenFile(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, path, mode, 0x660, 0, 0); if (fsError != (FSStatus)FS_RESULT::SUCCESS) return fsError; @@ -919,15 +916,15 @@ namespace coreinit return (FSStatus)FS_RESULT::SUCCESS; } - sint32 FSOpenFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, FSFileHandleDepr_t* fileHandle, uint32 errHandling) + sint32 FSOpenFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, FSFileHandlePtr outFileHandle, uint32 errHandling) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); - sint32 fsAsyncRet = FSOpenFileAsync(fsClient, fsCmdBlock, path, mode, fileHandle, errHandling, &asyncParams); + sint32 fsAsyncRet = FSOpenFileAsync(fsClient, fsCmdBlock, path, mode, outFileHandle, errHandling, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errHandling); } - sint32 FSOpenFileExAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, uint32 createMode, uint32 openFlag, uint32 preallocSize, FSFileHandleDepr_t* outFileHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSOpenFileExAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, uint32 createMode, uint32 openFlag, uint32 preallocSize, FSFileHandlePtr outFileHandle, uint32 errorMask, FSAsyncParams* fsAsyncParams) { if (openFlag != 0) { @@ -938,7 +935,7 @@ namespace coreinit _FSCmdIntro(); if (outFileHandle == nullptr || path == nullptr || mode == nullptr) return -0x400; - fsCmdBlockBody->returnValues.cmdOpenFile.handlePtr = &outFileHandle->fileHandle; + fsCmdBlockBody->returnValues.cmdOpenFile.handlePtr = outFileHandle; FSA_RESULT prepareResult = __FSPrepareCmd_OpenFile(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, path, mode, createMode, openFlag, preallocSize); if (prepareResult != FSA_RESULT::OK) @@ -948,11 +945,11 @@ namespace coreinit return (FSStatus)FS_RESULT::SUCCESS; } - sint32 FSOpenFileEx(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, uint32 createMode, uint32 openFlag, uint32 preallocSize, FSFileHandleDepr_t* fileHandle, uint32 errHandling) + sint32 FSOpenFileEx(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, uint32 createMode, uint32 openFlag, uint32 preallocSize, FSFileHandlePtr outFileHandle, uint32 errHandling) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); - sint32 fsAsyncRet = FSOpenFileExAsync(fsClient, fsCmdBlock, path, mode, createMode, openFlag, preallocSize, fileHandle, errHandling, &asyncParams); + sint32 fsAsyncRet = FSOpenFileExAsync(fsClient, fsCmdBlock, path, mode, createMode, openFlag, preallocSize, outFileHandle, errHandling, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errHandling); } @@ -970,7 +967,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSCloseFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSCloseFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); @@ -984,7 +981,7 @@ namespace coreinit sint32 FSCloseFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errHandling) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSCloseFileAsync(fsClient, fsCmdBlock, fileHandle, errHandling, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errHandling); @@ -1004,7 +1001,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSFlushFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSFlushFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); @@ -1018,7 +1015,7 @@ namespace coreinit sint32 FSFlushFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errHandling) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSFlushFileAsync(fsClient, fsCmdBlock, fileHandle, errHandling, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errHandling); @@ -1060,7 +1057,7 @@ namespace coreinit SysAllocator<uint8, 128, 64> _tempFSSpace; - sint32 __FSReadFileAsyncEx(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dest, uint32 size, uint32 count, bool usePos, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 __FSReadFileAsyncEx(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dest, uint32 size, uint32 count, bool usePos, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); if (size == 0 || count == 0 || dest == NULL) @@ -1091,7 +1088,7 @@ namespace coreinit return (FSStatus)FS_RESULT::SUCCESS; } - sint32 FSReadFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSReadFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParams* fsAsyncParams) { cemu_assert_debug(flag == 0); // todo return __FSReadFileAsyncEx(fsClient, fsCmdBlock, dst, size, count, false, 0, fileHandle, flag, errorMask, fsAsyncParams); @@ -1099,13 +1096,13 @@ namespace coreinit sint32 FSReadFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSReadFileAsync(fsClient, fsCmdBlock, dst, size, count, fileHandle, flag, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } - sint32 FSReadFileWithPosAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSReadFileWithPosAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParams* fsAsyncParams) { cemu_assert_debug(flag == 0); // todo sint32 fsStatus = __FSReadFileAsyncEx(fsClient, fsCmdBlock, dst, size, count, true, filePos, fileHandle, flag, errorMask, fsAsyncParams); @@ -1114,7 +1111,7 @@ namespace coreinit sint32 FSReadFileWithPos(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSReadFileWithPosAsync(fsClient, fsCmdBlock, dst, size, count, filePos, fileHandle, flag, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1154,7 +1151,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 __FSWriteFileWithPosAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dest, uint32 size, uint32 count, bool useFilePos, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 __FSWriteFileWithPosAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dest, uint32 size, uint32 count, bool useFilePos, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); if (size == 0 || count == 0 || dest == nullptr) @@ -1185,27 +1182,27 @@ namespace coreinit return (FSStatus)FS_RESULT::SUCCESS; } - sint32 FSWriteFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSWriteFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParams* fsAsyncParams) { return __FSWriteFileWithPosAsync(fsClient, fsCmdBlock, src, size, count, false, 0, fileHandle, flag, errorMask, fsAsyncParams); } sint32 FSWriteFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSWriteFileAsync(fsClient, fsCmdBlock, src, size, count, fileHandle, flag, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } - sint32 FSWriteFileWithPosAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSWriteFileWithPosAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParams* fsAsyncParams) { return __FSWriteFileWithPosAsync(fsClient, fsCmdBlock, src, size, count, true, filePos, fileHandle, flag, errorMask, fsAsyncParams); } sint32 FSWriteFileWithPos(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSWriteFileWithPosAsync(fsClient, fsCmdBlock, src, size, count, filePos, fileHandle, flag, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1224,7 +1221,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSSetPosFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 filePos, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSSetPosFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 filePos, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); FSA_RESULT prepareResult = __FSPrepareCmd_SetPosFile(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, fileHandle, filePos); @@ -1237,7 +1234,7 @@ namespace coreinit sint32 FSSetPosFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 filePos, uint32 errorMask) { // used by games: Mario Kart 8 - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSSetPosFileAsync(fsClient, fsCmdBlock, fileHandle, filePos, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1254,7 +1251,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSGetPosFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32be* returnedFilePos, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSGetPosFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32be* returnedFilePos, uint32 errorMask, FSAsyncParams* fsAsyncParams) { // games using this: Darksiders Warmastered Edition _FSCmdIntro(); @@ -1268,7 +1265,7 @@ namespace coreinit sint32 FSGetPosFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32be* returnedFilePos, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSGetPosFileAsync(fsClient, fsCmdBlock, fileHandle, returnedFilePos, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1302,7 +1299,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSOpenDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, FSDirHandlePtr dirHandleOut, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSOpenDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, FSDirHandlePtr dirHandleOut, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); cemu_assert(dirHandleOut && path); @@ -1316,7 +1313,7 @@ namespace coreinit sint32 FSOpenDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, FSDirHandlePtr dirHandleOut, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSOpenDirAsync(fsClient, fsCmdBlock, path, dirHandleOut, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1333,7 +1330,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSReadDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, FSDirEntry_t* dirEntryOut, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSReadDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, FSDirEntry_t* dirEntryOut, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); FSA_RESULT prepareResult = __FSPrepareCmd_ReadDir(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, dirHandle); @@ -1344,9 +1341,9 @@ namespace coreinit return (FSStatus)FS_RESULT::SUCCESS; } - sint32 FSReadDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, FSDirEntry_t* dirEntryOut, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSReadDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, FSDirEntry_t* dirEntryOut, uint32 errorMask, FSAsyncParams* fsAsyncParams) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSReadDirAsync(fsClient, fsCmdBlock, dirHandle, dirEntryOut, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1363,7 +1360,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSCloseDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSCloseDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); FSA_RESULT prepareResult = __FSPrepareCmd_CloseDir(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, dirHandle); @@ -1376,7 +1373,7 @@ namespace coreinit sint32 FSCloseDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSCloseDirAsync(fsClient, fsCmdBlock, dirHandle, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1396,7 +1393,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSRewindDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSRewindDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); FSA_RESULT prepareResult = __FSPrepareCmd_RewindDir(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, dirHandle); @@ -1409,7 +1406,7 @@ namespace coreinit sint32 FSRewindDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSRewindDirAsync(fsClient, fsCmdBlock, dirHandle, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1431,7 +1428,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSAppendFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 size, uint32 count, uint32 fileHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSAppendFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 size, uint32 count, uint32 fileHandle, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); FSA_RESULT prepareResult = __FSPrepareCmd_AppendFile(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, size, count, fileHandle, 0); @@ -1444,7 +1441,7 @@ namespace coreinit sint32 FSAppendFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 size, uint32 count, uint32 fileHandle, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t> asyncParams; + StackAllocator<FSAsyncParams> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSAppendFileAsync(fsClient, fsCmdBlock, size, count, fileHandle, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1463,7 +1460,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSTruncateFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSFileHandle2 fileHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSTruncateFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSFileHandle2 fileHandle, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); FSA_RESULT prepareResult = __FSPrepareCmd_TruncateFile(&fsCmdBlockBody->fsaShimBuffer, fsClientBody->iosuFSAHandle, fileHandle); @@ -1476,7 +1473,7 @@ namespace coreinit sint32 FSTruncateFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSFileHandle2 fileHandle, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t> asyncParams; + StackAllocator<FSAsyncParams> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSTruncateFileAsync(fsClient, fsCmdBlock, fileHandle, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1521,7 +1518,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSRenameAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* srcPath, char* dstPath, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSRenameAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* srcPath, char* dstPath, uint32 errorMask, FSAsyncParams* fsAsyncParams) { // used by titles: XCX (via SAVERenameAsync) _FSCmdIntro(); @@ -1540,7 +1537,7 @@ namespace coreinit sint32 FSRename(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* srcPath, char* dstPath, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t> asyncParams; + StackAllocator<FSAsyncParams> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSRenameAsync(fsClient, fsCmdBlock, srcPath, dstPath, errorMask, asyncParams.GetPointer()); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1572,7 +1569,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSRemoveAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* filePath, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSRemoveAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* filePath, uint32 errorMask, FSAsyncParams* fsAsyncParams) { // used by titles: XCX (via SAVERemoveAsync) _FSCmdIntro(); @@ -1591,7 +1588,7 @@ namespace coreinit sint32 FSRemove(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* filePath, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t> asyncParams; + StackAllocator<FSAsyncParams> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSRemoveAsync(fsClient, fsCmdBlock, filePath, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1624,7 +1621,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSMakeDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* dirPath, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSMakeDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* dirPath, uint32 errorMask, FSAsyncParams* fsAsyncParams) { // used by titles: XCX (via SAVEMakeDirAsync) _FSCmdIntro(); @@ -1643,7 +1640,7 @@ namespace coreinit sint32 FSMakeDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t> asyncParams; + StackAllocator<FSAsyncParams> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSMakeDirAsync(fsClient, fsCmdBlock, path, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1674,7 +1671,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSChangeDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSChangeDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); if (path == NULL) @@ -1692,7 +1689,7 @@ namespace coreinit sint32 FSChangeDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t> asyncParams; + StackAllocator<FSAsyncParams> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSChangeDirAsync(fsClient, fsCmdBlock, path, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1710,7 +1707,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSGetCwdAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* dirPathOut, sint32 dirPathMaxLen, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSGetCwdAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* dirPathOut, sint32 dirPathMaxLen, uint32 errorMask, FSAsyncParams* fsAsyncParams) { // used by titles: Super Mario Maker _FSCmdIntro(); @@ -1727,7 +1724,7 @@ namespace coreinit sint32 FSGetCwd(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* dirPathOut, sint32 dirPathMaxLen, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t> asyncParams; + StackAllocator<FSAsyncParams> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSGetCwdAsync(fsClient, fsCmdBlock, dirPathOut, dirPathMaxLen, errorMask, &asyncParams); auto r = __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1758,7 +1755,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSFlushQuotaAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSFlushQuotaAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); @@ -1772,7 +1769,7 @@ namespace coreinit sint32 FSFlushQuota(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t> asyncParams; + StackAllocator<FSAsyncParams> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSFlushQuotaAsync(fsClient, fsCmdBlock, path, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1808,7 +1805,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 __FSQueryInfoAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* queryString, uint32 queryType, void* queryResult, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 __FSQueryInfoAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* queryString, uint32 queryType, void* queryResult, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); cemu_assert(queryString && queryResult); // query string and result must not be null @@ -1822,7 +1819,7 @@ namespace coreinit return (FSStatus)FS_RESULT::SUCCESS; } - sint32 FSGetStatAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSStat_t* statOut, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSGetStatAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSStat_t* statOut, uint32 errorMask, FSAsyncParams* fsAsyncParams) { sint32 fsStatus = __FSQueryInfoAsync(fsClient, fsCmdBlock, (uint8*)path, FSA_QUERY_TYPE_STAT, statOut, errorMask, fsAsyncParams); return fsStatus; @@ -1830,7 +1827,7 @@ namespace coreinit sint32 FSGetStat(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSStat_t* statOut, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSGetStatAsync(fsClient, fsCmdBlock, path, statOut, errorMask, &asyncParams); sint32 ret = __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1851,7 +1848,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSGetStatFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSFileHandle2 fileHandle, FSStat_t* statOut, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSGetStatFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSFileHandle2 fileHandle, FSStat_t* statOut, uint32 errorMask, FSAsyncParams* fsAsyncParams) { _FSCmdIntro(); cemu_assert(statOut); // statOut must not be null @@ -1867,13 +1864,13 @@ namespace coreinit sint32 FSGetStatFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSFileHandle2 fileHandle, FSStat_t* statOut, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSGetStatFileAsync(fsClient, fsCmdBlock, fileHandle, statOut, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); } - sint32 FSGetFreeSpaceSizeAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSLargeSize* returnedFreeSize, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSGetFreeSpaceSizeAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSLargeSize* returnedFreeSize, uint32 errorMask, FSAsyncParams* fsAsyncParams) { // used by: Wii U system settings app, Art Academy, Unity (e.g. Snoopy's Grand Adventure), Super Smash Bros sint32 fsStatus = __FSQueryInfoAsync(fsClient, fsCmdBlock, (uint8*)path, FSA_QUERY_TYPE_FREESPACE, returnedFreeSize, errorMask, fsAsyncParams); @@ -1882,7 +1879,7 @@ namespace coreinit sint32 FSGetFreeSpaceSize(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSLargeSize* returnedFreeSize, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSGetFreeSpaceSizeAsync(fsClient, fsCmdBlock, path, returnedFreeSize, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1902,7 +1899,7 @@ namespace coreinit return FSA_RESULT::OK; } - sint32 FSIsEofAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams) + sint32 FSIsEofAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask, FSAsyncParams* fsAsyncParams) { // used by Paper Monsters Recut _FSCmdIntro(); @@ -1917,7 +1914,7 @@ namespace coreinit sint32 FSIsEof(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask) { - StackAllocator<FSAsyncParamsNew_t, 1> asyncParams; + StackAllocator<FSAsyncParams, 1> asyncParams; __FSAsyncToSyncInit(fsClient, fsCmdBlock, &asyncParams); sint32 fsAsyncRet = FSIsEofAsync(fsClient, fsCmdBlock, fileHandle, errorMask, &asyncParams); return __FSProcessAsyncResult(fsClient, fsCmdBlock, fsAsyncRet, errorMask); @@ -1925,14 +1922,14 @@ namespace coreinit void FSSetUserData(FSCmdBlock_t* fsCmdBlock, void* userData) { - FSCmdBlockBody_t* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); + FSCmdBlockBody* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); if (fsCmdBlockBody) fsCmdBlockBody->userData = userData; } void* FSGetUserData(FSCmdBlock_t* fsCmdBlock) { - FSCmdBlockBody_t* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); + FSCmdBlockBody* fsCmdBlockBody = __FSGetCmdBlockBody(fsCmdBlock); void* userData = nullptr; if (fsCmdBlockBody) userData = fsCmdBlockBody->userData.GetPtr(); @@ -1956,7 +1953,7 @@ namespace coreinit FSClientBody_t* fsClientBody = __FSGetClientBody(fsClient); if (!fsClientBody) return nullptr; - FSCmdBlockBody_t* cmdBlockBody = fsClientBody->currentCmdBlockBody; + FSCmdBlockBody* cmdBlockBody = fsClientBody->currentCmdBlockBody; if (!cmdBlockBody) return nullptr; return cmdBlockBody->selfCmdBlock; diff --git a/src/Cafe/OS/libs/coreinit/coreinit_FS.h b/src/Cafe/OS/libs/coreinit/coreinit_FS.h index 2a57f7da..bf12e33c 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_FS.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_FS.h @@ -5,33 +5,23 @@ #include "Cafe/IOSU/fsa/iosu_fsa.h" #include "coreinit_MessageQueue.h" -typedef struct -{ - uint32be fileHandle; -} FSFileHandleDepr_t; - +typedef MEMPTR<betype<FSFileHandle2>> FSFileHandlePtr; typedef MEMPTR<betype<FSDirHandle2>> FSDirHandlePtr; typedef uint32 FSAClientHandle; -typedef struct +struct FSAsyncParams { MEMPTR<void> userCallback; MEMPTR<void> userContext; MEMPTR<coreinit::OSMessageQueue> ioMsgQueue; -} FSAsyncParamsNew_t; - -static_assert(sizeof(FSAsyncParamsNew_t) == 0xC); - -typedef struct -{ - MPTR userCallback; // 0x96C - MPTR userContext; - MPTR ioMsgQueue; -} FSAsyncParams_t; // legacy struct. Replace with FSAsyncParamsNew_t +}; +static_assert(sizeof(FSAsyncParams) == 0xC); namespace coreinit { + struct FSCmdBlockBody; + struct FSCmdQueue { enum class QUEUE_FLAG : uint32 @@ -40,8 +30,8 @@ namespace coreinit CANCEL_ALL = (1 << 4), }; - /* +0x00 */ MPTR firstMPTR; - /* +0x04 */ MPTR lastMPTR; + /* +0x00 */ MEMPTR<FSCmdBlockBody> first; + /* +0x04 */ MEMPTR<FSCmdBlockBody> last; /* +0x08 */ OSFastMutex fastMutex; /* +0x34 */ MPTR dequeueHandlerFuncMPTR; /* +0x38 */ uint32be numCommandsInFlight; @@ -108,7 +98,7 @@ namespace coreinit uint8 ukn1460[0x10]; uint8 ukn1470[0x10]; FSCmdQueue fsCmdQueue; - /* +0x14C4 */ MEMPTR<struct FSCmdBlockBody_t> currentCmdBlockBody; // set to currently active cmd + /* +0x14C4 */ MEMPTR<struct FSCmdBlockBody> currentCmdBlockBody; // set to currently active cmd uint32 ukn14C8; uint32 ukn14CC; uint8 ukn14D0[0x10]; @@ -128,7 +118,7 @@ namespace coreinit struct FSAsyncResult { - /* +0x00 */ FSAsyncParamsNew_t fsAsyncParamsNew; + /* +0x00 */ FSAsyncParams fsAsyncParamsNew; // fs message storage struct FSMessage @@ -159,7 +149,7 @@ namespace coreinit uint8 ukn0[0x14]; struct { - MEMPTR<uint32be> handlePtr; + MEMPTR<betype<FSResHandle>> handlePtr; } cmdOpenFile; struct { @@ -205,7 +195,7 @@ namespace coreinit static_assert(sizeof(FSCmdBlockReturnValues_t) == 0x14); - struct FSCmdBlockBody_t + struct FSCmdBlockBody { iosu::fsa::FSAShimBuffer fsaShimBuffer; /* +0x0938 */ MEMPTR<FSClientBody_t> fsClientBody; @@ -213,9 +203,8 @@ namespace coreinit /* +0x0940 */ uint32be cancelState; // bitmask. Bit 0 -> If set command has been canceled FSCmdBlockReturnValues_t returnValues; // link for cmd queue - MPTR nextMPTR; // points towards FSCmdQueue->first - MPTR previousMPTR; // points towards FSCmdQueue->last - + MEMPTR<FSCmdBlockBody> next; + MEMPTR<FSCmdBlockBody> previous; /* +0x960 */ betype<FSA_RESULT> lastFSAStatus; uint32 ukn0964; /* +0x0968 */ uint8 errHandling; // return error flag mask @@ -235,7 +224,6 @@ namespace coreinit uint32 ukn9FC; }; - static_assert(sizeof(FSAsyncParams_t) == 0xC); static_assert(sizeof(FSCmdBlock_t) == 0xA80); #define FSA_CMD_FLAG_SET_POS (1 << 0) @@ -251,7 +239,7 @@ namespace coreinit }; // internal interface - sint32 __FSQueryInfoAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* queryString, uint32 queryType, void* queryResult, uint32 errHandling, FSAsyncParamsNew_t* fsAsyncParams); + sint32 __FSQueryInfoAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* queryString, uint32 queryType, void* queryResult, uint32 errHandling, FSAsyncParams* fsAsyncParams); // coreinit exports FS_RESULT FSAddClientEx(FSClient_t* fsClient, uint32 uknR4, uint32 errHandling); @@ -260,52 +248,52 @@ namespace coreinit void FSInitCmdBlock(FSCmdBlock_t* fsCmdBlock); - sint32 FSOpenFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, FSFileHandleDepr_t* fileHandle, uint32 errHandling, FSAsyncParamsNew_t* asyncParams); - sint32 FSOpenFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, FSFileHandleDepr_t* fileHandle, uint32 errHandling); + sint32 FSOpenFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, FSFileHandlePtr outFileHandle, uint32 errHandling, FSAsyncParams* asyncParams); + sint32 FSOpenFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, char* mode, FSFileHandlePtr outFileHandle, uint32 errHandling); - sint32 FSReadFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSReadFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSReadFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask); - sint32 FSReadFileWithPosAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSReadFileWithPosAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSReadFileWithPos(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* dst, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask); - sint32 FSWriteFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSWriteFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSWriteFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 fileHandle, uint32 flag, uint32 errorMask); - sint32 FSWriteFileWithPosAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSWriteFileWithPosAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSWriteFileWithPos(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, void* src, uint32 size, uint32 count, uint32 filePos, uint32 fileHandle, uint32 flag, uint32 errorMask); - sint32 FSSetPosFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 filePos, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSSetPosFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 filePos, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSSetPosFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 filePos, uint32 errorMask); - sint32 FSGetPosFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32be* returnedFilePos, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSGetPosFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32be* returnedFilePos, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSGetPosFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32be* returnedFilePos, uint32 errorMask); - sint32 FSAppendFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 size, uint32 count, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSAppendFileAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 size, uint32 count, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSAppendFile(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 size, uint32 count, uint32 errorMask); - sint32 FSIsEofAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSIsEofAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSIsEof(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint32 fileHandle, uint32 errorMask); - sint32 FSRenameAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* srcPath, char* dstPath, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSRenameAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* srcPath, char* dstPath, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSRename(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* srcPath, char* dstPath, uint32 errorMask); - sint32 FSRemoveAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* filePath, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSRemoveAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* filePath, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSRemove(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, uint8* filePath, uint32 errorMask); - sint32 FSMakeDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* dirPath, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSMakeDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* dirPath, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSMakeDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, uint32 errorMask); - sint32 FSChangeDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSChangeDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSChangeDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask); - sint32 FSGetCwdAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* dirPathOut, sint32 dirPathMaxLen, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSGetCwdAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* dirPathOut, sint32 dirPathMaxLen, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSGetCwd(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* dirPathOut, sint32 dirPathMaxLen, uint32 errorMask); - sint32 FSGetFreeSpaceSizeAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSLargeSize* returnedFreeSize, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSGetFreeSpaceSizeAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSLargeSize* returnedFreeSize, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSGetFreeSpaceSize(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, const char* path, FSLargeSize* returnedFreeSize, uint32 errorMask); - sint32 FSOpenDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, FSDirHandlePtr dirHandleOut, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSOpenDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, FSDirHandlePtr dirHandleOut, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSOpenDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, FSDirHandlePtr dirHandleOut, uint32 errorMask); - sint32 FSReadDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, FSDirEntry_t* dirEntryOut, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); - sint32 FSReadDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, FSDirEntry_t* dirEntryOut, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); - sint32 FSCloseDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSReadDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, FSDirEntry_t* dirEntryOut, uint32 errorMask, FSAsyncParams* fsAsyncParams); + sint32 FSReadDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, FSDirEntry_t* dirEntryOut, uint32 errorMask, FSAsyncParams* fsAsyncParams); + sint32 FSCloseDirAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSCloseDir(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, FSDirHandle2 dirHandle, uint32 errorMask); - sint32 FSFlushQuotaAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask, FSAsyncParamsNew_t* fsAsyncParams); + sint32 FSFlushQuotaAsync(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask, FSAsyncParams* fsAsyncParams); sint32 FSFlushQuota(FSClient_t* fsClient, FSCmdBlock_t* fsCmdBlock, char* path, uint32 errorMask); FS_VOLSTATE FSGetVolumeState(FSClient_t* fsClient); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MEM.cpp b/src/Cafe/OS/libs/coreinit/coreinit_MEM.cpp index dc82f772..83658f3c 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_MEM.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_MEM.cpp @@ -128,7 +128,7 @@ namespace coreinit { MEMPTR<void> memBound; uint32be memBoundSize; - OSGetMemBound(1, (MPTR*)memBound.GetBEPtr(), (uint32*)&memBoundSize); + OSGetMemBound(1, &memBound, &memBoundSize); MEMPTR<void> bucket; uint32be bucketSize; @@ -257,7 +257,7 @@ namespace coreinit { MEMPTR<void> memBound; uint32be memBoundSize; - OSGetMemBound(1, (MPTR*)memBound.GetBEPtr(), (uint32*)&memBoundSize); + OSGetMemBound(1, &memBound, &memBoundSize); MEMPTR<void> bucket; uint32be bucketSize; @@ -593,16 +593,16 @@ namespace coreinit { MEMPTR<void> memBound; uint32be memBoundSize; - OSGetMemBound(1, (MPTR*)memBound.GetBEPtr(), (uint32*)&memBoundSize); + OSGetMemBound(1, &memBound, &memBoundSize); mem1Heap = MEMCreateFrmHeapEx(memBound.GetPtr(), (uint32)memBoundSize, 0); - OSGetForegroundBucketFreeArea((MPTR*)memBound.GetBEPtr(), (MPTR*)&memBoundSize); + OSGetForegroundBucketFreeArea(&memBound, &memBoundSize); memFGHeap = MEMCreateFrmHeapEx(memBound.GetPtr(), (uint32)memBoundSize, 0); } MEMPTR<void> memBound; uint32be memBoundSize; - OSGetMemBound(2, (MPTR*)memBound.GetBEPtr(), (uint32*)&memBoundSize); + OSGetMemBound(2, &memBound, &memBoundSize); mem2Heap = MEMDefaultHeap_Init(memBound.GetPtr(), (uint32)memBoundSize); // set DynLoad allocators diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Memory.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Memory.cpp index cff4ee2b..80ec212d 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Memory.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Memory.cpp @@ -131,7 +131,7 @@ namespace coreinit // no-op } - void OSGetMemBound(sint32 memType, MPTR* offsetOutput, uint32* sizeOutput) + void OSGetMemBound(sint32 memType, MEMPTR<void>* offsetOutput, uint32be* sizeOutput) { MPTR memAddr = MPTR_NULL; uint32 memSize = 0; @@ -195,9 +195,9 @@ namespace coreinit cemu_assert_debug(false); } if (offsetOutput) - *offsetOutput = _swapEndianU32(memAddr); + *offsetOutput = memAddr; if (sizeOutput) - *sizeOutput = _swapEndianU32(memSize); + *sizeOutput = memSize; } void InitializeMemory() diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Memory.h b/src/Cafe/OS/libs/coreinit/coreinit_Memory.h index 0a212f61..62c9f135 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Memory.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_Memory.h @@ -4,7 +4,7 @@ namespace coreinit { void InitializeMemory(); - void OSGetMemBound(sint32 memType, MPTR* offsetOutput, uint32* sizeOutput); + void OSGetMemBound(sint32 memType, MEMPTR<void>* offsetOutput, uint32be* sizeOutput); void* OSBlockMove(MEMPTR<void> dst, MEMPTR<void> src, uint32 size, bool flushDC); void* OSBlockSet(MEMPTR<void> dst, uint32 value, uint32 size); diff --git a/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp b/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp index 0268c7df..7a8eacb7 100644 --- a/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp +++ b/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp @@ -1401,12 +1401,10 @@ void export_curl_easy_getinfo(PPCInterpreter_t* hCPU) } case CURLINFO_CONTENT_TYPE: { - //cemuLog_logDebug(LogType::Force, "CURLINFO_CONTENT_TYPE not supported"); - //*(uint32*)parameter.GetPtr() = MPTR_NULL; char* contentType = nullptr; result = curl_easy_getinfo(curlObj, CURLINFO_REDIRECT_URL, &contentType); _updateGuestString(curl.GetPtr(), curl->info_contentType, contentType); - *(uint32*)parameter.GetPtr() = curl->info_contentType.GetMPTRBE(); + *(MEMPTR<char>*)parameter.GetPtr() = curl->info_contentType; break; } case CURLINFO_REDIRECT_URL: @@ -1414,7 +1412,7 @@ void export_curl_easy_getinfo(PPCInterpreter_t* hCPU) char* redirectUrl = nullptr; result = curl_easy_getinfo(curlObj, CURLINFO_REDIRECT_URL, &redirectUrl); _updateGuestString(curl.GetPtr(), curl->info_redirectUrl, redirectUrl); - *(uint32*)parameter.GetPtr() = curl->info_redirectUrl.GetMPTRBE(); + *(MEMPTR<char>*)parameter.GetPtr() = curl->info_redirectUrl; break; } default: diff --git a/src/Cafe/OS/libs/nn_save/nn_save.cpp b/src/Cafe/OS/libs/nn_save/nn_save.cpp index 05e49438..518e4195 100644 --- a/src/Cafe/OS/libs/nn_save/nn_save.cpp +++ b/src/Cafe/OS/libs/nn_save/nn_save.cpp @@ -320,7 +320,7 @@ namespace save return SAVE_STATUS_OK; } - SAVEStatus SAVERemoveAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + SAVEStatus SAVERemoveAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -331,7 +331,7 @@ namespace save { char fullPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPath(persistentId, path, fullPath)) - result = coreinit::FSRemoveAsync(client, block, (uint8*)fullPath, errHandling, (FSAsyncParamsNew_t*)asyncParams); + result = coreinit::FSRemoveAsync(client, block, (uint8*)fullPath, errHandling, (FSAsyncParams*)asyncParams); } else result = (FSStatus)FS_RESULT::NOT_FOUND; @@ -340,7 +340,7 @@ namespace save return result; } - SAVEStatus SAVEMakeDirAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + SAVEStatus SAVEMakeDirAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -351,7 +351,7 @@ namespace save { char fullPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPath(persistentId, path, fullPath)) - result = coreinit::FSMakeDirAsync(client, block, fullPath, errHandling, (FSAsyncParamsNew_t*)asyncParams); + result = coreinit::FSMakeDirAsync(client, block, fullPath, errHandling, (FSAsyncParams*)asyncParams); } else result = (FSStatus)FS_RESULT::NOT_FOUND; @@ -361,7 +361,7 @@ namespace save return result; } - SAVEStatus SAVEOpenDirAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + SAVEStatus SAVEOpenDirAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -372,7 +372,7 @@ namespace save { char fullPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPath(persistentId, path, fullPath)) - result = coreinit::FSOpenDirAsync(client, block, fullPath, hDir, errHandling, (FSAsyncParamsNew_t*)asyncParams); + result = coreinit::FSOpenDirAsync(client, block, fullPath, hDir, errHandling, (FSAsyncParams*)asyncParams); } else @@ -383,7 +383,7 @@ namespace save return result; } - SAVEStatus SAVEOpenFileAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, const char* mode, FSFileHandleDepr_t* hFile, FS_ERROR_MASK errHandling, const FSAsyncParamsNew_t* asyncParams) + SAVEStatus SAVEOpenFileAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -394,7 +394,7 @@ namespace save { char fullPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPath(persistentId, path, fullPath)) - result = coreinit::FSOpenFileAsync(client, block, fullPath, (char*)mode, hFile, errHandling, (FSAsyncParamsNew_t*)asyncParams); + result = coreinit::FSOpenFileAsync(client, block, fullPath, (char*)mode, outFileHandle, errHandling, (FSAsyncParams*)asyncParams); } else result = (FSStatus)FS_RESULT::NOT_FOUND; @@ -404,7 +404,7 @@ namespace save return result; } - SAVEStatus SAVEOpenFileOtherApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, const char* mode, FSFileHandleDepr_t* hFile, FS_ERROR_MASK errHandling, const FSAsyncParamsNew_t* asyncParams) + SAVEStatus SAVEOpenFileOtherApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { if (strcmp(mode, "r") != 0) return (SAVEStatus)(FS_RESULT::PERMISSION_ERROR); @@ -418,7 +418,7 @@ namespace save { char fullPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPathOtherApplication(persistentId, titleId, path, fullPath)) - result = coreinit::FSOpenFileAsync(client, block, fullPath, (char*)mode, hFile, errHandling, (FSAsyncParamsNew_t*)asyncParams); + result = coreinit::FSOpenFileAsync(client, block, fullPath, (char*)mode, outFileHandle, errHandling, (FSAsyncParams*)asyncParams); } else result = (FSStatus)FS_RESULT::NOT_FOUND; @@ -428,26 +428,10 @@ namespace save return result; } - void export_SAVEOpenFileOtherApplicationAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU64(titleId, 2); - ppcDefineParamU8(accountSlot, 4); - ppcDefineParamMEMPTR(path, const char, 5); - ppcDefineParamMEMPTR(mode, const char, 6); - ppcDefineParamMEMPTR(hFile, FSFileHandleDepr_t, 7); - ppcDefineParamU32(errHandling, 8); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParamsNew_t, 9); - - const SAVEStatus result = SAVEOpenFileOtherApplicationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), titleId, accountSlot, path.GetPtr(), mode.GetPtr(), hFile.GetPtr(), errHandling, asyncParams.GetPtr()); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenFileOtherApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, const char* mode, FSFileHandleDepr_t* hFile, FS_ERROR_MASK errHandling) + SAVEStatus SAVEOpenFileOtherApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling) { MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParamsNew_t asyncParams; + FSAsyncParams asyncParams; asyncParams.ioMsgQueue = nullptr; asyncParams.userCallback = PPCInterpreter_makeCallableExportDepr(AsyncCallback); @@ -456,7 +440,7 @@ namespace save param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; asyncParams.userContext = param.GetPointer(); - SAVEStatus status = SAVEOpenFileOtherApplicationAsync(client, block, titleId, accountSlot, path, mode, hFile, errHandling, &asyncParams); + SAVEStatus status = SAVEOpenFileOtherApplicationAsync(client, block, titleId, accountSlot, path, mode, outFileHandle, errHandling, &asyncParams); if (status == (FSStatus)FS_RESULT::SUCCESS) { coreinit_suspendThread(currentThread, 1000); @@ -467,113 +451,31 @@ namespace save return status; } - void export_SAVEOpenFileOtherApplication(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU64(titleId, 2); - ppcDefineParamU8(accountSlot, 4); - ppcDefineParamMEMPTR(path, const char, 5); - ppcDefineParamMEMPTR(mode, const char, 6); - ppcDefineParamMEMPTR(hFile, FSFileHandleDepr_t, 7); - ppcDefineParamU32(errHandling, 8); - - const SAVEStatus result = SAVEOpenFileOtherApplication(fsClient.GetPtr(), fsCmdBlock.GetPtr(), titleId, accountSlot, path.GetPtr(), mode.GetPtr(), hFile.GetPtr(), errHandling); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenFileOtherNormalApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, const char* mode, FSFileHandleDepr_t* hFile, FS_ERROR_MASK errHandling, const FSAsyncParamsNew_t* asyncParams) + SAVEStatus SAVEOpenFileOtherNormalApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); - return SAVEOpenFileOtherApplicationAsync(client, block, titleId, accountSlot, path, mode, hFile, errHandling, asyncParams); + return SAVEOpenFileOtherApplicationAsync(client, block, titleId, accountSlot, path, mode, outFileHandle, errHandling, asyncParams); } - void export_SAVEOpenFileOtherNormalApplicationAsync(PPCInterpreter_t* hCPU) + SAVEStatus SAVEOpenFileOtherNormalApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling) { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU32(uniqueId, 2); - ppcDefineParamU8(accountSlot, 3); - ppcDefineParamMEMPTR(path, const char, 4); - ppcDefineParamMEMPTR(mode, const char, 5); - ppcDefineParamMEMPTR(hFile, FSFileHandleDepr_t, 6); - ppcDefineParamU32(errHandling, 7); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParamsNew_t, 8); - - const SAVEStatus result = SAVEOpenFileOtherNormalApplicationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, accountSlot, path.GetPtr(), mode.GetPtr(), hFile.GetPtr(), errHandling, asyncParams.GetPtr()); - osLib_returnFromFunction(hCPU, result); - } - SAVEStatus SAVEOpenFileOtherNormalApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, const char* mode, FSFileHandleDepr_t* hFile, FS_ERROR_MASK errHandling) - { - //peterBreak(); - uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); - return SAVEOpenFileOtherApplication(client, block, titleId, accountSlot, path, mode, hFile, errHandling); + return SAVEOpenFileOtherApplication(client, block, titleId, accountSlot, path, mode, outFileHandle, errHandling); } - void export_SAVEOpenFileOtherNormalApplication(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU32(uniqueId, 2); - ppcDefineParamU8(accountSlot, 3); - ppcDefineParamMEMPTR(path, const char, 4); - ppcDefineParamMEMPTR(mode, const char, 5); - ppcDefineParamMEMPTR(hFile, FSFileHandleDepr_t, 6); - ppcDefineParamU32(errHandling, 7); - - const SAVEStatus result = SAVEOpenFileOtherNormalApplication(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, accountSlot, path.GetPtr(), mode.GetPtr(), hFile.GetPtr(), errHandling); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenFileOtherNormalApplicationVariationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, const char* mode, FSFileHandleDepr_t* hFile, FS_ERROR_MASK errHandling, const FSAsyncParamsNew_t* asyncParams) - { - //peterBreak(); - - uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); - return SAVEOpenFileOtherApplicationAsync(client, block, titleId, accountSlot, path, mode, hFile, errHandling, asyncParams); - } - - void export_SAVEOpenFileOtherNormalApplicationVariationAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU32(uniqueId, 2); - ppcDefineParamU8(variation, 3); - ppcDefineParamU8(accountSlot, 4); - ppcDefineParamMEMPTR(path, const char, 5); - ppcDefineParamMEMPTR(mode, const char, 6); - ppcDefineParamMEMPTR(hFile, FSFileHandleDepr_t, 7); - ppcDefineParamU32(errHandling, 8); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParamsNew_t, 9); - - const SAVEStatus result = SAVEOpenFileOtherNormalApplicationVariationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, variation, accountSlot, path.GetPtr(), mode.GetPtr(), hFile.GetPtr(), errHandling, asyncParams.GetPtr()); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenFileOtherNormalApplicationVariation(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, const char* mode, FSFileHandleDepr_t* hFile, FS_ERROR_MASK errHandling) + SAVEStatus SAVEOpenFileOtherNormalApplicationVariationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); - return SAVEOpenFileOtherApplication(client, block, titleId, accountSlot, path, mode, hFile, errHandling); + return SAVEOpenFileOtherApplicationAsync(client, block, titleId, accountSlot, path, mode, outFileHandle, errHandling, asyncParams); } - void export_SAVEOpenFileOtherNormalApplicationVariation(PPCInterpreter_t* hCPU) + SAVEStatus SAVEOpenFileOtherNormalApplicationVariation(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling) { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU32(uniqueId, 2); - ppcDefineParamU8(variation, 3); - ppcDefineParamU8(accountSlot, 4); - ppcDefineParamMEMPTR(path, const char, 5); - ppcDefineParamMEMPTR(mode, const char, 6); - ppcDefineParamMEMPTR(hFile, FSFileHandleDepr_t, 7); - ppcDefineParamU32(errHandling, 8); - - const SAVEStatus result = SAVEOpenFileOtherNormalApplicationVariation(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, variation, accountSlot, path.GetPtr(), mode.GetPtr(), hFile.GetPtr(), errHandling); - osLib_returnFromFunction(hCPU, result); + uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); + return SAVEOpenFileOtherApplication(client, block, titleId, accountSlot, path, mode, outFileHandle, errHandling); } - SAVEStatus SAVEGetFreeSpaceSizeAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FSLargeSize* freeSize, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + SAVEStatus SAVEGetFreeSpaceSizeAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FSLargeSize* freeSize, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -583,9 +485,8 @@ namespace save if (GetPersistentIdEx(accountSlot, &persistentId)) { char fullPath[SAVE_MAX_PATH_SIZE]; - // usually a pointer with '\0' instead of nullptr, but it's basically the same if (GetAbsoluteFullPath(persistentId, nullptr, fullPath)) - result = coreinit::FSGetFreeSpaceSizeAsync(client, block, fullPath, freeSize, errHandling, (FSAsyncParamsNew_t*)asyncParams); + result = coreinit::FSGetFreeSpaceSizeAsync(client, block, fullPath, freeSize, errHandling, (FSAsyncParams*)asyncParams); } else result = (FSStatus)FS_RESULT::NOT_FOUND; @@ -595,7 +496,7 @@ namespace save return result; } - SAVEStatus SAVEGetStatAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + SAVEStatus SAVEGetStatAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -606,7 +507,7 @@ namespace save { char fullPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPath(persistentId, path, fullPath)) - result = coreinit::__FSQueryInfoAsync(client, block, (uint8*)fullPath, FSA_QUERY_TYPE_STAT, stat, errHandling, (FSAsyncParamsNew_t*)asyncParams); // FSGetStatAsync(...) + result = coreinit::__FSQueryInfoAsync(client, block, (uint8*)fullPath, FSA_QUERY_TYPE_STAT, stat, errHandling, (FSAsyncParams*)asyncParams); // FSGetStatAsync(...) } else result = (FSStatus)FS_RESULT::NOT_FOUND; @@ -616,7 +517,7 @@ namespace save return result; } - SAVEStatus SAVEGetStatOtherApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + SAVEStatus SAVEGetStatOtherApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -627,7 +528,7 @@ namespace save { char fullPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPathOtherApplication(persistentId, titleId, path, fullPath) == (FSStatus)FS_RESULT::SUCCESS) - result = coreinit::__FSQueryInfoAsync(client, block, (uint8*)fullPath, FSA_QUERY_TYPE_STAT, stat, errHandling, (FSAsyncParamsNew_t*)asyncParams); // FSGetStatAsync(...) + result = coreinit::__FSQueryInfoAsync(client, block, (uint8*)fullPath, FSA_QUERY_TYPE_STAT, stat, errHandling, (FSAsyncParams*)asyncParams); // FSGetStatAsync(...) } else result = (FSStatus)FS_RESULT::NOT_FOUND; @@ -637,25 +538,25 @@ namespace save return result; } - SAVEStatus SAVEGetStatOtherNormalApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + SAVEStatus SAVEGetStatOtherNormalApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); return SAVEGetStatOtherApplicationAsync(client, block, titleId, accountSlot, path, stat, errHandling, asyncParams); } - SAVEStatus SAVEGetStatOtherDemoApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + SAVEStatus SAVEGetStatOtherDemoApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { uint64 titleId = SAVE_UNIQUE_DEMO_TO_TITLE_ID(uniqueId); return SAVEGetStatOtherApplicationAsync(client, block, titleId, accountSlot, path, stat, errHandling, asyncParams); } - SAVEStatus SAVEGetStatOtherNormalApplicationVariationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + SAVEStatus SAVEGetStatOtherNormalApplicationVariationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); return SAVEGetStatOtherApplicationAsync(client, block, titleId, accountSlot, path, stat, errHandling, asyncParams); } - SAVEStatus SAVEGetStatOtherDemoApplicationVariationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + SAVEStatus SAVEGetStatOtherDemoApplicationVariationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { uint64 titleId = SAVE_UNIQUE_DEMO_TO_TITLE_ID_VARIATION(uniqueId, variation); return SAVEGetStatOtherApplicationAsync(client, block, titleId, accountSlot, path, stat, errHandling, asyncParams); @@ -682,14 +583,14 @@ namespace save SAVEStatus SAVEGetFreeSpaceSize(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FSLargeSize* freeSize, FS_ERROR_MASK errHandling) { MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams_t asyncParams; - asyncParams.ioMsgQueue = MPTR_NULL; - asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); + FSAsyncParams asyncParams; + asyncParams.ioMsgQueue = nullptr; + asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); StackAllocator<AsyncCallbackParam_t> param; param->thread = currentThread; param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = param.GetMPTRBE(); + asyncParams.userContext = ¶m; SAVEStatus status = SAVEGetFreeSpaceSizeAsync(client, block, accountSlot, freeSize, errHandling, &asyncParams); if (status == (FSStatus)FS_RESULT::SUCCESS) @@ -722,7 +623,7 @@ namespace save ppcDefineParamU8(accountSlot, 2); ppcDefineParamMEMPTR(returnedFreeSize, FSLargeSize, 3); ppcDefineParamU32(errHandling, 4); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 5); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 5); const SAVEStatus result = SAVEGetFreeSpaceSizeAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, returnedFreeSize.GetPtr(), errHandling, asyncParams.GetPtr()); cemuLog_log(LogType::Save, "SAVEGetFreeSpaceSizeAsync(0x{:08x}, 0x{:08x}, {:x}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, errHandling, result); @@ -743,7 +644,7 @@ namespace save ppcDefineParamU8(accountSlot, 2); ppcDefineParamMEMPTR(path, const char, 3); ppcDefineParamU32(errHandling, 4); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 5); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 5); const SAVEStatus result = SAVERemoveAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), errHandling, asyncParams.GetPtr()); osLib_returnFromFunction(hCPU, result); @@ -752,14 +653,14 @@ namespace save SAVEStatus SAVERemove(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling) { MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams_t asyncParams; - asyncParams.ioMsgQueue = MPTR_NULL; - asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); + FSAsyncParams asyncParams; + asyncParams.ioMsgQueue = nullptr; + asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); StackAllocator<AsyncCallbackParam_t> param; param->thread = currentThread; param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = param.GetMPTRBE(); + asyncParams.userContext = ¶m; SAVEStatus status = SAVERemoveAsync(client, block, accountSlot, path, errHandling, &asyncParams); if (status == (FSStatus)FS_RESULT::SUCCESS) @@ -784,7 +685,7 @@ namespace save osLib_returnFromFunction(hCPU, result); } - SAVEStatus SAVERenameAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* oldPath, const char* newPath, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + SAVEStatus SAVERenameAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* oldPath, const char* newPath, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -798,7 +699,7 @@ namespace save { char fullNewPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPath(persistentId, newPath, fullNewPath)) - result = coreinit::FSRenameAsync(client, block, fullOldPath, fullNewPath, errHandling, (FSAsyncParamsNew_t*)asyncParams); + result = coreinit::FSRenameAsync(client, block, fullOldPath, fullNewPath, errHandling, (FSAsyncParams*)asyncParams); } } else @@ -817,7 +718,7 @@ namespace save ppcDefineParamMEMPTR(oldPath, const char, 3); ppcDefineParamMEMPTR(newPath, const char, 4); ppcDefineParamU32(errHandling, 5); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 6); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 6); const SAVEStatus result = SAVERenameAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, oldPath.GetPtr(), newPath.GetPtr(), errHandling, asyncParams.GetPtr()); cemuLog_log(LogType::Save, "SAVERenameAsync(0x{:08x}, 0x{:08x}, {:x}, {}, {}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, oldPath.GetPtr(), newPath.GetPtr(), errHandling, result); @@ -855,7 +756,7 @@ namespace save ppcDefineParamMEMPTR(path, const char, 3); ppcDefineParamMEMPTR(hDir, betype<FSDirHandle2>, 4); ppcDefineParamU32(errHandling, 5); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 6); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 6); const SAVEStatus result = SAVEOpenDirAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), hDir, errHandling, asyncParams.GetPtr()); cemuLog_log(LogType::Save, "SAVEOpenDirAsync(0x{:08x}, 0x{:08x}, {:x}, {}, 0x{:08x} ({:x}), {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), hDir.GetMPTR(), @@ -866,14 +767,14 @@ namespace save SAVEStatus SAVEOpenDir(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) { MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams_t asyncParams; - asyncParams.ioMsgQueue = MPTR_NULL; - asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); + FSAsyncParams asyncParams; + asyncParams.ioMsgQueue = nullptr; + asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); StackAllocator<AsyncCallbackParam_t> param; param->thread = currentThread; param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = param.GetMPTRBE(); + asyncParams.userContext = ¶m; SAVEStatus status = SAVEOpenDirAsync(client, block, accountSlot, path, hDir, errHandling, &asyncParams); if (status == (FSStatus)FS_RESULT::SUCCESS) @@ -901,7 +802,7 @@ namespace save osLib_returnFromFunction(hCPU, result); } - SAVEStatus SAVEOpenDirOtherApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + SAVEStatus SAVEOpenDirOtherApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -911,7 +812,7 @@ namespace save { char fullPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPathOtherApplication(persistentId, titleId, path, fullPath)) - result = coreinit::FSOpenDirAsync(client, block, fullPath, hDir, errHandling, (FSAsyncParamsNew_t*)asyncParams); + result = coreinit::FSOpenDirAsync(client, block, fullPath, hDir, errHandling, (FSAsyncParams*)asyncParams); } else result = (FSStatus)FS_RESULT::NOT_FOUND; @@ -929,7 +830,7 @@ namespace save ppcDefineParamMEMPTR(path, const char, 4); ppcDefineParamMEMPTR(hDir, betype<FSDirHandle2>, 5); ppcDefineParamU32(errHandling, 6); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 7); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 7); const SAVEStatus result = SAVEOpenDirOtherApplicationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), titleId, accountSlot, path.GetPtr(), hDir, errHandling, asyncParams.GetPtr()); cemuLog_log(LogType::Save, "SAVEOpenDirOtherApplicationAsync(0x{:08x}, 0x{:08x}, {:x}, {:x}, {}, 0x{:08x} ({:x}), {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), titleId, accountSlot, path.GetPtr(), hDir.GetMPTR(), @@ -940,14 +841,14 @@ namespace save SAVEStatus SAVEOpenDirOtherApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) { MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams_t asyncParams; - asyncParams.ioMsgQueue = MPTR_NULL; - asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); + FSAsyncParams asyncParams; + asyncParams.ioMsgQueue = nullptr; + asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); StackAllocator<AsyncCallbackParam_t> param; param->thread = currentThread; param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = param.GetMPTRBE(); + asyncParams.userContext = ¶m; SAVEStatus status = SAVEOpenDirOtherApplicationAsync(client, block, titleId, accountSlot, path, hDir, errHandling, &asyncParams); if (status == (FSStatus)FS_RESULT::SUCCESS) @@ -976,7 +877,7 @@ namespace save osLib_returnFromFunction(hCPU, result); } - SAVEStatus SAVEOpenDirOtherNormalApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + SAVEStatus SAVEOpenDirOtherNormalApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); return SAVEOpenDirOtherApplicationAsync(client, block, titleId, accountSlot, path, hDir, errHandling, asyncParams); @@ -991,7 +892,7 @@ namespace save ppcDefineParamMEMPTR(path, const char, 4); ppcDefineParamMEMPTR(hDir, betype<FSDirHandle2>, 5); ppcDefineParamU32(errHandling, 6); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 7); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 7); const SAVEStatus result = SAVEOpenDirOtherNormalApplicationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, accountSlot, path.GetPtr(), hDir, errHandling, asyncParams.GetPtr()); osLib_returnFromFunction(hCPU, result); @@ -1017,7 +918,7 @@ namespace save osLib_returnFromFunction(hCPU, result); } - SAVEStatus SAVEOpenDirOtherNormalApplicationVariationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + SAVEStatus SAVEOpenDirOtherNormalApplicationVariationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); return SAVEOpenDirOtherApplicationAsync(client, block, titleId, accountSlot, path, hDir, errHandling, asyncParams); @@ -1033,7 +934,7 @@ namespace save ppcDefineParamMEMPTR(path, const char, 5); ppcDefineParamMEMPTR(hDir, betype<FSDirHandle2>, 6); ppcDefineParamU32(errHandling, 7); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 8); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 8); const SAVEStatus result = SAVEOpenDirOtherNormalApplicationVariationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, variation, accountSlot, path.GetPtr(), hDir, errHandling, asyncParams.GetPtr()); osLib_returnFromFunction(hCPU, result); @@ -1067,7 +968,7 @@ namespace save ppcDefineParamU8(accountSlot, 2); ppcDefineParamMEMPTR(path, const char, 3); ppcDefineParamU32(errHandling, 4); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 5); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 5); const SAVEStatus result = SAVEMakeDirAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), errHandling, asyncParams.GetPtr()); cemuLog_log(LogType::Save, "SAVEMakeDirAsync(0x{:08x}, 0x{:08x}, {:x}, {}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), errHandling, result); @@ -1077,14 +978,14 @@ namespace save SAVEStatus SAVEMakeDir(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling) { MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams_t asyncParams; - asyncParams.ioMsgQueue = MPTR_NULL; - asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); + FSAsyncParams asyncParams; + asyncParams.ioMsgQueue = nullptr; + asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); StackAllocator<AsyncCallbackParam_t> param; param->thread = currentThread; param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = param.GetMPTRBE(); + asyncParams.userContext = ¶m; SAVEStatus status = SAVEMakeDirAsync(client, block, accountSlot, path, errHandling, &asyncParams); if (status == (FSStatus)FS_RESULT::SUCCESS) @@ -1110,26 +1011,10 @@ namespace save osLib_returnFromFunction(hCPU, result); } - void export_SAVEOpenFileAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamMEMPTR(mode, const char, 4); - ppcDefineParamMEMPTR(hFile, FSFileHandleDepr_t, 5); - ppcDefineParamU32(errHandling, 6); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParamsNew_t, 7); - - const SAVEStatus result = SAVEOpenFileAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), mode.GetPtr(), hFile.GetPtr(), errHandling, asyncParams.GetPtr()); - cemuLog_log(LogType::Save, "SAVEOpenFileAsync(0x{:08x}, 0x{:08x}, {:x}, {}, {}, 0x{:08x}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), mode.GetPtr(), hFile.GetMPTR(), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenFile(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, const char* mode, FSFileHandleDepr_t* hFile, FS_ERROR_MASK errHandling) + SAVEStatus SAVEOpenFile(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling) { MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParamsNew_t asyncParams; + FSAsyncParams asyncParams; asyncParams.ioMsgQueue = nullptr; asyncParams.userCallback = PPCInterpreter_makeCallableExportDepr(AsyncCallback); @@ -1138,7 +1023,7 @@ namespace save param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; asyncParams.userContext = param.GetPointer(); - SAVEStatus status = SAVEOpenFileAsync(client, block, accountSlot, path, mode, hFile, errHandling, &asyncParams); + SAVEStatus status = SAVEOpenFileAsync(client, block, accountSlot, path, mode, outFileHandle, errHandling, &asyncParams); if (status == (FSStatus)FS_RESULT::SUCCESS) { coreinit_suspendThread(currentThread, 1000); @@ -1149,21 +1034,6 @@ namespace save return status; } - void export_SAVEOpenFile(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamMEMPTR(mode, const char, 4); - ppcDefineParamMEMPTR(hFile, FSFileHandleDepr_t, 5); - ppcDefineParamU32(errHandling, 6); - - const SAVEStatus result = SAVEOpenFile(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), mode.GetPtr(), hFile.GetPtr(), errHandling); - cemuLog_log(LogType::Save, "SAVEOpenFile(0x{:08x}, 0x{:08x}, {:x}, {}, {}, 0x{:08x}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), mode.GetPtr(), hFile.GetMPTR(), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - void export_SAVEInitSaveDir(PPCInterpreter_t* hCPU) { ppcDefineParamU8(accountSlot, 0); @@ -1180,7 +1050,7 @@ namespace save ppcDefineParamMEMPTR(path, const char, 3); ppcDefineParamMEMPTR(stat, FSStat_t, 4); ppcDefineParamU32(errHandling, 5); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 6); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 6); const SAVEStatus result = SAVEGetStatAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), stat.GetPtr(), errHandling, asyncParams.GetPtr()); cemuLog_log(LogType::Save, "SAVEGetStatAsync(0x{:08x}, 0x{:08x}, {:x}, {}, 0x{:08x}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), stat.GetMPTR(), errHandling, result); @@ -1190,14 +1060,14 @@ namespace save SAVEStatus SAVEGetStat(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) { MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams_t asyncParams; - asyncParams.ioMsgQueue = MPTR_NULL; - asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); + FSAsyncParams asyncParams; + asyncParams.ioMsgQueue = nullptr; + asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); StackAllocator<AsyncCallbackParam_t> param; param->thread = currentThread; param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = param.GetMPTRBE(); + asyncParams.userContext = ¶m; SAVEStatus status = SAVEGetStatAsync(client, block, accountSlot, path, stat, errHandling, &asyncParams); if (status == (FSStatus)FS_RESULT::SUCCESS) @@ -1233,7 +1103,7 @@ namespace save ppcDefineParamMEMPTR(path, const char, 5); ppcDefineParamMEMPTR(stat, FSStat_t, 6); ppcDefineParamU32(errHandling, 7); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 8); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 8); const SAVEStatus result = SAVEGetStatOtherApplicationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), titleId, accountSlot, path.GetPtr(), stat.GetPtr(), errHandling, asyncParams.GetPtr()); osLib_returnFromFunction(hCPU, result); @@ -1242,14 +1112,14 @@ namespace save SAVEStatus SAVEGetStatOtherApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) { MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams_t asyncParams; - asyncParams.ioMsgQueue = MPTR_NULL; - asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); + FSAsyncParams asyncParams; + asyncParams.ioMsgQueue = nullptr; + asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); StackAllocator<AsyncCallbackParam_t> param; param->thread = currentThread; param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = param.GetMPTRBE(); + asyncParams.userContext = ¶m; SAVEStatus status = SAVEGetStatOtherApplicationAsync(client, block, titleId, accountSlot, path, stat, errHandling, &asyncParams); if (status == (FSStatus)FS_RESULT::SUCCESS) @@ -1286,7 +1156,7 @@ namespace save ppcDefineParamMEMPTR(path, const char, 4); ppcDefineParamMEMPTR(stat, FSStat_t, 5); ppcDefineParamU32(errHandling, 6); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 8); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 8); const SAVEStatus result = SAVEGetStatOtherNormalApplicationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, accountSlot, path.GetPtr(), stat.GetPtr(), errHandling, asyncParams.GetPtr()); osLib_returnFromFunction(hCPU, result); @@ -1294,8 +1164,6 @@ namespace save SAVEStatus SAVEGetStatOtherNormalApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) { - //peterBreak(); - uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); return SAVEGetStatOtherApplication(client, block, titleId, accountSlot, path, stat, errHandling); } @@ -1326,7 +1194,7 @@ namespace save ppcDefineParamMEMPTR(path, const char, 5); ppcDefineParamMEMPTR(stat, FSStat_t, 6); ppcDefineParamU32(errHandling, 7); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 8); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 8); const SAVEStatus result = SAVEGetStatOtherNormalApplicationVariationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, variation, accountSlot, path.GetPtr(), stat.GetPtr(), errHandling, asyncParams.GetPtr()); osLib_returnFromFunction(hCPU, result); @@ -1397,7 +1265,7 @@ namespace save osLib_returnFromFunction(hCPU, result); } - SAVEStatus SAVEChangeDirAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + SAVEStatus SAVEChangeDirAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -1407,7 +1275,7 @@ namespace save { char fullPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPath(persistentId, path, fullPath)) - result = coreinit::FSChangeDirAsync(client, block, fullPath, errHandling, (FSAsyncParamsNew_t*)asyncParams); + result = coreinit::FSChangeDirAsync(client, block, fullPath, errHandling, (FSAsyncParams*)asyncParams); } else result = (FSStatus)FS_RESULT::NOT_FOUND; @@ -1423,7 +1291,7 @@ namespace save ppcDefineParamU8(accountSlot, 2); ppcDefineParamMEMPTR(path, const char, 3); ppcDefineParamU32(errHandling, 4); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 5); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 5); const SAVEStatus result = SAVEChangeDirAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), errHandling, asyncParams.GetPtr()); cemuLog_log(LogType::Save, "SAVEChangeDirAsync(0x{:08x}, 0x{:08x}, {:x}, {}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), errHandling, result); osLib_returnFromFunction(hCPU, result); @@ -1432,14 +1300,14 @@ namespace save SAVEStatus SAVEChangeDir(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling) { MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams_t asyncParams; - asyncParams.ioMsgQueue = MPTR_NULL; - asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); + FSAsyncParams asyncParams; + asyncParams.ioMsgQueue = nullptr; + asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); StackAllocator<AsyncCallbackParam_t> param; param->thread = currentThread; param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = param.GetMPTRBE(); + asyncParams.userContext = ¶m; SAVEStatus status = SAVEChangeDirAsync(client, block, accountSlot, path, errHandling, &asyncParams); if (status == (FSStatus)FS_RESULT::SUCCESS) @@ -1464,7 +1332,7 @@ namespace save osLib_returnFromFunction(hCPU, result); } - SAVEStatus SAVEFlushQuotaAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FS_ERROR_MASK errHandling, const FSAsyncParams_t* asyncParams) + SAVEStatus SAVEFlushQuotaAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -1475,7 +1343,7 @@ namespace save char fullPath[SAVE_MAX_PATH_SIZE]; if (GetAbsoluteFullPath(persistentId, nullptr, fullPath)) { - result = coreinit::FSFlushQuotaAsync(client, block, fullPath, errHandling, (FSAsyncParamsNew_t*)asyncParams); + result = coreinit::FSFlushQuotaAsync(client, block, fullPath, errHandling, (FSAsyncParams*)asyncParams); // if(OSGetUPID != 0xF) UpdateSaveTimeStamp(persistentId); } @@ -1493,7 +1361,7 @@ namespace save ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); ppcDefineParamU8(accountSlot, 2); ppcDefineParamU32(errHandling, 3); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams_t, 4); + ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 4); const SAVEStatus result = SAVEFlushQuotaAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, errHandling, asyncParams.GetPtr()); cemuLog_log(LogType::Save, "SAVEFlushQuotaAsync(0x{:08x}, 0x{:08x}, {:x}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, errHandling, result); osLib_returnFromFunction(hCPU, result); @@ -1502,14 +1370,14 @@ namespace save SAVEStatus SAVEFlushQuota(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FS_ERROR_MASK errHandling) { MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams_t asyncParams; - asyncParams.ioMsgQueue = MPTR_NULL; - asyncParams.userCallback = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(AsyncCallback)); + FSAsyncParams asyncParams; + asyncParams.ioMsgQueue = nullptr; + asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); StackAllocator<AsyncCallbackParam_t> param; param->thread = currentThread; param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = param.GetMPTRBE(); + asyncParams.userContext = ¶m; SAVEStatus status = SAVEFlushQuotaAsync(client, block, accountSlot, errHandling, &asyncParams); if (status == (FSStatus)FS_RESULT::SUCCESS) @@ -1553,10 +1421,14 @@ namespace save osLib_addFunction("nn_save", "SAVEGetStatOtherNormalApplication", export_SAVEGetStatOtherNormalApplication); osLib_addFunction("nn_save", "SAVEGetStatOtherNormalApplicationVariation", export_SAVEGetStatOtherNormalApplicationVariation); - osLib_addFunction("nn_save", "SAVEOpenFile", export_SAVEOpenFile); - osLib_addFunction("nn_save", "SAVEOpenFileOtherApplication", export_SAVEOpenFileOtherApplication); - osLib_addFunction("nn_save", "SAVEOpenFileOtherNormalApplication", export_SAVEOpenFileOtherNormalApplication); - osLib_addFunction("nn_save", "SAVEOpenFileOtherNormalApplicationVariation", export_SAVEOpenFileOtherNormalApplicationVariation); + cafeExportRegister("nn_save", SAVEOpenFile, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenFileAsync, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenFileOtherApplication, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenFileOtherApplicationAsync, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenFileOtherNormalApplication, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenFileOtherNormalApplicationAsync, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenFileOtherNormalApplicationVariation, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenFileOtherNormalApplicationVariationAsync, LogType::Save); osLib_addFunction("nn_save", "SAVEOpenDir", export_SAVEOpenDir); osLib_addFunction("nn_save", "SAVEOpenDirOtherApplication", export_SAVEOpenDirOtherApplication); @@ -1578,11 +1450,6 @@ namespace save osLib_addFunction("nn_save", "SAVEGetStatOtherNormalApplicationAsync", export_SAVEGetStatOtherNormalApplicationAsync); osLib_addFunction("nn_save", "SAVEGetStatOtherNormalApplicationVariationAsync", export_SAVEGetStatOtherNormalApplicationVariationAsync); - osLib_addFunction("nn_save", "SAVEOpenFileAsync", export_SAVEOpenFileAsync); - osLib_addFunction("nn_save", "SAVEOpenFileOtherApplicationAsync", export_SAVEOpenFileOtherApplicationAsync); - osLib_addFunction("nn_save", "SAVEOpenFileOtherNormalApplicationAsync", export_SAVEOpenFileOtherNormalApplicationAsync); - osLib_addFunction("nn_save", "SAVEOpenFileOtherNormalApplicationVariationAsync", export_SAVEOpenFileOtherNormalApplicationVariationAsync); - osLib_addFunction("nn_save", "SAVEOpenDirAsync", export_SAVEOpenDirAsync); osLib_addFunction("nn_save", "SAVEOpenDirOtherApplicationAsync", export_SAVEOpenDirOtherApplicationAsync); osLib_addFunction("nn_save", "SAVEOpenDirOtherNormalApplicationAsync", export_SAVEOpenDirOtherNormalApplicationAsync); diff --git a/src/Cafe/OS/libs/proc_ui/proc_ui.cpp b/src/Cafe/OS/libs/proc_ui/proc_ui.cpp index 91d15af4..5560568d 100644 --- a/src/Cafe/OS/libs/proc_ui/proc_ui.cpp +++ b/src/Cafe/OS/libs/proc_ui/proc_ui.cpp @@ -511,7 +511,7 @@ namespace proc_ui { MEMPTR<void> fgBase; uint32be fgFreeSize; - OSGetForegroundBucketFreeArea((MPTR*)&fgBase, (MPTR*)&fgFreeSize); + OSGetForegroundBucketFreeArea(&fgBase, &fgFreeSize); if(fgFreeSize < size) cemuLog_log(LogType::Force, "ProcUISetBucketStorage: Buffer size too small"); s_bucketStorageBasePtr = memBase; @@ -521,7 +521,7 @@ namespace proc_ui { MEMPTR<void> memBound; uint32be memBoundSize; - OSGetMemBound(1, (MPTR*)memBound.GetBEPtr(), (uint32*)&memBoundSize); + OSGetMemBound(1, &memBound, &memBoundSize); if(memBoundSize < size) cemuLog_log(LogType::Force, "ProcUISetMEM1Storage: Buffer size too small"); s_mem1StorageBasePtr = memBase; @@ -751,14 +751,14 @@ namespace proc_ui { MEMPTR<void> memBound; uint32be memBoundSize; - OSGetMemBound(1, (MPTR*)memBound.GetBEPtr(), (uint32*)&memBoundSize); + OSGetMemBound(1, &memBound, &memBoundSize); OSBlockMove(s_mem1StorageBasePtr.GetPtr(), memBound.GetPtr(), memBoundSize, true); } if (s_bucketStorageBasePtr) { MEMPTR<void> memBound; uint32be memBoundSize; - OSGetForegroundBucketFreeArea((MPTR*)memBound.GetBEPtr(), (MPTR*)&memBoundSize); + OSGetForegroundBucketFreeArea(&memBound, &memBoundSize); OSBlockMove(s_bucketStorageBasePtr.GetPtr(), memBound.GetPtr(), memBoundSize, true); } } @@ -769,7 +769,7 @@ namespace proc_ui { MEMPTR<void> memBound; uint32be memBoundSize; - OSGetMemBound(1, (MPTR*)memBound.GetBEPtr(), (uint32*)&memBoundSize); + OSGetMemBound(1, &memBound, &memBoundSize); OSBlockMove(memBound.GetPtr(), s_mem1StorageBasePtr, memBoundSize, true); GX2::GX2Invalidate(0x40, s_mem1StorageBasePtr.GetMPTR(), memBoundSize); } @@ -777,7 +777,7 @@ namespace proc_ui { MEMPTR<void> memBound; uint32be memBoundSize; - OSGetForegroundBucketFreeArea((MPTR*)memBound.GetBEPtr(), (MPTR*)&memBoundSize); + OSGetForegroundBucketFreeArea(&memBound, &memBoundSize); OSBlockMove(memBound.GetPtr(), s_bucketStorageBasePtr, memBoundSize, true); GX2::GX2Invalidate(0x40, memBound.GetMPTR(), memBoundSize); } diff --git a/src/Common/MemPtr.h b/src/Common/MemPtr.h index de787cc1..b2362d0b 100644 --- a/src/Common/MemPtr.h +++ b/src/Common/MemPtr.h @@ -136,16 +136,10 @@ public: C* GetPtr() const { return (C*)(GetPtr()); } constexpr uint32 GetMPTR() const { return m_value.value(); } - constexpr uint32 GetRawValue() const { return m_value.bevalue(); } // accesses value using host-endianness - constexpr const uint32be& GetBEValue() const { return m_value; } constexpr bool IsNull() const { return m_value == 0; } - constexpr uint32 GetMPTRBE() const { return m_value.bevalue(); } - - uint32be* GetBEPtr() { return &m_value; } - private: uint32be m_value; }; diff --git a/src/Common/StackAllocator.h b/src/Common/StackAllocator.h index a69b7aaa..1dc52d51 100644 --- a/src/Common/StackAllocator.h +++ b/src/Common/StackAllocator.h @@ -28,7 +28,6 @@ public: T* GetPointer() const { return m_ptr; } uint32 GetMPTR() const { return MEMPTR<T>(m_ptr).GetMPTR(); } - uint32 GetMPTRBE() const { return MEMPTR<T>(m_ptr).GetMPTRBE(); } T* operator&() { return GetPointer(); } explicit operator T*() const { return GetPointer(); } From c11d83e9d8980bdc8978001583ddaa5ca0b6529b Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Fri, 3 May 2024 02:41:05 +0200 Subject: [PATCH 091/299] coreinit: Implement MCP_GetTitleId --- src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp b/src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp index 14d7a645..330663ac 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_MCP.cpp @@ -415,6 +415,12 @@ namespace coreinit return 0; } + uint32 MCP_GetTitleId(uint32 mcpHandle, uint64be* outTitleId) + { + *outTitleId = CafeSystem::GetForegroundTitleId(); + return 0; + } + void InitializeMCP() { osLib_addFunction("coreinit", "MCP_Open", coreinitExport_MCP_Open); @@ -442,6 +448,8 @@ namespace coreinit cafeExportRegister("coreinit", MCP_RightCheckLaunchable, LogType::Placeholder); cafeExportRegister("coreinit", MCP_GetEcoSettings, LogType::Placeholder); + + cafeExportRegister("coreinit", MCP_GetTitleId, LogType::Placeholder); } } From 1b5c885621c8a2e6057cc6c6c0bd557f6da5a327 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Fri, 3 May 2024 02:41:39 +0200 Subject: [PATCH 092/299] nn_acp: Implement ACPGetTitleMetaXml --- src/Cafe/OS/libs/nn_acp/nn_acp.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Cafe/OS/libs/nn_acp/nn_acp.cpp b/src/Cafe/OS/libs/nn_acp/nn_acp.cpp index 61640ae7..37ea471f 100644 --- a/src/Cafe/OS/libs/nn_acp/nn_acp.cpp +++ b/src/Cafe/OS/libs/nn_acp/nn_acp.cpp @@ -289,6 +289,18 @@ namespace acp osLib_returnFromFunction(hCPU, acpRequest->returnCode); } + uint32 ACPGetTitleMetaXml(uint64 titleId, acpMetaXml_t* acpMetaXml) + { + acpPrepareRequest(); + acpRequest->requestCode = IOSU_ACP_GET_TITLE_META_XML; + acpRequest->ptr = acpMetaXml; + acpRequest->titleId = titleId; + + __depr__IOS_Ioctlv(IOS_DEVICE_ACP_MAIN, IOSU_ACP_REQUEST_CEMU, 1, 1, acpBufferVector); + + return acpRequest->returnCode; + } + void export_ACPIsOverAgeEx(PPCInterpreter_t* hCPU) { ppcDefineParamU32(age, 0); @@ -341,6 +353,7 @@ namespace acp osLib_addFunction("nn_acp", "ACPGetTitleMetaDirByDevice", export_ACPGetTitleMetaDirByDevice); osLib_addFunction("nn_acp", "ACPGetTitleMetaXmlByDevice", export_ACPGetTitleMetaXmlByDevice); + cafeExportRegister("nn_acp", ACPGetTitleMetaXml, LogType::Placeholder); cafeExportRegister("nn_acp", ACPGetApplicationBox, LogType::Placeholder); From 041f29a914b0e0ef88b4dad863cf71a6fdcca84f Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Fri, 3 May 2024 02:43:51 +0200 Subject: [PATCH 093/299] nn_act: Implement GetTimeZoneId placeholder --- src/Cafe/OS/libs/nn_act/nn_act.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Cafe/OS/libs/nn_act/nn_act.cpp b/src/Cafe/OS/libs/nn_act/nn_act.cpp index 2a9f61bc..af53edd7 100644 --- a/src/Cafe/OS/libs/nn_act/nn_act.cpp +++ b/src/Cafe/OS/libs/nn_act/nn_act.cpp @@ -5,6 +5,7 @@ #include "nn_act.h" #include "Cafe/OS/libs/nn_common.h" #include "Cafe/CafeSystem.h" +#include "Common/CafeString.h" sint32 numAccounts = 1; @@ -140,6 +141,14 @@ namespace act return 0; } + nnResult GetTimeZoneId(CafeString<65>* outTimezoneId) + { + // return a placeholder timezone id for now + // in the future we should emulated this correctly and read the timezone from the account via IOSU + outTimezoneId->assign("Europe/London"); + return 0; + } + sint32 g_initializeCount = 0; // inc in Initialize and dec in Finalize uint32 Initialize() { @@ -162,7 +171,6 @@ namespace act NN_ERROR_CODE errCode = NNResultToErrorCode(*nnResult, NN_RESULT_MODULE_NN_ACT); return errCode; } - } } @@ -691,6 +699,8 @@ void nnAct_load() osLib_addFunction("nn_act", "GetPersistentIdEx__Q2_2nn3actFUc", nnActExport_GetPersistentIdEx); // country osLib_addFunction("nn_act", "GetCountry__Q2_2nn3actFPc", nnActExport_GetCountry); + // timezone + cafeExportRegisterFunc(nn::act::GetTimeZoneId, "nn_act", "GetTimeZoneId__Q2_2nn3actFPc", LogType::Placeholder); // parental osLib_addFunction("nn_act", "EnableParentalControlCheck__Q2_2nn3actFb", nnActExport_EnableParentalControlCheck); From a16c37f0c5b2435a829fc5348c66297d9c762347 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 4 May 2024 07:05:59 +0200 Subject: [PATCH 094/299] coreinit: Rework thread creation New implementation is much closer to console behavior. For example we didn't align the stack which would cause crashes in the Miiverse applet --- src/Cafe/HW/Latte/Core/LatteThread.cpp | 2 +- src/Cafe/OS/libs/coreinit/coreinit.cpp | 10 +- src/Cafe/OS/libs/coreinit/coreinit_GHS.cpp | 4 +- src/Cafe/OS/libs/coreinit/coreinit_IPC.cpp | 2 +- src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp | 315 ++++++++++++++---- src/Cafe/OS/libs/coreinit/coreinit_Thread.h | 69 ++-- src/Cafe/OS/libs/nsysnet/nsysnet.cpp | 4 +- src/Cafe/OS/libs/snd_core/ax_ist.cpp | 2 +- .../ExceptionHandler/ExceptionHandler.cpp | 2 +- .../DebugPPCThreadsWindow.cpp | 4 +- 10 files changed, 297 insertions(+), 117 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/LatteThread.cpp b/src/Cafe/HW/Latte/Core/LatteThread.cpp index a23bd5be..8874ecf4 100644 --- a/src/Cafe/HW/Latte/Core/LatteThread.cpp +++ b/src/Cafe/HW/Latte/Core/LatteThread.cpp @@ -187,7 +187,7 @@ int Latte_ThreadEntry() rule.overwrite_settings.width >= 0 || rule.overwrite_settings.height >= 0 || rule.overwrite_settings.depth >= 0) { LatteGPUState.allowFramebufferSizeOptimization = false; - cemuLog_log(LogType::Force, "Graphic pack {} prevents rendertarget size optimization.", pack->GetName()); + cemuLog_log(LogType::Force, "Graphic pack \"{}\" prevents rendertarget size optimization. This warning can be ignored and is intended for graphic pack developers", pack->GetName()); break; } } diff --git a/src/Cafe/OS/libs/coreinit/coreinit.cpp b/src/Cafe/OS/libs/coreinit/coreinit.cpp index e18d0e8d..49d232f8 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit.cpp @@ -35,12 +35,12 @@ #include "Cafe/OS/libs/coreinit/coreinit_MEM_BlockHeap.h" #include "Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.h" -CoreinitSharedData* gCoreinitData = NULL; +CoreinitSharedData* gCoreinitData = nullptr; sint32 ScoreStackTrace(OSThread_t* thread, MPTR sp) { - uint32 stackMinAddr = _swapEndianU32(thread->stackEnd); - uint32 stackMaxAddr = _swapEndianU32(thread->stackBase); + uint32 stackMinAddr = thread->stackEnd.GetMPTR(); + uint32 stackMaxAddr = thread->stackBase.GetMPTR(); sint32 score = 0; uint32 currentStackPtr = sp; @@ -95,8 +95,8 @@ void DebugLogStackTrace(OSThread_t* thread, MPTR sp) // print stack trace uint32 currentStackPtr = highestScoreSP; - uint32 stackMinAddr = _swapEndianU32(thread->stackEnd); - uint32 stackMaxAddr = _swapEndianU32(thread->stackBase); + uint32 stackMinAddr = thread->stackEnd.GetMPTR(); + uint32 stackMaxAddr = thread->stackBase.GetMPTR(); for (sint32 i = 0; i < 20; i++) { uint32 nextStackPtr = memory_readU32(currentStackPtr); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_GHS.cpp b/src/Cafe/OS/libs/coreinit/coreinit_GHS.cpp index 5699e3e7..e2864fb9 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_GHS.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_GHS.cpp @@ -22,7 +22,7 @@ namespace coreinit MPTR _iob_lock[GHS_FOPEN_MAX]; uint16be __gh_FOPEN_MAX; MEMPTR<void> ghs_environ; - uint32 ghs_Errno; // exposed by __gh_errno_ptr() or via 'errno' data export + uint32 ghs_Errno; // exposed as 'errno' data export }; SysAllocator<GHSAccessibleData> g_ghs_data; @@ -159,7 +159,7 @@ namespace coreinit void* __gh_errno_ptr() { OSThread_t* currentThread = coreinit::OSGetCurrentThread(); - return ¤tThread->context.error; + return ¤tThread->context.ghs_errno; } void* __get_eh_store_globals() diff --git a/src/Cafe/OS/libs/coreinit/coreinit_IPC.cpp b/src/Cafe/OS/libs/coreinit/coreinit_IPC.cpp index be3cb300..12d83afc 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_IPC.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_IPC.cpp @@ -204,7 +204,7 @@ namespace coreinit // and a message queue large enough to hold the maximum number of commands (IPC_NUM_RESOURCE_BUFFERS) OSInitMessageQueue(gIPCThreadMsgQueue.GetPtr() + coreIndex, _gIPCThreadSemaphoreStorage.GetPtr() + coreIndex * IPC_NUM_RESOURCE_BUFFERS, IPC_NUM_RESOURCE_BUFFERS); OSThread_t* ipcThread = gIPCThread.GetPtr() + coreIndex; - OSCreateThreadType(ipcThread, PPCInterpreter_makeCallableExportDepr(__IPCDriverThreadFunc), 0, nullptr, _gIPCThreadStack.GetPtr() + 0x4000 * coreIndex + 0x4000, 0x4000, 15, (1 << coreIndex), OSThread_t::THREAD_TYPE::TYPE_DRIVER); + __OSCreateThreadType(ipcThread, PPCInterpreter_makeCallableExportDepr(__IPCDriverThreadFunc), 0, nullptr, _gIPCThreadStack.GetPtr() + 0x4000 * coreIndex + 0x4000, 0x4000, 15, (1 << coreIndex), OSThread_t::THREAD_TYPE::TYPE_DRIVER); sprintf((char*)_gIPCThreadNameStorage.GetPtr()+coreIndex*0x18, "{SYS IPC Core %d}", coreIndex); OSSetThreadName(ipcThread, (char*)_gIPCThreadNameStorage.GetPtr() + coreIndex * 0x18); OSResumeThread(ipcThread); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp index 654e57a8..533360aa 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp @@ -215,14 +215,171 @@ namespace coreinit hCPU->spr.LR = lr; hCPU->gpr[3] = r3; hCPU->gpr[4] = r4; - hCPU->instructionPointer = _swapEndianU32(currentThread->entrypoint); + hCPU->instructionPointer = currentThread->entrypoint.GetMPTR(); } void coreinitExport_OSExitThreadDepr(PPCInterpreter_t* hCPU); - void OSCreateThreadInternal(OSThread_t* thread, uint32 entryPoint, MPTR stackLowerBaseAddr, uint32 stackSize, uint8 affinityMask, OSThread_t::THREAD_TYPE threadType) + void __OSInitContext(OSContext_t* ctx, MEMPTR<void> initialIP, MEMPTR<void> initialStackPointer) + { + ctx->SetContextMagic(); + ctx->gpr[0] = 0; // r0 is left uninitialized on console? + for(auto& it : ctx->gpr) + it = 0; + ctx->gpr[1] = _swapEndianU32(initialStackPointer.GetMPTR()); + ctx->gpr[2] = _swapEndianU32(RPLLoader_GetSDA2Base()); + ctx->gpr[13] = _swapEndianU32(RPLLoader_GetSDA1Base()); + ctx->srr0 = initialIP.GetMPTR(); + ctx->cr = 0; + ctx->ukn0A8 = 0; + ctx->ukn0AC = 0; + ctx->gqr[0] = 0; + ctx->gqr[1] = 0; + ctx->gqr[2] = 0; + ctx->gqr[3] = 0; + ctx->gqr[4] = 0; + ctx->gqr[5] = 0; + ctx->gqr[6] = 0; + ctx->gqr[7] = 0; + ctx->dsi_dar = 0; + ctx->srr1 = 0x9032; + ctx->xer = 0; + ctx->dsi_dsisr = 0; + ctx->upir = 0; + ctx->boostCount = 0; + ctx->state = 0; + for(auto& it : ctx->coretime) + it = 0; + ctx->starttime = 0; + ctx->ghs_errno = 0; + ctx->upmc1 = 0; + ctx->upmc2 = 0; + ctx->upmc3 = 0; + ctx->upmc4 = 0; + ctx->ummcr0 = 0; + ctx->ummcr1 = 0; + } + + void __OSThreadInit(OSThread_t* thread, MEMPTR<void> entrypoint, uint32 argInt, MEMPTR<void> argPtr, MEMPTR<void> stackTop, uint32 stackSize, sint32 priority, uint32 upirCoreIndex, OSThread_t::THREAD_TYPE threadType) + { + thread->effectivePriority = priority; + thread->type = threadType; + thread->basePriority = priority; + thread->SetThreadMagic(); + thread->id = 0x8000; + thread->waitAlarm = nullptr; + thread->entrypoint = entrypoint; + thread->quantumTicks = 0; + if(entrypoint) + { + thread->state = OSThread_t::THREAD_STATE::STATE_READY; + thread->suspendCounter = 1; + } + else + { + thread->state = OSThread_t::THREAD_STATE::STATE_NONE; + thread->suspendCounter = 0; + } + thread->exitValue = (uint32)-1; + thread->requestFlags = OSThread_t::REQUEST_FLAG_BIT::REQUEST_FLAG_NONE; + thread->pendingSuspend = 0; + thread->suspendResult = 0xFFFFFFFF; + thread->coretimeSumQuantumStart = 0; + thread->deallocatorFunc = nullptr; + thread->cleanupCallback = nullptr; + thread->waitingForFastMutex = nullptr; + thread->stateFlags = 0; + thread->waitingForMutex = nullptr; + memset(&thread->crt, 0, sizeof(thread->crt)); + static_assert(sizeof(thread->crt) == 0x1D8); + thread->tlsBlocksMPTR = 0; + thread->numAllocatedTLSBlocks = 0; + thread->tlsStatus = 0; + OSInitThreadQueueEx(&thread->joinQueue, thread); + OSInitThreadQueueEx(&thread->suspendQueue, thread); + thread->mutexQueue.ukn08 = thread; + thread->mutexQueue.ukn0C = 0; + thread->mutexQueue.tail = nullptr; + thread->mutexQueue.head = nullptr; + thread->ownedFastMutex.next = nullptr; + thread->ownedFastMutex.prev = nullptr; + thread->contendedFastMutex.next = nullptr; + thread->contendedFastMutex.prev = nullptr; + + MEMPTR<void> alignedStackTop{MEMPTR<void>(stackTop).GetMPTR() & 0xFFFFFFF8}; + MEMPTR<uint32be> alignedStackTop32{alignedStackTop}; + alignedStackTop32[-1] = 0; + alignedStackTop32[-2] = 0; + + __OSInitContext(&thread->context, MEMPTR<void>(PPCInterpreter_makeCallableExportDepr(threadEntry)), (void*)(alignedStackTop32.GetPtr() - 2)); + thread->stackBase = stackTop; // without alignment + thread->stackEnd = ((uint8*)stackTop.GetPtr() - stackSize); + thread->context.upir = upirCoreIndex; + thread->context.lr = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(coreinitExport_OSExitThreadDepr)); + thread->context.gpr[3] = _swapEndianU32(argInt); + thread->context.gpr[4] = _swapEndianU32(argPtr.GetMPTR()); + + *(uint32be*)((uint8*)stackTop.GetPtr() - stackSize) = 0xDEADBABE; + thread->alarmRelatedUkn = 0; + for(auto& it : thread->specificArray) + it = nullptr; + thread->context.fpscr.fpscr = 4; + for(sint32 i=0; i<32; i++) + { + thread->context.fp_ps0[i] = 0.0; + thread->context.fp_ps1[i] = 0.0; + } + thread->context.gqr[2] = 0x40004; + thread->context.gqr[3] = 0x50005; + thread->context.gqr[4] = 0x60006; + thread->context.gqr[5] = 0x70007; + + for(sint32 i=0; i<Espresso::CORE_COUNT; i++) + thread->context.coretime[i] = 0; + + // currentRunQueue and waitQueueLink is not initialized by COS and instead overwritten without validation + // since we already have integrity checks in other functions, lets initialize it here + for(sint32 i=0; i<Espresso::CORE_COUNT; i++) + thread->currentRunQueue[i] = nullptr; + thread->waitQueueLink.prev = nullptr; + thread->waitQueueLink.next = nullptr; + + thread->wakeTimeRelatedUkn2 = 0; + thread->wakeUpCount = 0; + thread->wakeUpTime = 0; + thread->wakeTimeRelatedUkn1 = 0x7FFFFFFFFFFFFFFF; + thread->quantumTicks = 0; + thread->coretimeSumQuantumStart = 0; + thread->totalCycles = 0; + + for(auto& it : thread->padding68C) + it = 0; + } + + void SetThreadAffinityToCore(OSThread_t* thread, uint32 coreIndex) + { + cemu_assert_debug(coreIndex < 3); + thread->attr &= ~(OSThread_t::ATTR_BIT::ATTR_AFFINITY_CORE0 | OSThread_t::ATTR_BIT::ATTR_AFFINITY_CORE1 | OSThread_t::ATTR_BIT::ATTR_AFFINITY_CORE2 | OSThread_t::ATTR_BIT::ATTR_UKN_010); + thread->context.affinity &= 0xFFFFFFF8; + if (coreIndex == 0) + { + thread->attr |= OSThread_t::ATTR_BIT::ATTR_AFFINITY_CORE0; + thread->context.affinity |= (1<<0); + } + else if (coreIndex == 1) + { + thread->attr |= OSThread_t::ATTR_BIT::ATTR_AFFINITY_CORE1; + thread->context.affinity |= (1<<1); + } + else // if (coreIndex == 2) + { + thread->attr |= OSThread_t::ATTR_BIT::ATTR_AFFINITY_CORE2; + thread->context.affinity |= (1<<2); + } + } + + void __OSCreateThreadOnActiveThreadWorkaround(OSThread_t* thread) { - cemu_assert_debug(thread != nullptr); // make thread struct mandatory. Caller can always use SysAllocator __OSLockScheduler(); bool isThreadStillActive = __OSIsThreadActive(thread); if (isThreadStillActive) @@ -248,84 +405,97 @@ namespace coreinit } cemu_assert_debug(__OSIsThreadActive(thread) == false); __OSUnlockScheduler(); - memset(thread, 0x00, sizeof(OSThread_t)); - // init signatures - thread->SetMagic(); - thread->type = threadType; - thread->state = (entryPoint != MPTR_NULL) ? OSThread_t::THREAD_STATE::STATE_READY : OSThread_t::THREAD_STATE::STATE_NONE; - thread->entrypoint = _swapEndianU32(entryPoint); - __OSSetThreadBasePriority(thread, 0); - __OSUpdateThreadEffectivePriority(thread); - // untested, but seems to work (Batman Arkham City uses these values to calculate the stack size for duplicated threads) - thread->stackBase = _swapEndianU32(stackLowerBaseAddr + stackSize); // these fields are quite important and lots of games rely on them being accurate (Examples: Darksiders 2, SMW3D, Batman Arkham City) - thread->stackEnd = _swapEndianU32(stackLowerBaseAddr); - // init stackpointer - thread->context.gpr[GPR_SP] = _swapEndianU32(stackLowerBaseAddr + stackSize - 0x20); // how many free bytes should there be at the beginning of the stack? - // init misc stuff - thread->attr = affinityMask; - thread->context.setAffinity(affinityMask); - thread->context.srr0 = PPCInterpreter_makeCallableExportDepr(threadEntry); - thread->context.lr = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(coreinitExport_OSExitThreadDepr)); - thread->id = 0x8000; // Warriors Orochi 3 softlocks if this is zero due to confusing threads (_OSActivateThread should set this?) - // init ugqr - thread->context.gqr[0] = 0x00000000; - thread->context.gqr[1] = 0x00000000; - thread->context.gqr[2] = 0x00040004; - thread->context.gqr[3] = 0x00050005; - thread->context.gqr[4] = 0x00060006; - thread->context.gqr[5] = 0x00070007; - thread->context.gqr[6] = 0x00000000; - thread->context.gqr[7] = 0x00000000; - // init r2 (SDA2) and r3 (SDA) - thread->context.gpr[2] = _swapEndianU32(RPLLoader_GetSDA2Base()); - thread->context.gpr[13] = _swapEndianU32(RPLLoader_GetSDA1Base()); - // GHS related thread init? + } - __OSLockScheduler(); - // if entrypoint is non-zero then put the thread on the active list and suspend it - if (entryPoint != MPTR_NULL) + bool __OSCreateThreadInternal2(OSThread_t* thread, MEMPTR<void> entrypoint, uint32 argInt, MEMPTR<void> argPtr, MEMPTR<void> stackBase, uint32 stackSize, sint32 priority, uint32 attrBits, OSThread_t::THREAD_TYPE threadType) + { + __OSCreateThreadOnActiveThreadWorkaround(thread); + OSThread_t* currentThread = OSGetCurrentThread(); + if (priority < 0 || priority >= 32) { - thread->suspendCounter = 1; - __OSActivateThread(thread); - thread->state = OSThread_t::THREAD_STATE::STATE_READY; + cemuLog_log(LogType::APIErrors, "OSCreateThreadInternal: Thread priority must be in range 0-31"); + return false; + } + if (threadType == OSThread_t::THREAD_TYPE::TYPE_IO) + { + priority = priority + 0x20; + } + else if (threadType == OSThread_t::THREAD_TYPE::TYPE_APP) + { + priority = priority + 0x40; + } + if(attrBits >= 0x20 || stackBase == nullptr || stackSize == 0) + { + cemuLog_logDebug(LogType::APIErrors, "OSCreateThreadInternal: Invalid attributes, stack base or size"); + return false; + } + uint32 im = OSDisableInterrupts(); + __OSLockScheduler(thread); + + uint32 coreIndex = PPCInterpreter_getCurrentInstance() ? OSGetCoreId() : 1; + __OSThreadInit(thread, entrypoint, argInt, argPtr, stackBase, stackSize, priority, coreIndex, threadType); + thread->threadName = nullptr; + thread->context.affinity = attrBits & 7; + thread->attr = attrBits; + if ((attrBits & 7) == 0) // if no explicit affinity is given, use the current core + SetThreadAffinityToCore(thread, OSGetCoreId()); + if(currentThread) + { + for(sint32 i=0; i<Espresso::CORE_COUNT; i++) + { + thread->dsiCallback[i] = currentThread->dsiCallback[i]; + thread->isiCallback[i] = currentThread->isiCallback[i]; + thread->programCallback[i] = currentThread->programCallback[i]; + thread->perfMonCallback[i] = currentThread->perfMonCallback[i]; + thread->alignmentExceptionCallback[i] = currentThread->alignmentExceptionCallback[i]; + } + thread->context.srr1 = thread->context.srr1 | (currentThread->context.srr1 & 0x900); + thread->context.fpscr.fpscr = thread->context.fpscr.fpscr | (currentThread->context.fpscr.fpscr & 0xF8); } else - thread->suspendCounter = 0; - __OSUnlockScheduler(); + { + for(sint32 i=0; i<Espresso::CORE_COUNT; i++) + { + thread->dsiCallback[i] = 0; + thread->isiCallback[i] = 0; + thread->programCallback[i] = 0; + thread->perfMonCallback[i] = 0; + thread->alignmentExceptionCallback[i] = nullptr; + } + } + if (entrypoint) + { + thread->id = 0x8000; + __OSActivateThread(thread); // also handles adding the thread to g_activeThreadQueue + } + __OSUnlockScheduler(thread); + OSRestoreInterrupts(im); + // recompile entry point function + if (entrypoint) + PPCRecompiler_recompileIfUnvisited(entrypoint.GetMPTR()); + return true; } bool OSCreateThreadType(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop, sint32 stackSize, sint32 priority, uint32 attr, OSThread_t::THREAD_TYPE threadType) { - OSCreateThreadInternal(thread, entryPoint, memory_getVirtualOffsetFromPointer(stackTop) - stackSize, stackSize, attr, threadType); - thread->context.gpr[3] = _swapEndianU32(numParam); // num arguments - thread->context.gpr[4] = _swapEndianU32(memory_getVirtualOffsetFromPointer(ptrParam)); // arguments pointer - __OSSetThreadBasePriority(thread, priority); - __OSUpdateThreadEffectivePriority(thread); - // set affinity - uint8 affinityMask = 0; - affinityMask = attr & 0x7; - // if no core is selected -> set current one - if (affinityMask == 0) - affinityMask |= (1 << PPCInterpreter_getCoreIndex(PPCInterpreter_getCurrentInstance())); - // set attr - // todo: Support for other attr bits - thread->attr = (affinityMask & 0xFF) | (attr & OSThread_t::ATTR_BIT::ATTR_DETACHED); - thread->context.setAffinity(affinityMask); - // recompile entry point function - if (entryPoint != MPTR_NULL) - PPCRecompiler_recompileIfUnvisited(entryPoint); - return true; + if(threadType != OSThread_t::THREAD_TYPE::TYPE_APP && threadType != OSThread_t::THREAD_TYPE::TYPE_IO) + { + cemuLog_logDebug(LogType::APIErrors, "OSCreateThreadType: Invalid thread type"); + cemu_assert_suspicious(); + return false; + } + return __OSCreateThreadInternal2(thread, MEMPTR<void>(entryPoint), numParam, ptrParam, stackTop, stackSize, priority, attr, threadType); } bool OSCreateThread(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop, sint32 stackSize, sint32 priority, uint32 attr) { - return OSCreateThreadType(thread, entryPoint, numParam, ptrParam, stackTop, stackSize, priority, attr, OSThread_t::THREAD_TYPE::TYPE_APP); + return __OSCreateThreadInternal2(thread, MEMPTR<void>(entryPoint), numParam, ptrParam, stackTop, stackSize, priority, attr, OSThread_t::THREAD_TYPE::TYPE_APP); } - // alias to OSCreateThreadType, similar to OSCreateThread, but with an additional parameter for the thread type + // similar to OSCreateThreadType, but can be used to create any type of thread bool __OSCreateThreadType(OSThread_t* thread, MPTR entryPoint, sint32 numParam, void* ptrParam, void* stackTop, sint32 stackSize, sint32 priority, uint32 attr, OSThread_t::THREAD_TYPE threadType) { - return OSCreateThreadType(thread, entryPoint, numParam, ptrParam, stackTop, stackSize, priority, attr, threadType); + return __OSCreateThreadInternal2(thread, MEMPTR<void>(entryPoint), numParam, ptrParam, stackTop, stackSize, priority, attr, threadType); } bool OSRunThread(OSThread_t* thread, MPTR funcAddress, sint32 numParam, void* ptrParam) @@ -352,7 +522,7 @@ namespace coreinit // set thread state // todo - this should fully reinitialize the thread? - thread->entrypoint = _swapEndianU32(funcAddress); + thread->entrypoint = funcAddress; thread->context.srr0 = PPCInterpreter_makeCallableExportDepr(threadEntry); thread->context.lr = _swapEndianU32(PPCInterpreter_makeCallableExportDepr(coreinitExport_OSExitThreadDepr)); thread->context.gpr[3] = _swapEndianU32(numParam); @@ -378,10 +548,10 @@ namespace coreinit OSThread_t* currentThread = coreinit::OSGetCurrentThread(); // thread cleanup callback - if (!currentThread->cleanupCallback2.IsNull()) + if (currentThread->cleanupCallback) { currentThread->stateFlags = _swapEndianU32(_swapEndianU32(currentThread->stateFlags) | 0x00000001); - PPCCoreCallback(currentThread->cleanupCallback2.GetMPTR(), currentThread, _swapEndianU32(currentThread->stackEnd)); + PPCCoreCallback(currentThread->cleanupCallback.GetMPTR(), currentThread, currentThread->stackEnd); } // cpp exception cleanup if (gCoreinitData->__cpp_exception_cleanup_ptr != 0 && currentThread->crt.eh_globals != nullptr) @@ -602,7 +772,10 @@ namespace coreinit sint32 previousSuspendCount = thread->suspendCounter; cemu_assert_debug(previousSuspendCount >= 0); if (previousSuspendCount == 0) + { + cemuLog_log(LogType::APIErrors, "OSResumeThread: Resuming thread 0x{:08x} which isn't suspended", MEMPTR<OSThread_t>(thread).GetMPTR()); return 0; + } thread->suspendCounter = previousSuspendCount - resumeCount; if (thread->suspendCounter < 0) thread->suspendCounter = 0; @@ -732,8 +905,8 @@ namespace coreinit void* OSSetThreadCleanupCallback(OSThread_t* thread, void* cleanupCallback) { __OSLockScheduler(); - void* previousFunc = thread->cleanupCallback2.GetPtr(); - thread->cleanupCallback2 = cleanupCallback; + void* previousFunc = thread->cleanupCallback.GetPtr(); + thread->cleanupCallback = cleanupCallback; __OSUnlockScheduler(); return previousFunc; } @@ -1341,7 +1514,7 @@ namespace coreinit void __OSQueueThreadDeallocation(OSThread_t* thread) { uint32 coreIndex = OSGetCoreId(); - TerminatorThread::DeallocatorQueueEntry queueEntry(thread, memory_getPointerFromVirtualOffset(_swapEndianU32(thread->stackEnd)), thread->deallocatorFunc); + TerminatorThread::DeallocatorQueueEntry queueEntry(thread, thread->stackEnd, thread->deallocatorFunc); s_terminatorThreads[coreIndex].queueDeallocators.push(queueEntry); OSSignalSemaphoreInternal(s_terminatorThreads[coreIndex].semaphoreQueuedDeallocators.GetPtr(), false); // do not reschedule here! Current thread must not be interrupted otherwise deallocator will run too early } diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.h b/src/Cafe/OS/libs/coreinit/coreinit_Thread.h index b401d96d..fdbcfea7 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.h @@ -2,9 +2,6 @@ #include "Cafe/HW/Espresso/Const.h" #include "Cafe/OS/libs/coreinit/coreinit_Scheduler.h" -#define OS_CONTEXT_MAGIC_0 'OSCo' -#define OS_CONTEXT_MAGIC_1 'ntxt' - struct OSThread_t; struct OSContextRegFPSCR_t @@ -16,6 +13,9 @@ struct OSContextRegFPSCR_t struct OSContext_t { + static constexpr uint32 OS_CONTEXT_MAGIC_0 = 0x4f53436f; // "OSCo" + static constexpr uint32 OS_CONTEXT_MAGIC_1 = 0x6e747874; // "ntxt" + /* +0x000 */ betype<uint32> magic0; /* +0x004 */ betype<uint32> magic1; /* +0x008 */ uint32 gpr[32]; @@ -36,24 +36,29 @@ struct OSContext_t /* +0x1BC */ uint32 gqr[8]; // GQR/UGQR /* +0x1DC */ uint32be upir; // set to current core index /* +0x1E0 */ uint64be fp_ps1[32]; - /* +0x2E0 */ uint64 uknTime2E0; - /* +0x2E8 */ uint64 uknTime2E8; - /* +0x2F0 */ uint64 uknTime2F0; - /* +0x2F8 */ uint64 uknTime2F8; - /* +0x300 */ uint32 error; // returned by __gh_errno_ptr() (used by socketlasterr) + /* +0x2E0 */ uint64be coretime[3]; + /* +0x2F8 */ uint64be starttime; + /* +0x300 */ uint32be ghs_errno; // returned by __gh_errno_ptr() (used by socketlasterr) /* +0x304 */ uint32be affinity; - /* +0x308 */ uint32 ukn0308; - /* +0x30C */ uint32 ukn030C; - /* +0x310 */ uint32 ukn0310; - /* +0x314 */ uint32 ukn0314; - /* +0x318 */ uint32 ukn0318; - /* +0x31C */ uint32 ukn031C; + /* +0x308 */ uint32be upmc1; + /* +0x30C */ uint32be upmc2; + /* +0x310 */ uint32be upmc3; + /* +0x314 */ uint32be upmc4; + /* +0x318 */ uint32be ummcr0; + /* +0x31C */ uint32be ummcr1; bool checkMagic() { return magic0 == (uint32)OS_CONTEXT_MAGIC_0 && magic1 == (uint32)OS_CONTEXT_MAGIC_1; } + void SetContextMagic() + { + magic0 = OS_CONTEXT_MAGIC_0; + magic1 = OS_CONTEXT_MAGIC_1; + } + + bool hasCoreAffinitySet(uint32 coreIndex) const { return (((uint32)affinity >> coreIndex) & 1) != 0; @@ -361,6 +366,8 @@ namespace coreinit struct OSThread_t { + static constexpr uint32 MAGIC_THREAD = 0x74487244; // "tHrD" + enum class THREAD_TYPE : uint32 { TYPE_DRIVER = 0, @@ -383,7 +390,7 @@ struct OSThread_t ATTR_AFFINITY_CORE1 = 0x2, ATTR_AFFINITY_CORE2 = 0x4, ATTR_DETACHED = 0x8, - // more flags? + ATTR_UKN_010 = 0x10, }; enum REQUEST_FLAG_BIT : uint32 @@ -404,23 +411,21 @@ struct OSThread_t return 0; } - void SetMagic() + void SetThreadMagic() { - context.magic0 = OS_CONTEXT_MAGIC_0; - context.magic1 = OS_CONTEXT_MAGIC_1; - magic = 'tHrD'; + magic = MAGIC_THREAD; } bool IsValidMagic() const { - return magic == 'tHrD' && context.magic0 == OS_CONTEXT_MAGIC_0 && context.magic1 == OS_CONTEXT_MAGIC_1; + return magic == MAGIC_THREAD && context.magic0 == OSContext_t::OS_CONTEXT_MAGIC_0 && context.magic1 == OSContext_t::OS_CONTEXT_MAGIC_1; } /* +0x000 */ OSContext_t context; - /* +0x320 */ uint32be magic; // 'tHrD' + /* +0x320 */ uint32be magic; // "tHrD" (0x74487244) /* +0x324 */ betype<THREAD_STATE> state; /* +0x325 */ uint8 attr; - /* +0x326 */ uint16be id; // Warriors Orochi 3 uses this to identify threads. Seems like this is always set to 0x8000 ? + /* +0x326 */ uint16be id; // Warriors Orochi 3 uses this to identify threads /* +0x328 */ betype<sint32> suspendCounter; /* +0x32C */ sint32be effectivePriority; // effective priority (lower is higher) /* +0x330 */ sint32be basePriority; // base priority (lower is higher) @@ -440,21 +445,21 @@ struct OSThread_t /* +0x38C */ coreinit::OSThreadLink activeThreadChain; // queue of active threads (g_activeThreadQueue) - /* +0x394 */ MPTR stackBase; // upper limit of stack - /* +0x398 */ MPTR stackEnd; // lower limit of stack + /* +0x394 */ MEMPTR<void> stackBase; // upper limit of stack + /* +0x398 */ MEMPTR<void> stackEnd; // lower limit of stack - /* +0x39C */ MPTR entrypoint; + /* +0x39C */ MEMPTR<void> entrypoint; /* +0x3A0 */ crt_t crt; /* +0x578 */ sint32 alarmRelatedUkn; /* +0x57C */ std::array<MEMPTR<void>, 16> specificArray; /* +0x5BC */ betype<THREAD_TYPE> type; /* +0x5C0 */ MEMPTR<const char> threadName; - /* +0x5C4 */ MPTR waitAlarm; // used only by OSWaitEventWithTimeout/OSSignalEvent ? + /* +0x5C4 */ MEMPTR<void> waitAlarm; // used only by OSWaitEventWithTimeout/OSSignalEvent ? /* +0x5C8 */ uint32 userStackPointer; - /* +0x5CC */ MEMPTR<void> cleanupCallback2; + /* +0x5CC */ MEMPTR<void> cleanupCallback; /* +0x5D0 */ MEMPTR<void> deallocatorFunc; /* +0x5D4 */ uint32 stateFlags; // 0x5D4 | various flags? Controls if canceling/suspension is allowed (at cancel points) or not? If 1 -> Cancel/Suspension not allowed, if 0 -> Cancel/Suspension allowed @@ -480,19 +485,21 @@ struct OSThread_t /* +0x660 */ uint32 ukn660; + // todo - some of the members towards the end of the struct were only added in later COS versions. Figure out the mapping between version and members + // TLS /* +0x664 */ uint16 numAllocatedTLSBlocks; /* +0x666 */ sint16 tlsStatus; /* +0x668 */ MPTR tlsBlocksMPTR; - + /* +0x66C */ MEMPTR<coreinit::OSFastMutex> waitingForFastMutex; /* +0x670 */ coreinit::OSFastMutexLink contendedFastMutex; /* +0x678 */ coreinit::OSFastMutexLink ownedFastMutex; + /* +0x680 */ MEMPTR<void> alignmentExceptionCallback[Espresso::CORE_COUNT]; - /* +0x680 */ uint32 padding680[28 / 4]; + /* +0x68C */ uint32 padding68C[20 / 4]; }; - -static_assert(sizeof(OSThread_t) == 0x6A0-4); // todo - determine correct size +static_assert(sizeof(OSThread_t) == 0x6A0); namespace coreinit { diff --git a/src/Cafe/OS/libs/nsysnet/nsysnet.cpp b/src/Cafe/OS/libs/nsysnet/nsysnet.cpp index 88bca8af..dd7c9189 100644 --- a/src/Cafe/OS/libs/nsysnet/nsysnet.cpp +++ b/src/Cafe/OS/libs/nsysnet/nsysnet.cpp @@ -117,10 +117,10 @@ void nsysnetExport_socket_lib_finish(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, 0); // 0 -> Success } -uint32* __gh_errno_ptr() +static uint32be* __gh_errno_ptr() { OSThread_t* osThread = coreinit::OSGetCurrentThread(); - return &osThread->context.error; + return &osThread->context.ghs_errno; } void _setSockError(sint32 errCode) diff --git a/src/Cafe/OS/libs/snd_core/ax_ist.cpp b/src/Cafe/OS/libs/snd_core/ax_ist.cpp index 30cbdbb1..17f247e0 100644 --- a/src/Cafe/OS/libs/snd_core/ax_ist.cpp +++ b/src/Cafe/OS/libs/snd_core/ax_ist.cpp @@ -963,7 +963,7 @@ namespace snd_core OSInitMessageQueue(__AXIstThreadMsgQueue.GetPtr(), __AXIstThreadMsgArray.GetPtr(), 0x10); // create thread uint8 istThreadAttr = 0; - coreinit::OSCreateThreadType(__AXIstThread.GetPtr(), PPCInterpreter_makeCallableExportDepr(AXIst_ThreadEntry), 0, &__AXIstThreadMsgQueue, __AXIstThreadStack.GetPtr() + 0x4000, 0x4000, 14, istThreadAttr, OSThread_t::THREAD_TYPE::TYPE_DRIVER); + coreinit::__OSCreateThreadType(__AXIstThread.GetPtr(), PPCInterpreter_makeCallableExportDepr(AXIst_ThreadEntry), 0, &__AXIstThreadMsgQueue, __AXIstThreadStack.GetPtr() + 0x4000, 0x4000, 14, istThreadAttr, OSThread_t::THREAD_TYPE::TYPE_DRIVER); coreinit::OSResumeThread(__AXIstThread.GetPtr()); } diff --git a/src/Common/ExceptionHandler/ExceptionHandler.cpp b/src/Common/ExceptionHandler/ExceptionHandler.cpp index 5fefc8ca..b6755fd8 100644 --- a/src/Common/ExceptionHandler/ExceptionHandler.cpp +++ b/src/Common/ExceptionHandler/ExceptionHandler.cpp @@ -155,7 +155,7 @@ void ExceptionHandler_LogGeneralInfo() const char* threadName = "NULL"; if (!threadItrBE->threadName.IsNull()) threadName = threadItrBE->threadName.GetPtr(); - sprintf(dumpLine, "%08x Ent %08x IP %08x LR %08x %-9s Aff %d%d%d Pri %2d Name %s", threadItrMPTR, _swapEndianU32(threadItrBE->entrypoint), threadItrBE->context.srr0, _swapEndianU32(threadItrBE->context.lr), threadStateStr, (affinity >> 0) & 1, (affinity >> 1) & 1, (affinity >> 2) & 1, effectivePriority, threadName); + sprintf(dumpLine, "%08x Ent %08x IP %08x LR %08x %-9s Aff %d%d%d Pri %2d Name %s", threadItrMPTR, threadItrBE->entrypoint.GetMPTR(), threadItrBE->context.srr0, _swapEndianU32(threadItrBE->context.lr), threadStateStr, (affinity >> 0) & 1, (affinity >> 1) & 1, (affinity >> 2) & 1, effectivePriority, threadName); // write line to log CrashLog_WriteLine(dumpLine); } diff --git a/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp b/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp index bd71942f..dfbaf76e 100644 --- a/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp +++ b/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp @@ -195,10 +195,10 @@ void DebugPPCThreadsWindow::RefreshThreadList() m_thread_list->InsertItem(item); m_thread_list->SetItemData(item, (long)threadItrMPTR); // entry point - sprintf(tempStr, "%08X", _swapEndianU32(cafeThread->entrypoint)); + sprintf(tempStr, "%08X", cafeThread->entrypoint.GetMPTR()); m_thread_list->SetItem(i, 1, tempStr); // stack base (low) - sprintf(tempStr, "%08X - %08X", _swapEndianU32(cafeThread->stackEnd), _swapEndianU32(cafeThread->stackBase)); + sprintf(tempStr, "%08X - %08X", cafeThread->stackEnd.GetMPTR(), cafeThread->stackBase.GetMPTR()); m_thread_list->SetItem(i, 2, tempStr); // pc RPLStoredSymbol* symbol = rplSymbolStorage_getByAddress(cafeThread->context.srr0); From 91a010fbdd023b3cac85f455fb3c32de3d2c3784 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 4 May 2024 08:05:10 +0200 Subject: [PATCH 095/299] proc_ui: Fix crash due to incorrect version handling Resolves a crash in NEX Remix --- src/Cafe/OS/libs/proc_ui/proc_ui.cpp | 3 +++ src/Cafe/TitleList/TitleInfo.cpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Cafe/OS/libs/proc_ui/proc_ui.cpp b/src/Cafe/OS/libs/proc_ui/proc_ui.cpp index 5560568d..dd9a460f 100644 --- a/src/Cafe/OS/libs/proc_ui/proc_ui.cpp +++ b/src/Cafe/OS/libs/proc_ui/proc_ui.cpp @@ -391,6 +391,9 @@ namespace proc_ui { cemuLog_log(LogType::Force, "ProcUI: Trying to register callback before init"); cemu_assert_suspicious(); + // this shouldn't happen but lets set the memory pointers anyway to prevent a crash in case the user has incorrect meta info + s_memAllocPtr = gCoreinitData->MEMAllocFromDefaultHeap.GetMPTR(); + s_memFreePtr = gCoreinitData->MEMFreeToDefaultHeap.GetMPTR(); } ProcUIInternalCallbackEntry* entry = (ProcUIInternalCallbackEntry*)_AllocMem(sizeof(ProcUIInternalCallbackEntry)); entry->funcPtr = funcPtr; diff --git a/src/Cafe/TitleList/TitleInfo.cpp b/src/Cafe/TitleList/TitleInfo.cpp index 6d21929e..2f295811 100644 --- a/src/Cafe/TitleList/TitleInfo.cpp +++ b/src/Cafe/TitleList/TitleInfo.cpp @@ -563,7 +563,7 @@ bool TitleInfo::ParseAppXml(std::vector<uint8>& appXmlData) else if (name == "group_id") m_parsedAppXml->group_id = (uint32)std::stoull(child.text().as_string(), nullptr, 16); else if (name == "sdk_version") - m_parsedAppXml->sdk_version = (uint32)std::stoull(child.text().as_string(), nullptr, 16); + m_parsedAppXml->sdk_version = (uint32)std::stoull(child.text().as_string(), nullptr, 10); } return true; } From 48d2a8371b3b35b2a4439e1475c694856728f4ec Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 5 May 2024 01:27:39 +0200 Subject: [PATCH 096/299] sndcore: Write log message instead of asserting in AXSetDeviceRemixMatrix Fixes a crash in Watch Dogs due to the non-debug assert --- src/Cafe/OS/libs/snd_core/ax_ist.cpp | 33 ++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/Cafe/OS/libs/snd_core/ax_ist.cpp b/src/Cafe/OS/libs/snd_core/ax_ist.cpp index 17f247e0..2ea27cbb 100644 --- a/src/Cafe/OS/libs/snd_core/ax_ist.cpp +++ b/src/Cafe/OS/libs/snd_core/ax_ist.cpp @@ -218,21 +218,36 @@ namespace snd_core // validate parameters if (deviceId == AX_DEV_TV) { - cemu_assert(inputChannelCount <= AX_TV_CHANNEL_COUNT); - cemu_assert(outputChannelCount == 1 || outputChannelCount == 2 || outputChannelCount == 6); + if(inputChannelCount > AX_TV_CHANNEL_COUNT) + { + cemuLog_log(LogType::APIErrors, "AXSetDeviceRemixMatrix: Input channel count must be smaller or equal to 6 for TV device"); + return -7; + } + if(outputChannelCount != 1 && outputChannelCount != 2 && outputChannelCount != 6) + { + // seems like Watch Dogs uses 4 as outputChannelCount for some reason? + cemuLog_log(LogType::APIErrors, "AXSetDeviceRemixMatrix: Output channel count must be 1, 2 or 6 for TV device"); + return -8; + } } else if (deviceId == AX_DEV_DRC) { - cemu_assert(inputChannelCount <= AX_DRC_CHANNEL_COUNT); - cemu_assert(outputChannelCount == 1 || outputChannelCount == 2 || outputChannelCount == 4); - } - else if (deviceId == AX_DEV_RMT) - { - cemu_assert(false); + if(inputChannelCount > AX_DRC_CHANNEL_COUNT) + { + cemuLog_log(LogType::APIErrors, "AXSetDeviceRemixMatrix: Input channel count must be smaller or equal to 4 for DRC device"); + return -7; + } + if(outputChannelCount != 1 && outputChannelCount != 2 && outputChannelCount != 4) + { + cemuLog_log(LogType::APIErrors, "AXSetDeviceRemixMatrix: Output channel count must be 1, 2 or 4 for DRC device"); + return -8; + } } else + { + cemuLog_log(LogType::APIErrors, "AXSetDeviceRemixMatrix: Only TV (0) and DRC (1) device are supported"); return -1; - + } auto matrices = g_remix_matrices.GetPtr(); // test if we already have an entry and just need to update the matrix data From a744670486cf27e14dd884d3a1b2ee04dc05a8cb Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 5 May 2024 01:28:08 +0200 Subject: [PATCH 097/299] coreinit: Add export for OSGetForegroundBucketFreeArea --- src/Cafe/OS/libs/coreinit/coreinit_FG.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Cafe/OS/libs/coreinit/coreinit_FG.cpp b/src/Cafe/OS/libs/coreinit/coreinit_FG.cpp index b751a8fd..e22c3eb3 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_FG.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_FG.cpp @@ -185,6 +185,7 @@ namespace coreinit { osLib_addFunction("coreinit", "OSGetForegroundBucket", coreinitExport_OSGetForegroundBucket); cafeExportRegister("coreinit", OSGetForegroundBucket, LogType::CoreinitMem); + cafeExportRegister("coreinit", OSGetForegroundBucketFreeArea, LogType::CoreinitMem); osLib_addFunction("coreinit", "OSCopyFromClipboard", coreinitExport_OSCopyFromClipboard); } } From f28043e0e969f5ff5e8ad1e5eea8964ebf6f2523 Mon Sep 17 00:00:00 2001 From: qurious-pixel <62252937+qurious-pixel@users.noreply.github.com> Date: Sat, 4 May 2024 16:34:36 -0700 Subject: [PATCH 098/299] Linux/Mac Auto-Updater (#1145) --- .github/workflows/build.yml | 20 ++++++------ src/CMakeLists.txt | 1 + src/gui/CemuUpdateWindow.cpp | 56 +++++++++++++++++++++++++++----- src/gui/GeneralSettings2.cpp | 10 +++--- src/gui/GettingStartedDialog.cpp | 7 ++-- src/gui/MainWindow.cpp | 8 +++-- src/resource/update.sh | 8 +++++ 7 files changed, 82 insertions(+), 28 deletions(-) create mode 100755 src/resource/update.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 58a8508d..d188b4a1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: "Checkout repo" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: "recursive" fetch-depth: 0 @@ -91,7 +91,7 @@ jobs: run: mv bin/Cemu_release bin/Cemu - name: Upload artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: ${{ inputs.deploymode == 'release' }} with: name: cemu-bin-linux-x64 @@ -102,9 +102,9 @@ jobs: needs: build-ubuntu steps: - name: Checkout Upstream Repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: cemu-bin-linux-x64 path: bin @@ -121,7 +121,7 @@ jobs: dist/linux/appimage.sh - name: Upload artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: cemu-appimage-x64 path: artifacts @@ -130,7 +130,7 @@ jobs: runs-on: windows-2022 steps: - name: "Checkout repo" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: "recursive" @@ -200,7 +200,7 @@ jobs: run: Rename-Item bin/Cemu_release.exe Cemu.exe - name: Upload artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: ${{ inputs.deploymode == 'release' }} with: name: cemu-bin-windows-x64 @@ -210,7 +210,7 @@ jobs: runs-on: macos-12 steps: - name: "Checkout repo" - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: "recursive" @@ -289,14 +289,14 @@ jobs: 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 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 + chmod a+x bin/Cemu_app/Cemu.app/Contents/MacOS/{Cemu,update.sh} ln -s /Applications bin/Cemu_app/Applications hdiutil create ./bin/tmp.dmg -ov -volname "Cemu" -fs HFS+ -srcfolder "./bin/Cemu_app" hdiutil convert ./bin/tmp.dmg -format UDZO -o bin/Cemu.dmg rm bin/tmp.dmg - name: Upload artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: ${{ inputs.deploymode == 'release' }} with: name: cemu-bin-macos-x64 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7442e37c..1b78b1fb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -98,6 +98,7 @@ if (MACOS_BUNDLE) add_custom_command (TARGET CemuBin POST_BUILD COMMAND ${CMAKE_COMMAND} ARGS -E copy "/usr/local/lib/libMoltenVK.dylib" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libMoltenVK.dylib" COMMAND ${CMAKE_COMMAND} ARGS -E copy "${CMAKE_BINARY_DIR}/vcpkg_installed/x64-osx/lib/libusb-1.0.0.dylib" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libusb-1.0.0.dylib" + COMMAND ${CMAKE_COMMAND} ARGS -E copy "${CMAKE_SOURCE_DIR}/src/resource/update.sh" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/update.sh" COMMAND bash -c "install_name_tool -add_rpath @executable_path/../Frameworks ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}" COMMAND bash -c "install_name_tool -change /usr/local/opt/libusb/lib/libusb-1.0.0.dylib @executable_path/../Frameworks/libusb-1.0.0.dylib ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}") endif() diff --git a/src/gui/CemuUpdateWindow.cpp b/src/gui/CemuUpdateWindow.cpp index 91394ee2..445c7c17 100644 --- a/src/gui/CemuUpdateWindow.cpp +++ b/src/gui/CemuUpdateWindow.cpp @@ -12,6 +12,11 @@ #include <wx/msgdlg.h> #include <wx/stdpaths.h> +#ifndef BOOST_OS_WINDOWS +#include <unistd.h> +#include <sys/stat.h> +#endif + #include <curl/curl.h> #include <zip.h> #include <boost/tokenizer.hpp> @@ -105,11 +110,11 @@ bool CemuUpdateWindow::QueryUpdateInfo(std::string& downloadUrlOut, std::string& auto* curl = curl_easy_init(); urlStr.append(_curlUrlEscape(curl, BUILD_VERSION_STRING)); #if BOOST_OS_LINUX - urlStr.append("&platform=linux"); + urlStr.append("&platform=linux_appimage_x86"); #elif BOOST_OS_WINDOWS urlStr.append("&platform=windows"); #elif BOOST_OS_MACOS - urlStr.append("&platform=macos_x86"); + urlStr.append("&platform=macos_bundle_x86"); #elif #error Name for current platform is missing @@ -407,7 +412,13 @@ void CemuUpdateWindow::WorkerThread() if (!exists(tmppath)) create_directory(tmppath); +#if BOOST_OS_WINDOWS const auto update_file = tmppath / L"update.zip"; +#elif BOOST_OS_LINUX + const auto update_file = tmppath / L"Cemu.AppImage"; +#elif BOOST_OS_MACOS + const auto update_file = tmppath / L"cemu.dmg"; +#endif if (DownloadCemuZip(url, update_file)) { auto* event = new wxCommandEvent(wxEVT_RESULT); @@ -427,6 +438,7 @@ void CemuUpdateWindow::WorkerThread() // extract std::string cemuFolderName; +#if BOOST_OS_WINDOWS if (!ExtractUpdate(update_file, tmppath, cemuFolderName)) { cemuLog_log(LogType::Force, "Extracting Cemu zip failed"); @@ -437,7 +449,7 @@ void CemuUpdateWindow::WorkerThread() cemuLog_log(LogType::Force, "Cemu folder not found in zip"); break; } - +#endif const auto expected_path = tmppath / cemuFolderName; if (exists(expected_path)) { @@ -472,6 +484,7 @@ void CemuUpdateWindow::WorkerThread() // apply update fs::path exePath = ActiveSettings::GetExecutablePath(); +#if BOOST_OS_WINDOWS std::wstring target_directory = exePath.parent_path().generic_wstring(); if (target_directory[target_directory.size() - 1] == '/') target_directory = target_directory.substr(0, target_directory.size() - 1); // remove trailing / @@ -480,8 +493,19 @@ void CemuUpdateWindow::WorkerThread() const auto exec = ActiveSettings::GetExecutablePath(); const auto target_exe = fs::path(exec).replace_extension("exe.backup"); fs::rename(exec, target_exe); - m_restartFile = exec; - + m_restartFile = exec; +#elif BOOST_OS_LINUX + const char* appimage_path = std::getenv("APPIMAGE"); + const auto target_exe = fs::path(appimage_path).replace_extension("AppImage.backup"); + const char* filePath = update_file.c_str(); + mode_t permissions = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; + fs::rename(appimage_path, target_exe); + m_restartFile = appimage_path; + chmod(filePath, permissions); + wxString wxAppPath = wxString::FromUTF8(appimage_path); + wxCopyFile (wxT("/tmp/cemu_update/Cemu.AppImage"), wxAppPath); +#endif +#if BOOST_OS_WINDOWS const auto index = expected_path.wstring().size(); int counter = 0; for (const auto& it : fs::recursive_directory_iterator(expected_path)) @@ -516,7 +540,7 @@ void CemuUpdateWindow::WorkerThread() wxQueueEvent(this, event); } } - +#endif auto* event = new wxCommandEvent(wxEVT_PROGRESS); event->SetInt(m_gaugeMaxValue); wxQueueEvent(this, event); @@ -565,8 +589,24 @@ void CemuUpdateWindow::OnClose(wxCloseEvent& event) exit(0); } -#else - cemuLog_log(LogType::Force, "unimplemented - restart on update"); +#elif BOOST_OS_LINUX + if (m_restartRequired && !m_restartFile.empty() && fs::exists(m_restartFile)) + { + const char* appimage_path = std::getenv("APPIMAGE"); + execlp(appimage_path, appimage_path, (char *)NULL); + + exit(0); + } +#elif BOOST_OS_MACOS + if (m_restartRequired) + { + const auto tmppath = fs::temp_directory_path() / L"cemu_update/Cemu.dmg"; + fs::path exePath = ActiveSettings::GetExecutablePath().parent_path(); + const auto apppath = exePath / L"update.sh"; + execlp("sh", "sh", apppath.c_str(), NULL); + + exit(0); + } #endif } diff --git a/src/gui/GeneralSettings2.cpp b/src/gui/GeneralSettings2.cpp index 27ce37fa..dab30981 100644 --- a/src/gui/GeneralSettings2.cpp +++ b/src/gui/GeneralSettings2.cpp @@ -166,9 +166,11 @@ wxPanel* GeneralSettings2::AddGeneralPage(wxNotebook* notebook) m_auto_update = new wxCheckBox(box, wxID_ANY, _("Automatically check for updates")); m_auto_update->SetToolTip(_("Automatically checks for new cemu versions on startup")); second_row->Add(m_auto_update, 0, botflag, 5); -#if BOOST_OS_LINUX || BOOST_OS_MACOS - m_auto_update->Disable(); -#endif +#if BOOST_OS_LINUX + if (!std::getenv("APPIMAGE")) { + m_auto_update->Disable(); + } +#endif second_row->AddSpacer(10); m_save_screenshot = new wxCheckBox(box, wxID_ANY, _("Save screenshot")); m_save_screenshot->SetToolTip(_("Pressing the screenshot key (F12) will save a screenshot directly to the screenshots folder")); @@ -2055,4 +2057,4 @@ wxString GeneralSettings2::GetOnlineAccountErrorMessage(OnlineAccountError error default: return "no error"; } -} \ No newline at end of file +} diff --git a/src/gui/GettingStartedDialog.cpp b/src/gui/GettingStartedDialog.cpp index 91cc3a11..bfd206b1 100644 --- a/src/gui/GettingStartedDialog.cpp +++ b/src/gui/GettingStartedDialog.cpp @@ -146,10 +146,11 @@ wxPanel* GettingStartedDialog::CreatePage2() m_update = new wxCheckBox(sizer->GetStaticBox(), wxID_ANY, _("Automatically check for updates")); option_sizer->Add(m_update, 0, wxALL, 5); -#if BOOST_OS_LINUX || BOOST_OS_MACOS - m_update->Disable(); +#if BOOST_OS_LINUX + if (!std::getenv("APPIMAGE")) { + m_update->Disable(); + } #endif - sizer->Add(option_sizer, 1, wxEXPAND, 5); page2_sizer->Add(sizer, 0, wxALL | wxEXPAND, 5); } diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index da57870c..e8103f9a 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -2292,9 +2292,11 @@ void MainWindow::RecreateMenu() // help menu wxMenu* helpMenu = new wxMenu(); m_check_update_menu = helpMenu->Append(MAINFRAME_MENU_ID_HELP_UPDATE, _("&Check for updates")); -#if BOOST_OS_LINUX || BOOST_OS_MACOS - m_check_update_menu->Enable(false); -#endif +#if BOOST_OS_LINUX + if (!std::getenv("APPIMAGE")) { + m_check_update_menu->Enable(false); + } +#endif helpMenu->Append(MAINFRAME_MENU_ID_HELP_GETTING_STARTED, _("&Getting started")); helpMenu->AppendSeparator(); helpMenu->Append(MAINFRAME_MENU_ID_HELP_ABOUT, _("&About Cemu")); diff --git a/src/resource/update.sh b/src/resource/update.sh new file mode 100755 index 00000000..5ff22160 --- /dev/null +++ b/src/resource/update.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +APP=$(cd "$(dirname "0")"/;pwd) +hdiutil attach $TMPDIR/cemu_update/cemu.dmg +cp -rf /Volumes/Cemu/Cemu.app "$APP" +hdiutil detach /Volumes/Cemu/ + +open -n -a "$APP/Cemu.app" From dc480ac00bc6367f9272c490fbf2a7e4cacee218 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Sun, 5 May 2024 02:35:01 +0200 Subject: [PATCH 099/299] Add support for WUHB file format (#1190) --- src/Cafe/CMakeLists.txt | 4 + src/Cafe/Filesystem/WUHB/RomFSStructs.h | 40 ++++ src/Cafe/Filesystem/WUHB/WUHBReader.cpp | 224 ++++++++++++++++++++++ src/Cafe/Filesystem/WUHB/WUHBReader.h | 45 +++++ src/Cafe/Filesystem/fsc.h | 3 + src/Cafe/Filesystem/fscDeviceWuhb.cpp | 151 +++++++++++++++ src/Cafe/TitleList/TitleInfo.cpp | 82 ++++++++ src/Cafe/TitleList/TitleInfo.h | 3 + src/Cafe/TitleList/TitleList.cpp | 3 +- src/gui/MainWindow.cpp | 6 +- src/gui/components/wxGameList.cpp | 10 + src/gui/components/wxTitleManagerList.cpp | 5 + src/gui/components/wxTitleManagerList.h | 1 + src/util/helpers/helpers.cpp | 41 ++++ src/util/helpers/helpers.h | 2 + 15 files changed, 617 insertions(+), 3 deletions(-) create mode 100644 src/Cafe/Filesystem/WUHB/RomFSStructs.h create mode 100644 src/Cafe/Filesystem/WUHB/WUHBReader.cpp create mode 100644 src/Cafe/Filesystem/WUHB/WUHBReader.h create mode 100644 src/Cafe/Filesystem/fscDeviceWuhb.cpp diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index d64a5998..851854fc 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -10,6 +10,7 @@ add_library(CemuCafe Filesystem/fscDeviceRedirect.cpp Filesystem/fscDeviceWua.cpp Filesystem/fscDeviceWud.cpp + Filesystem/fscDeviceWuhb.cpp Filesystem/fsc.h Filesystem/FST/FST.cpp Filesystem/FST/FST.h @@ -18,6 +19,9 @@ add_library(CemuCafe Filesystem/FST/KeyCache.h Filesystem/WUD/wud.cpp Filesystem/WUD/wud.h + Filesystem/WUHB/RomFSStructs.h + Filesystem/WUHB/WUHBReader.cpp + Filesystem/WUHB/WUHBReader.h GamePatch.cpp GamePatch.h GameProfile/GameProfile.cpp diff --git a/src/Cafe/Filesystem/WUHB/RomFSStructs.h b/src/Cafe/Filesystem/WUHB/RomFSStructs.h new file mode 100644 index 00000000..59ef503f --- /dev/null +++ b/src/Cafe/Filesystem/WUHB/RomFSStructs.h @@ -0,0 +1,40 @@ +#pragma once + +struct romfs_header_t +{ + uint32 header_magic; + uint32be header_size; + uint64be dir_hash_table_ofs; + uint64be dir_hash_table_size; + uint64be dir_table_ofs; + uint64be dir_table_size; + uint64be file_hash_table_ofs; + uint64be file_hash_table_size; + uint64be file_table_ofs; + uint64be file_table_size; + uint64be file_partition_ofs; +}; + +struct romfs_direntry_t +{ + uint32be parent; + uint32be listNext; // offset to next directory entry in linked list of parent directory (aka "sibling") + uint32be dirListHead; // offset to first entry in linked list of directory entries (aka "child") + uint32be fileListHead; // offset to first entry in linked list of file entries (aka "file") + uint32be hash; + uint32be name_size; + std::string name; +}; + +struct romfs_fentry_t +{ + uint32be parent; + uint32be listNext; // offset to next file entry in linked list of parent directory (aka "sibling") + uint64be offset; + uint64be size; + uint32be hash; + uint32be name_size; + std::string name; +}; + +#define ROMFS_ENTRY_EMPTY 0xFFFFFFFF diff --git a/src/Cafe/Filesystem/WUHB/WUHBReader.cpp b/src/Cafe/Filesystem/WUHB/WUHBReader.cpp new file mode 100644 index 00000000..e7a4c9be --- /dev/null +++ b/src/Cafe/Filesystem/WUHB/WUHBReader.cpp @@ -0,0 +1,224 @@ +#include "WUHBReader.h" +WUHBReader* WUHBReader::FromPath(const fs::path& path) +{ + FileStream* fileIn{FileStream::openFile2(path)}; + if (!fileIn) + return nullptr; + + WUHBReader* ret = new WUHBReader(fileIn); + if (!ret->CheckMagicValue()) + { + delete ret; + return nullptr; + } + + if (!ret->ReadHeader()) + { + delete ret; + return nullptr; + } + + return ret; +} + +static const romfs_direntry_t fallbackDirEntry{ + .parent = ROMFS_ENTRY_EMPTY, + .listNext = ROMFS_ENTRY_EMPTY, + .dirListHead = ROMFS_ENTRY_EMPTY, + .fileListHead = ROMFS_ENTRY_EMPTY, + .hash = ROMFS_ENTRY_EMPTY, + .name_size = 0, + .name = "" +}; +static const romfs_fentry_t fallbackFileEntry{ + .parent = ROMFS_ENTRY_EMPTY, + .listNext = ROMFS_ENTRY_EMPTY, + .offset = 0, + .size = 0, + .hash = ROMFS_ENTRY_EMPTY, + .name_size = 0, + .name = "" +}; +template<bool File> +const WUHBReader::EntryType<File>& WUHBReader::GetFallback() +{ + if constexpr (File) + return fallbackFileEntry; + else + return fallbackDirEntry; +} + +template<bool File> +WUHBReader::EntryType<File> WUHBReader::GetEntry(uint32 offset) const +{ + auto fallback = GetFallback<File>(); + if(offset == ROMFS_ENTRY_EMPTY) + return fallback; + + const char* typeName = File ? "fentry" : "direntry"; + EntryType<File> ret; + if (offset >= (File ? m_header.file_table_size : m_header.dir_table_size)) + { + cemuLog_log(LogType::Force, "WUHB {} offset exceeds table size declared in header", typeName); + return fallback; + } + + // read the entry + m_fileIn->SetPosition((File ? m_header.file_table_ofs : m_header.dir_table_ofs) + offset); + auto read = m_fileIn->readData(&ret, offsetof(EntryType<File>, name)); + if (read != offsetof(EntryType<File>, name)) + { + cemuLog_log(LogType::Force, "failed to read WUHB {} at offset: {}", typeName, offset); + return fallback; + } + + // read the name + ret.name.resize(ret.name_size); + read = m_fileIn->readData(ret.name.data(), ret.name_size); + if (read != ret.name_size) + { + cemuLog_log(LogType::Force, "failed to read WUHB {} name", typeName); + return fallback; + } + + return ret; +} + +romfs_direntry_t WUHBReader::GetDirEntry(uint32 offset) const +{ + return GetEntry<false>(offset); +} +romfs_fentry_t WUHBReader::GetFileEntry(uint32 offset) const +{ + return GetEntry<true>(offset); +} + +uint64 WUHBReader::GetFileSize(uint32 entryOffset) const +{ + return GetFileEntry(entryOffset).size; +} + +uint64 WUHBReader::ReadFromFile(uint32 entryOffset, uint64 fileOffset, uint64 length, void* buffer) const +{ + const auto fileEntry = GetFileEntry(entryOffset); + if (fileOffset >= fileEntry.size) + return 0; + const uint64 readAmount = std::min(length, fileEntry.size - fileOffset); + const uint64 wuhbOffset = m_header.file_partition_ofs + fileEntry.offset + fileOffset; + m_fileIn->SetPosition(wuhbOffset); + return m_fileIn->readData(buffer, readAmount); +} + +uint32 WUHBReader::GetHashTableEntryOffset(uint32 hash, bool isFile) const +{ + const uint64 hash_table_size = (isFile ? m_header.file_hash_table_size : m_header.dir_hash_table_size); + const uint64 hash_table_ofs = (isFile ? m_header.file_hash_table_ofs : m_header.dir_hash_table_ofs); + + const uint64 hash_table_entry_count = hash_table_size / sizeof(uint32); + const uint64 hash_table_entry_offset = hash_table_ofs + (hash % hash_table_entry_count) * sizeof(uint32); + + m_fileIn->SetPosition(hash_table_entry_offset); + uint32 tableOffset; + if (!m_fileIn->readU32(tableOffset)) + { + cemuLog_log(LogType::Force, "failed to read WUHB hash table entry at file offset: {}", hash_table_entry_offset); + return ROMFS_ENTRY_EMPTY; + } + + return uint32be::from_bevalue(tableOffset); +} + +template<bool T> +bool WUHBReader::SearchHashList(uint32& entryOffset, const fs::path& targetName) const +{ + for (;;) + { + if (entryOffset == ROMFS_ENTRY_EMPTY) + return false; + auto entry = GetEntry<T>(entryOffset); + + if (entry.name == targetName) + return true; + entryOffset = entry.hash; + } + return false; +} + +uint32 WUHBReader::Lookup(const std::filesystem::path& path, bool isFile) const +{ + uint32 currentEntryOffset = 0; + auto look = [&](const fs::path& part, bool lookInFileHT) { + const auto partString = part.string(); + currentEntryOffset = GetHashTableEntryOffset(CalcPathHash(currentEntryOffset, partString.c_str(), 0, partString.size()), lookInFileHT); + if (lookInFileHT) + return SearchHashList<true>(currentEntryOffset, part); + else + return SearchHashList<false>(currentEntryOffset, part); + }; + // look for the root entry + if (!look("", false)) + return ROMFS_ENTRY_EMPTY; + + auto it = path.begin(); + while (it != path.end()) + { + fs::path part = *it; + ++it; + // no need to recurse after trailing forward slash (e.g. directory/) + if (part.empty() && !isFile) + break; + // skip leading forward slash + if (part == "/") + continue; + + // if the lookup target is a file and this is the last iteration, look in the file hash table instead. + if (!look(part, it == path.end() && isFile)) + return ROMFS_ENTRY_EMPTY; + } + return currentEntryOffset; +} +bool WUHBReader::CheckMagicValue() const +{ + uint8 magic[4]; + m_fileIn->SetPosition(0); + int read = m_fileIn->readData(magic, 4); + if (read != 4) + { + cemuLog_log(LogType::Force, "Failed to read WUHB magic numbers"); + return false; + } + static_assert(sizeof(magic) == s_headerMagicValue.size()); + return std::memcmp(&magic, s_headerMagicValue.data(), sizeof(magic)) == 0; +} +bool WUHBReader::ReadHeader() +{ + m_fileIn->SetPosition(0); + auto read = m_fileIn->readData(&m_header, sizeof(m_header)); + auto readSuccess = read == sizeof(m_header); + if (!readSuccess) + cemuLog_log(LogType::Force, "Failed to read WUHB header"); + return readSuccess; +} +unsigned char WUHBReader::NormalizeChar(unsigned char c) +{ + if (c >= 'a' && c <= 'z') + { + return c + 'A' - 'a'; + } + else + { + return c; + } +} +uint32 WUHBReader::CalcPathHash(uint32 parent, const char* path, uint32 start, size_t path_len) +{ + cemu_assert(path != nullptr || path_len == 0); + uint32 hash = parent ^ 123456789; + for (uint32 i = 0; i < path_len; i++) + { + hash = (hash >> 5) | (hash << 27); + hash ^= NormalizeChar(path[start + i]); + } + + return hash; +} diff --git a/src/Cafe/Filesystem/WUHB/WUHBReader.h b/src/Cafe/Filesystem/WUHB/WUHBReader.h new file mode 100644 index 00000000..9187f05a --- /dev/null +++ b/src/Cafe/Filesystem/WUHB/WUHBReader.h @@ -0,0 +1,45 @@ +#pragma once +#include <Common/FileStream.h> +#include "RomFSStructs.h" +class WUHBReader +{ + public: + static WUHBReader* FromPath(const fs::path& path); + + romfs_direntry_t GetDirEntry(uint32 offset) const; + romfs_fentry_t GetFileEntry(uint32 offset) const; + + uint64 GetFileSize(uint32 entryOffset) const; + + uint64 ReadFromFile(uint32 entryOffset, uint64 fileOffset, uint64 length, void* buffer) const; + + uint32 Lookup(const std::filesystem::path& path, bool isFile) const; + + private: + WUHBReader(FileStream* file) + : m_fileIn(file) + { + cemu_assert_debug(file != nullptr); + }; + WUHBReader() = delete; + + romfs_header_t m_header; + std::unique_ptr<FileStream> m_fileIn; + constexpr static std::string_view s_headerMagicValue = "WUHB"; + bool ReadHeader(); + bool CheckMagicValue() const; + + static inline unsigned char NormalizeChar(unsigned char c); + static uint32 CalcPathHash(uint32 parent, const char* path, uint32 start, size_t path_len); + + template<bool File> + using EntryType = std::conditional_t<File, romfs_fentry_t, romfs_direntry_t>; + template<bool File> + static const EntryType<File>& GetFallback(); + template<bool File> + EntryType<File> GetEntry(uint32 offset) const; + + template<bool T> + bool SearchHashList(uint32& entryOffset, const fs::path& targetName) const; + uint32 GetHashTableEntryOffset(uint32 hash, bool isFile) const; +}; diff --git a/src/Cafe/Filesystem/fsc.h b/src/Cafe/Filesystem/fsc.h index 09c1f508..a3df2af2 100644 --- a/src/Cafe/Filesystem/fsc.h +++ b/src/Cafe/Filesystem/fsc.h @@ -204,6 +204,9 @@ bool FSCDeviceWUD_Mount(std::string_view mountPath, std::string_view destination // wua device bool FSCDeviceWUA_Mount(std::string_view mountPath, std::string_view destinationBaseDir, class ZArchiveReader* archive, sint32 priority); +// wuhb device +bool FSCDeviceWUHB_Mount(std::string_view mountPath, std::string_view destinationBaseDir, class WUHBReader* wuhbReader, sint32 priority); + // hostFS device bool FSCDeviceHostFS_Mount(std::string_view mountPath, std::string_view hostTargetPath, sint32 priority); diff --git a/src/Cafe/Filesystem/fscDeviceWuhb.cpp b/src/Cafe/Filesystem/fscDeviceWuhb.cpp new file mode 100644 index 00000000..5e8e6484 --- /dev/null +++ b/src/Cafe/Filesystem/fscDeviceWuhb.cpp @@ -0,0 +1,151 @@ +#include "Filesystem/WUHB/WUHBReader.h" +#include "Cafe/Filesystem/fsc.h" +#include "Cafe/Filesystem/FST/FST.h" + +class FSCDeviceWuhbFileCtx : public FSCVirtualFile +{ + public: + FSCDeviceWuhbFileCtx(WUHBReader* reader, uint32 entryOffset, uint32 fscType) + : m_wuhbReader(reader), m_entryOffset(entryOffset), m_fscType(fscType) + { + cemu_assert(entryOffset != ROMFS_ENTRY_EMPTY); + if (fscType == FSC_TYPE_DIRECTORY) + { + romfs_direntry_t entry = reader->GetDirEntry(entryOffset); + m_dirIterOffset = entry.dirListHead; + m_fileIterOffset = entry.fileListHead; + } + } + sint32 fscGetType() override + { + return m_fscType; + } + uint64 fscQueryValueU64(uint32 id) override + { + if (m_fscType == FSC_TYPE_FILE) + { + if (id == FSC_QUERY_SIZE) + return m_wuhbReader->GetFileSize(m_entryOffset); + else if (id == FSC_QUERY_WRITEABLE) + return 0; // WUHB images are read-only + else + cemu_assert_error(); + } + else + { + cemu_assert_unimplemented(); + } + return 0; + } + uint32 fscWriteData(void* buffer, uint32 size) override + { + cemu_assert_error(); + return 0; + } + uint32 fscReadData(void* buffer, uint32 size) override + { + if (m_fscType != FSC_TYPE_FILE) + return 0; + auto read = m_wuhbReader->ReadFromFile(m_entryOffset, m_seek, size, buffer); + m_seek += read; + return read; + } + void fscSetSeek(uint64 seek) override + { + m_seek = seek; + } + uint64 fscGetSeek() override + { + if (m_fscType != FSC_TYPE_FILE) + return 0; + return m_seek; + } + void fscSetFileLength(uint64 endOffset) override + { + cemu_assert_error(); + } + bool fscDirNext(FSCDirEntry* dirEntry) override + { + if (m_dirIterOffset != ROMFS_ENTRY_EMPTY) + { + romfs_direntry_t entry = m_wuhbReader->GetDirEntry(m_dirIterOffset); + m_dirIterOffset = entry.listNext; + if(entry.name_size > 0) + { + dirEntry->isDirectory = true; + dirEntry->isFile = false; + dirEntry->fileSize = 0; + std::strncpy(dirEntry->path, entry.name.c_str(), FSC_MAX_DIR_NAME_LENGTH); + return true; + } + } + if (m_fileIterOffset != ROMFS_ENTRY_EMPTY) + { + romfs_fentry_t entry = m_wuhbReader->GetFileEntry(m_fileIterOffset); + m_fileIterOffset = entry.listNext; + if(entry.name_size > 0) + { + dirEntry->isDirectory = false; + dirEntry->isFile = true; + dirEntry->fileSize = entry.size; + std::strncpy(dirEntry->path, entry.name.c_str(), FSC_MAX_DIR_NAME_LENGTH); + return true; + } + } + + return false; + } + + private: + WUHBReader* m_wuhbReader{}; + uint32 m_fscType; + uint32 m_entryOffset = ROMFS_ENTRY_EMPTY; + uint32 m_dirIterOffset = ROMFS_ENTRY_EMPTY; + uint32 m_fileIterOffset = ROMFS_ENTRY_EMPTY; + uint64 m_seek = 0; +}; + +class fscDeviceWUHB : public fscDeviceC +{ + FSCVirtualFile* fscDeviceOpenByPath(std::string_view path, FSC_ACCESS_FLAG accessFlags, void* ctx, sint32* fscStatus) override + { + WUHBReader* reader = (WUHBReader*)ctx; + cemu_assert_debug(!HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::WRITE_PERMISSION)); // writing to WUHB is not supported + + bool isFile; + uint32 table_offset = ROMFS_ENTRY_EMPTY; + + if (table_offset == ROMFS_ENTRY_EMPTY && HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR)) + { + table_offset = reader->Lookup(path, false); + isFile = false; + } + if (table_offset == ROMFS_ENTRY_EMPTY && HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE)) + { + table_offset = reader->Lookup(path, true); + isFile = true; + } + + if (table_offset == ROMFS_ENTRY_EMPTY) + { + *fscStatus = FSC_STATUS_FILE_NOT_FOUND; + return nullptr; + } + + *fscStatus = FSC_STATUS_OK; + return new FSCDeviceWuhbFileCtx(reader, table_offset, isFile ? FSC_TYPE_FILE : FSC_TYPE_DIRECTORY); + } + + // singleton + public: + static fscDeviceWUHB& instance() + { + static fscDeviceWUHB _instance; + return _instance; + } +}; + +bool FSCDeviceWUHB_Mount(std::string_view mountPath, std::string_view destinationBaseDir, WUHBReader* wuhbReader, sint32 priority) +{ + return fsc_mount(mountPath, destinationBaseDir, &fscDeviceWUHB::instance(), wuhbReader, priority) == FSC_STATUS_OK; +} diff --git a/src/Cafe/TitleList/TitleInfo.cpp b/src/Cafe/TitleList/TitleInfo.cpp index 2f295811..12131058 100644 --- a/src/Cafe/TitleList/TitleInfo.cpp +++ b/src/Cafe/TitleList/TitleInfo.cpp @@ -1,9 +1,12 @@ #include "TitleInfo.h" #include "Cafe/Filesystem/fscDeviceHostFS.h" +#include "Cafe/Filesystem/WUHB/WUHBReader.h" #include "Cafe/Filesystem/FST/FST.h" #include "pugixml.hpp" #include "Common/FileStream.h" #include <zarchive/zarchivereader.h> +#include "util/IniParser/IniParser.h" +#include "util/crypto/crc32.h" #include "config/ActiveSettings.h" #include "util/helpers/helpers.h" @@ -97,6 +100,7 @@ TitleInfo::TitleInfo(const TitleInfo::CachedInfo& cachedInfo) m_isValid = false; if (cachedInfo.titleDataFormat != TitleDataFormat::HOST_FS && cachedInfo.titleDataFormat != TitleDataFormat::WIIU_ARCHIVE && + cachedInfo.titleDataFormat != TitleDataFormat::WUHB && cachedInfo.titleDataFormat != TitleDataFormat::WUD && cachedInfo.titleDataFormat != TitleDataFormat::NUS && cachedInfo.titleDataFormat != TitleDataFormat::INVALID_STRUCTURE) @@ -245,6 +249,16 @@ bool TitleInfo::DetectFormat(const fs::path& path, fs::path& pathOut, TitleDataF delete zar; return foundBase; } + else if (boost::iends_with(filenameStr, ".wuhb")) + { + std::unique_ptr<WUHBReader> reader{WUHBReader::FromPath(path)}; + if(reader) + { + formatOut = TitleDataFormat::WUHB; + pathOut = path; + return true; + } + } // note: Since a Wii U archive file (.wua) contains multiple titles we shouldn't auto-detect them here // instead TitleInfo has a second constructor which takes a subpath // unable to determine type by extension, check contents @@ -436,6 +450,23 @@ bool TitleInfo::Mount(std::string_view virtualPath, std::string_view subfolder, return false; } } + else if (m_titleFormat == TitleDataFormat::WUHB) + { + if (!m_wuhbreader) + { + m_wuhbreader = WUHBReader::FromPath(m_fullPath); + if (!m_wuhbreader) + return false; + } + bool r = FSCDeviceWUHB_Mount(virtualPath, subfolder, m_wuhbreader, mountPriority); + if (!r) + { + cemuLog_log(LogType::Force, "Failed to mount {} to {}", virtualPath, subfolder); + delete m_wuhbreader; + m_wuhbreader = nullptr; + return false; + } + } else { cemu_assert_unimplemented(); @@ -467,6 +498,12 @@ void TitleInfo::Unmount(std::string_view virtualPath) if (m_mountpoints.empty()) m_zarchive = nullptr; } + if (m_wuhbreader) + { + cemu_assert_debug(m_titleFormat == TitleDataFormat::WUHB); + delete m_wuhbreader; + m_wuhbreader = nullptr; + } } return; } @@ -502,6 +539,20 @@ bool TitleInfo::ParseXmlInfo() auto xmlData = fsc_extractFile(fmt::format("{}meta/meta.xml", mountPath).c_str()); if(xmlData) m_parsedMetaXml = ParsedMetaXml::Parse(xmlData->data(), xmlData->size()); + + if(!m_parsedMetaXml) + { + // meta/meta.ini (WUHB) + auto iniData = fsc_extractFile(fmt::format("{}meta/meta.ini", mountPath).c_str()); + if (iniData) + m_parsedMetaXml = ParseAromaIni(*iniData); + if(m_parsedMetaXml) + { + m_parsedCosXml = new ParsedCosXml{.argstr = "root.rpx"}; + m_parsedAppXml = new ParsedAppXml{m_parsedMetaXml->m_title_id, 0, 0, 0, 0}; + } + } + // code/app.xml xmlData = fsc_extractFile(fmt::format("{}code/app.xml", mountPath).c_str()); if(xmlData) @@ -539,6 +590,34 @@ bool TitleInfo::ParseXmlInfo() return true; } +ParsedMetaXml* TitleInfo::ParseAromaIni(std::span<unsigned char> content) +{ + IniParser parser{content}; + while (parser.NextSection() && parser.GetCurrentSectionName() != "menu") + continue; + if (parser.GetCurrentSectionName() != "menu") + return nullptr; + + auto parsed = std::make_unique<ParsedMetaXml>(); + + const auto author = parser.FindOption("author"); + if (author) + parsed->m_publisher[(size_t)CafeConsoleLanguage::EN] = *author; + + const auto longName = parser.FindOption("longname"); + if (longName) + parsed->m_long_name[(size_t)CafeConsoleLanguage::EN] = *longName; + + const auto shortName = parser.FindOption("shortname"); + if (shortName) + parsed->m_short_name[(size_t)CafeConsoleLanguage::EN] = *shortName; + + auto checksumInput = std::string{*author}.append(*longName).append(*shortName); + parsed->m_title_id = (0x0005000Full<<32) | crc32_calc(checksumInput.data(), checksumInput.length()); + + return parsed.release(); +} + bool TitleInfo::ParseAppXml(std::vector<uint8>& appXmlData) { pugi::xml_document app_doc; @@ -695,6 +774,9 @@ std::string TitleInfo::GetPrintPath() const case TitleDataFormat::WIIU_ARCHIVE: tmp.append(" [WUA]"); break; + case TitleDataFormat::WUHB: + tmp.append(" [WUHB]"); + break; default: break; } diff --git a/src/Cafe/TitleList/TitleInfo.h b/src/Cafe/TitleList/TitleInfo.h index e9347db7..fa5b9c89 100644 --- a/src/Cafe/TitleList/TitleInfo.h +++ b/src/Cafe/TitleList/TitleInfo.h @@ -127,6 +127,7 @@ public: WUD = 2, // WUD or WUX WIIU_ARCHIVE = 3, // Wii U compressed single-file archive (.wua) NUS = 4, // NUS format. Directory with .app files, title.tik and title.tmd + WUHB = 5, // error INVALID_STRUCTURE = 0, }; @@ -265,6 +266,7 @@ private: bool DetectFormat(const fs::path& path, fs::path& pathOut, TitleDataFormat& formatOut); void CalcUID(); void SetInvalidReason(InvalidReason reason); + ParsedMetaXml* ParseAromaIni(std::span<unsigned char> content); bool ParseAppXml(std::vector<uint8>& appXmlData); bool m_isValid{ false }; @@ -277,6 +279,7 @@ private: std::vector<std::pair<sint32, std::string>> m_mountpoints; class FSTVolume* m_wudVolume{}; class ZArchiveReader* m_zarchive{}; + class WUHBReader* m_wuhbreader{}; // xml info bool m_hasParsedXmlFiles{ false }; ParsedMetaXml* m_parsedMetaXml{}; diff --git a/src/Cafe/TitleList/TitleList.cpp b/src/Cafe/TitleList/TitleList.cpp index c288dd13..7b75fac7 100644 --- a/src/Cafe/TitleList/TitleList.cpp +++ b/src/Cafe/TitleList/TitleList.cpp @@ -342,7 +342,8 @@ bool _IsKnownFileNameOrExtension(const fs::path& path) fileExtension == ".wud" || fileExtension == ".wux" || fileExtension == ".iso" || - fileExtension == ".wua"; + fileExtension == ".wua" || + fileExtension == ".wuhb"; // note: To detect extracted titles with RPX we rely on the presence of the content,code,meta directory structure } diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index e8103f9a..c34c5477 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -643,16 +643,18 @@ void MainWindow::OnFileMenu(wxCommandEvent& event) if (menuId == MAINFRAME_MENU_ID_FILE_LOAD) { const auto wildcard = formatWxString( - "{}|*.wud;*.wux;*.wua;*.iso;*.rpx;*.elf;title.tmd" + "{}|*.wud;*.wux;*.wua;*.wuhb;*.iso;*.rpx;*.elf;title.tmd" "|{}|*.wud;*.wux;*.iso" "|{}|title.tmd" "|{}|*.wua" + "|{}|*.wuhb" "|{}|*.rpx;*.elf" "|{}|*", - _("All Wii U files (*.wud, *.wux, *.wua, *.iso, *.rpx, *.elf)"), + _("All Wii U files (*.wud, *.wux, *.wua, *.wuhb, *.iso, *.rpx, *.elf)"), _("Wii U image (*.wud, *.wux, *.iso, *.wad)"), _("Wii U NUS content"), _("Wii U archive (*.wua)"), + _("Wii U homebrew bundle (*.wuhb)"), _("Wii U executable (*.rpx, *.elf)"), _("All files (*.*)") ); diff --git a/src/gui/components/wxGameList.cpp b/src/gui/components/wxGameList.cpp index d7c9a4f8..eedfde5d 100644 --- a/src/gui/components/wxGameList.cpp +++ b/src/gui/components/wxGameList.cpp @@ -1230,6 +1230,16 @@ void wxGameList::AsyncWorkerThread() if(!titleInfo.Mount(tempMountPath, "", FSC_PRIORITY_BASE)) continue; auto tgaData = fsc_extractFile((tempMountPath + "/meta/iconTex.tga").c_str()); + // try iconTex.tga.gz + if (!tgaData) + { + tgaData = fsc_extractFile((tempMountPath + "/meta/iconTex.tga.gz").c_str()); + if (tgaData) + { + auto decompressed = zlibDecompress(*tgaData, 70*1024); + std::swap(tgaData, decompressed); + } + } bool iconSuccessfullyLoaded = false; if (tgaData && tgaData->size() > 16) { diff --git a/src/gui/components/wxTitleManagerList.cpp b/src/gui/components/wxTitleManagerList.cpp index c02bffb7..e8efb060 100644 --- a/src/gui/components/wxTitleManagerList.cpp +++ b/src/gui/components/wxTitleManagerList.cpp @@ -948,6 +948,8 @@ wxString wxTitleManagerList::GetTitleEntryText(const TitleEntry& entry, ItemColu return _("NUS"); case wxTitleManagerList::EntryFormat::WUA: return _("WUA"); + case wxTitleManagerList::EntryFormat::WUHB: + return _("WUHB"); } return ""; } @@ -1022,6 +1024,9 @@ void wxTitleManagerList::HandleTitleListCallback(CafeTitleListCallbackEvent* evt case TitleInfo::TitleDataFormat::WIIU_ARCHIVE: entryFormat = EntryFormat::WUA; break; + case TitleInfo::TitleDataFormat::WUHB: + entryFormat = EntryFormat::WUHB; + break; case TitleInfo::TitleDataFormat::HOST_FS: default: entryFormat = EntryFormat::Folder; diff --git a/src/gui/components/wxTitleManagerList.h b/src/gui/components/wxTitleManagerList.h index 14721c57..2780a9ce 100644 --- a/src/gui/components/wxTitleManagerList.h +++ b/src/gui/components/wxTitleManagerList.h @@ -44,6 +44,7 @@ public: WUD, NUS, WUA, + WUHB, }; // sort by column, if -1 will sort by last column or default (=titleid) diff --git a/src/util/helpers/helpers.cpp b/src/util/helpers/helpers.cpp index 7e22e9fb..bac2d446 100644 --- a/src/util/helpers/helpers.cpp +++ b/src/util/helpers/helpers.cpp @@ -11,6 +11,8 @@ #include <boost/random/uniform_int.hpp> +#include <zlib.h> + #if BOOST_OS_WINDOWS #include <TlHelp32.h> @@ -437,3 +439,42 @@ std::string GenerateRandomString(const size_t length, const std::string_view cha return result; } + +std::optional<std::vector<uint8>> zlibDecompress(const std::vector<uint8>& compressed, size_t sizeHint) +{ + int err; + std::vector<uint8> decompressed; + size_t outWritten = 0; + size_t bytesPerIteration = sizeHint; + z_stream stream; + stream.zalloc = Z_NULL; + stream.zfree = Z_NULL; + stream.opaque = Z_NULL; + stream.avail_in = compressed.size(); + stream.next_in = (Bytef*)compressed.data(); + err = inflateInit2(&stream, 32); // 32 is a zlib magic value to enable header detection + if (err != Z_OK) + return {}; + + do + { + decompressed.resize(decompressed.size() + bytesPerIteration); + const auto availBefore = decompressed.size() - outWritten; + stream.avail_out = availBefore; + stream.next_out = decompressed.data() + outWritten; + err = inflate(&stream, Z_NO_FLUSH); + if (!(err == Z_OK || err == Z_STREAM_END)) + { + inflateEnd(&stream); + return {}; + } + outWritten += availBefore - stream.avail_out; + bytesPerIteration *= 2; + } + while (err != Z_STREAM_END); + + inflateEnd(&stream); + decompressed.resize(stream.total_out); + + return decompressed; +} diff --git a/src/util/helpers/helpers.h b/src/util/helpers/helpers.h index 09b80fed..1edc2e19 100644 --- a/src/util/helpers/helpers.h +++ b/src/util/helpers/helpers.h @@ -257,3 +257,5 @@ bool IsWindows81OrGreater(); bool IsWindows10OrGreater(); fs::path GetParentProcess(); + +std::optional<std::vector<uint8>> zlibDecompress(const std::vector<uint8>& compressed, size_t sizeHint = 32*1024); From 70afe3a03342f3f89fb45089fd23cc1b4dffbe45 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 5 May 2024 09:11:08 +0200 Subject: [PATCH 100/299] nlibcurl: Use separte logging type --- src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp | 14 +++++++------- src/Cemu/Logging/CemuLogging.h | 1 + 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp b/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp index 7a8eacb7..a992665c 100644 --- a/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp +++ b/src/Cafe/OS/libs/nlibcurl/nlibcurl.cpp @@ -1505,18 +1505,18 @@ CURLcode curl_global_init_mem(uint32 flags, MEMPTR<curl_malloc_callback> malloc_ void load() { - cafeExportRegister("nlibcurl", curl_global_init_mem, LogType::Force); - cafeExportRegister("nlibcurl", curl_global_init, LogType::Force); + cafeExportRegister("nlibcurl", curl_global_init_mem, LogType::nlibcurl); + cafeExportRegister("nlibcurl", curl_global_init, LogType::nlibcurl); - cafeExportRegister("nlibcurl", curl_slist_append, LogType::Force); - cafeExportRegister("nlibcurl", curl_slist_free_all, LogType::Force); + cafeExportRegister("nlibcurl", curl_slist_append, LogType::nlibcurl); + cafeExportRegister("nlibcurl", curl_slist_free_all, LogType::nlibcurl); osLib_addFunction("nlibcurl", "curl_easy_strerror", export_curl_easy_strerror); osLib_addFunction("nlibcurl", "curl_share_init", export_curl_share_init); osLib_addFunction("nlibcurl", "curl_share_setopt", export_curl_share_setopt); osLib_addFunction("nlibcurl", "curl_share_cleanup", export_curl_share_cleanup); - cafeExportRegister("nlibcurl", mw_curl_easy_init, LogType::Force); + cafeExportRegister("nlibcurl", mw_curl_easy_init, LogType::nlibcurl); osLib_addFunction("nlibcurl", "curl_multi_init", export_curl_multi_init); osLib_addFunction("nlibcurl", "curl_multi_add_handle", export_curl_multi_add_handle); osLib_addFunction("nlibcurl", "curl_multi_perform", export_curl_multi_perform); @@ -1527,11 +1527,11 @@ void load() osLib_addFunction("nlibcurl", "curl_multi_cleanup", export_curl_multi_cleanup); osLib_addFunction("nlibcurl", "curl_multi_timeout", export_curl_multi_timeout); - cafeExportRegister("nlibcurl", curl_easy_init, LogType::Force); + cafeExportRegister("nlibcurl", curl_easy_init, LogType::nlibcurl); osLib_addFunction("nlibcurl", "curl_easy_reset", export_curl_easy_reset); osLib_addFunction("nlibcurl", "curl_easy_setopt", export_curl_easy_setopt); osLib_addFunction("nlibcurl", "curl_easy_getinfo", export_curl_easy_getinfo); - cafeExportRegister("nlibcurl", curl_easy_perform, LogType::Force); + cafeExportRegister("nlibcurl", curl_easy_perform, LogType::nlibcurl); diff --git a/src/Cemu/Logging/CemuLogging.h b/src/Cemu/Logging/CemuLogging.h index 44e89360..8fbb318c 100644 --- a/src/Cemu/Logging/CemuLogging.h +++ b/src/Cemu/Logging/CemuLogging.h @@ -41,6 +41,7 @@ enum class LogType : sint32 TextureReadback = 29, ProcUi = 39, + nlibcurl = 41, PRUDP = 40, }; From dd3ed5650983180ed71640567c588bd21bb43564 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 5 May 2024 10:05:35 +0200 Subject: [PATCH 101/299] nn_save: Fix inverted condition preventing accessing other title's saves --- src/Cafe/OS/libs/nn_save/nn_save.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Cafe/OS/libs/nn_save/nn_save.cpp b/src/Cafe/OS/libs/nn_save/nn_save.cpp index 518e4195..09d4413b 100644 --- a/src/Cafe/OS/libs/nn_save/nn_save.cpp +++ b/src/Cafe/OS/libs/nn_save/nn_save.cpp @@ -118,11 +118,11 @@ namespace save return false; } - SAVEStatus GetAbsoluteFullPathOtherApplication(uint32 persistentId, uint64 titleId, const char* subDir, char* outPath) + FS_RESULT GetAbsoluteFullPathOtherApplication(uint32 persistentId, uint64 titleId, const char* subDir, char* outPath) { uint32be applicationBox; if(acp::ACPGetApplicationBox(&applicationBox, titleId) != acp::ACPStatus::SUCCESS) - return (FSStatus)FS_RESULT::NOT_FOUND; + return FS_RESULT::NOT_FOUND; sint32 written = 0; if(applicationBox == 3) @@ -151,13 +151,13 @@ namespace save cemu_assert_unimplemented(); } else - return (FSStatus)FS_RESULT::NOT_FOUND; + return FS_RESULT::NOT_FOUND; if (written < SAVE_MAX_PATH_SIZE - 1) - return (FSStatus)FS_RESULT::SUCCESS; + return FS_RESULT::SUCCESS; cemu_assert_suspicious(); - return (FSStatus)(FS_RESULT::FATAL_ERROR); + return FS_RESULT::FATAL_ERROR; } typedef struct @@ -417,7 +417,7 @@ namespace save if (GetPersistentIdEx(accountSlot, &persistentId)) { char fullPath[SAVE_MAX_PATH_SIZE]; - if (GetAbsoluteFullPathOtherApplication(persistentId, titleId, path, fullPath)) + if (GetAbsoluteFullPathOtherApplication(persistentId, titleId, path, fullPath) == FS_RESULT::SUCCESS) result = coreinit::FSOpenFileAsync(client, block, fullPath, (char*)mode, outFileHandle, errHandling, (FSAsyncParams*)asyncParams); } else @@ -527,7 +527,7 @@ namespace save if (GetPersistentIdEx(accountSlot, &persistentId)) { char fullPath[SAVE_MAX_PATH_SIZE]; - if (GetAbsoluteFullPathOtherApplication(persistentId, titleId, path, fullPath) == (FSStatus)FS_RESULT::SUCCESS) + if (GetAbsoluteFullPathOtherApplication(persistentId, titleId, path, fullPath) == FS_RESULT::SUCCESS) result = coreinit::__FSQueryInfoAsync(client, block, (uint8*)fullPath, FSA_QUERY_TYPE_STAT, stat, errHandling, (FSAsyncParams*)asyncParams); // FSGetStatAsync(...) } else @@ -811,7 +811,7 @@ namespace save if (GetPersistentIdEx(accountSlot, &persistentId)) { char fullPath[SAVE_MAX_PATH_SIZE]; - if (GetAbsoluteFullPathOtherApplication(persistentId, titleId, path, fullPath)) + if (GetAbsoluteFullPathOtherApplication(persistentId, titleId, path, fullPath) == FS_RESULT::SUCCESS) result = coreinit::FSOpenDirAsync(client, block, fullPath, hDir, errHandling, (FSAsyncParams*)asyncParams); } else From bf37a8281e2dee8b7b9dc04478b99d7a8310ff0b Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 5 May 2024 14:06:26 +0200 Subject: [PATCH 102/299] CI: Update action versions --- .github/workflows/deploy_experimental_release.yml | 8 ++++---- .github/workflows/deploy_stable_release.yml | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/deploy_experimental_release.yml b/.github/workflows/deploy_experimental_release.yml index 3bf86db4..a8c5ec53 100644 --- a/.github/workflows/deploy_experimental_release.yml +++ b/.github/workflows/deploy_experimental_release.yml @@ -15,22 +15,22 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: cemu-bin-linux-x64 path: cemu-bin-linux-x64 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: cemu-appimage-x64 path: cemu-appimage-x64 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: cemu-bin-windows-x64 path: cemu-bin-windows-x64 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: cemu-bin-macos-x64 path: cemu-bin-macos-x64 diff --git a/.github/workflows/deploy_stable_release.yml b/.github/workflows/deploy_stable_release.yml index 5be31413..fd339e7d 100644 --- a/.github/workflows/deploy_stable_release.yml +++ b/.github/workflows/deploy_stable_release.yml @@ -17,22 +17,22 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: cemu-bin-linux-x64 path: cemu-bin-linux-x64 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: cemu-appimage-x64 path: cemu-appimage-x64 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: cemu-bin-windows-x64 path: cemu-bin-windows-x64 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: cemu-bin-macos-x64 path: cemu-bin-macos-x64 From bd13d4bdc30b608770f9f7cb7c5ec44f6687f329 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 5 May 2024 17:05:11 +0200 Subject: [PATCH 103/299] nn_act: Make AcquireToken gracefully fail in offline mode + refactor --- src/Cafe/IOSU/legacy/iosu_act.cpp | 500 ++++++++++++++++++----------- src/Cafe/IOSU/legacy/iosu_act.h | 2 +- src/Cafe/OS/libs/nn_act/nn_act.cpp | 10 +- 3 files changed, 310 insertions(+), 202 deletions(-) diff --git a/src/Cafe/IOSU/legacy/iosu_act.cpp b/src/Cafe/IOSU/legacy/iosu_act.cpp index 42856684..a115d6f1 100644 --- a/src/Cafe/IOSU/legacy/iosu_act.cpp +++ b/src/Cafe/IOSU/legacy/iosu_act.cpp @@ -21,14 +21,18 @@ using namespace iosu::kernel; +using NexToken = NAPI::ACTNexToken; +static_assert(sizeof(NexToken) == 0x25C); + struct { bool isInitialized; + std::mutex actMutex; }iosuAct = { }; // account manager -typedef struct +struct actAccountData_t { bool isValid; // options @@ -49,7 +53,12 @@ typedef struct // Mii FFLData_t miiData; uint16le miiNickname[ACT_NICKNAME_LENGTH]; -}actAccountData_t; + + bool IsNetworkAccount() const + { + return isNetworkAccount; // todo - IOSU only checks if accountId is not empty? + } +}; #define IOSU_ACT_ACCOUNT_MAX_COUNT (0xC) @@ -159,161 +168,11 @@ uint32 iosuAct_getAccountIdOfCurrentAccount() // IOSU act API interface -namespace iosu -{ - namespace act - { - uint8 getCurrentAccountSlot() - { - return 1; - } - - bool getPrincipalId(uint8 slot, uint32* principalId) - { - sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); - if (_actAccountData[accountIndex].isValid == false) - { - *principalId = 0; - return false; - } - *principalId = _actAccountData[accountIndex].principalId; - return true; - } - - bool getAccountId(uint8 slot, char* accountId) - { - sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); - if (_actAccountData[accountIndex].isValid == false) - { - *accountId = '\0'; - return false; - } - strcpy(accountId, _actAccountData[accountIndex].accountId); - return true; - } - - // returns empty string if invalid - std::string getAccountId2(uint8 slot) - { - sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); - if (_actAccountData[accountIndex].isValid == false) - return {}; - return {_actAccountData[accountIndex].accountId}; - } - - bool getMii(uint8 slot, FFLData_t* fflData) - { - sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); - if (_actAccountData[accountIndex].isValid == false) - { - return false; - } - memcpy(fflData, &_actAccountData[accountIndex].miiData, sizeof(FFLData_t)); - return true; - } - - // return screenname in little-endian wide characters - bool getScreenname(uint8 slot, uint16 screenname[ACT_NICKNAME_LENGTH]) - { - sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); - if (_actAccountData[accountIndex].isValid == false) - { - screenname[0] = '\0'; - return false; - } - for (sint32 i = 0; i < ACT_NICKNAME_LENGTH; i++) - { - screenname[i] = (uint16)_actAccountData[accountIndex].miiNickname[i]; - } - return true; - } - - bool getCountryIndex(uint8 slot, uint32* countryIndex) - { - sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); - if (_actAccountData[accountIndex].isValid == false) - { - *countryIndex = 0; - return false; - } - *countryIndex = _actAccountData[accountIndex].countryIndex; - return true; - } - - bool GetPersistentId(uint8 slot, uint32* persistentId) - { - sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); - if(!_actAccountData[accountIndex].isValid) - { - *persistentId = 0; - return false; - } - *persistentId = _actAccountData[accountIndex].persistentId; - return true; - } - - class ActService : public iosu::nn::IPCService - { - public: - ActService() : iosu::nn::IPCService("/dev/act") {} - - nnResult ServiceCall(uint32 serviceId, void* request, void* response) override - { - cemuLog_log(LogType::Force, "Unsupported service call to /dev/act"); - cemu_assert_unimplemented(); - return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_ACT, 0); - } - }; - - ActService gActService; - - void Initialize() - { - gActService.Start(); - } - - void Stop() - { - gActService.Stop(); - } - } -} - - -// IOSU act IO - -typedef struct -{ - /* +0x00 */ uint32be ukn00; - /* +0x04 */ uint32be ukn04; - /* +0x08 */ uint32be ukn08; - /* +0x0C */ uint32be subcommandCode; - /* +0x10 */ uint8 ukn10; - /* +0x11 */ uint8 ukn11; - /* +0x12 */ uint8 ukn12; - /* +0x13 */ uint8 accountSlot; - /* +0x14 */ uint32be unique; // is this command specific? -}cmdActRequest00_t; - -typedef struct -{ - uint32be returnCode; - uint8 transferableIdBase[8]; -}cmdActGetTransferableIDResult_t; - -#define ACT_SUBCMD_GET_TRANSFERABLE_ID 4 -#define ACT_SUBCMD_INITIALIZE 0x14 - -#define _cancelIfAccountDoesNotExist() \ -if (_actAccountData[accountIndex].isValid == false) \ -{ \ - /* account does not exist*/ \ - ioctlReturnValue = 0; \ - actCemuRequest->setACTReturnCode(BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_ACT, NN_ACT_RESULT_ACCOUNT_DOES_NOT_EXIST)); /* 0xA071F480 */ \ - actCemuRequest->resultU64.u64 = 0; \ - iosuIoctl_completeRequest(ioQueueEntry, ioctlReturnValue); \ - continue; \ -} +static const auto ACTResult_Ok = 0; +static const auto ACTResult_InvalidValue = BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_ACT, 0x12F00); // 0xC0712F00 +static const auto ACTResult_OutOfRange = BUILD_NN_RESULT(NN_RESULT_LEVEL_LVL6, NN_RESULT_MODULE_NN_ACT, 0x12D80); // 0xC0712D80 +static const auto ACTResult_AccountDoesNotExist = BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_ACT, NN_ACT_RESULT_ACCOUNT_DOES_NOT_EXIST); // 0xA071F480 +static const auto ACTResult_NotANetworkAccount = BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_ACT, 0x1FE80); // 0xA071FE80 nnResult ServerActErrorCodeToNNResult(NAPI::ACT_ERROR_CODE ec) { @@ -518,6 +377,291 @@ nnResult ServerActErrorCodeToNNResult(NAPI::ACT_ERROR_CODE ec) return nnResultStatus(NN_RESULT_MODULE_NN_ACT, NN_ERROR_CODE::ACT_UNKNOWN_SERVER_ERROR); } +namespace iosu +{ + namespace act + { + uint8 getCurrentAccountSlot() + { + return 1; + } + + actAccountData_t* GetAccountBySlotNo(uint8 slotNo) + { + // only call this while holding actMutex + uint8 accIndex; + if(slotNo == iosu::act::ACT_SLOT_CURRENT) + { + accIndex = getCurrentAccountSlot() - 1; + cemu_assert_debug(accIndex >= 0 && accIndex < IOSU_ACT_ACCOUNT_MAX_COUNT); + } + else if(slotNo > 0 && slotNo <= IOSU_ACT_ACCOUNT_MAX_COUNT) + accIndex = slotNo - 1; + else + { + return nullptr; + } + if(!_actAccountData[accIndex].isValid) + return nullptr; + return &_actAccountData[accIndex]; + } + + // has ownership of account data + // while any thread has a LockedAccount in non-null state no other thread can access the account data + class LockedAccount + { + public: + LockedAccount(uint8 slotNo) + { + iosuAct.actMutex.lock(); + m_account = GetAccountBySlotNo(slotNo); + if(!m_account) + iosuAct.actMutex.unlock(); + } + + ~LockedAccount() + { + if(m_account) + iosuAct.actMutex.unlock(); + } + + void Release() + { + if(m_account) + iosuAct.actMutex.unlock(); + m_account = nullptr; + } + + actAccountData_t* operator->() + { + return m_account; + } + + actAccountData_t& operator*() + { + return *m_account; + } + + LockedAccount(const LockedAccount&) = delete; + LockedAccount& operator=(const LockedAccount&) = delete; + + operator bool() const { return m_account != nullptr; } + + private: + actAccountData_t* m_account{nullptr}; + }; + + bool getPrincipalId(uint8 slot, uint32* principalId) + { + sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); + if (_actAccountData[accountIndex].isValid == false) + { + *principalId = 0; + return false; + } + *principalId = _actAccountData[accountIndex].principalId; + return true; + } + + bool getAccountId(uint8 slot, char* accountId) + { + sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); + if (_actAccountData[accountIndex].isValid == false) + { + *accountId = '\0'; + return false; + } + strcpy(accountId, _actAccountData[accountIndex].accountId); + return true; + } + + // returns empty string if invalid + std::string getAccountId2(uint8 slot) + { + sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); + if (_actAccountData[accountIndex].isValid == false) + return {}; + return {_actAccountData[accountIndex].accountId}; + } + + bool getMii(uint8 slot, FFLData_t* fflData) + { + sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); + if (_actAccountData[accountIndex].isValid == false) + { + return false; + } + memcpy(fflData, &_actAccountData[accountIndex].miiData, sizeof(FFLData_t)); + return true; + } + + // return screenname in little-endian wide characters + bool getScreenname(uint8 slot, uint16 screenname[ACT_NICKNAME_LENGTH]) + { + sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); + if (_actAccountData[accountIndex].isValid == false) + { + screenname[0] = '\0'; + return false; + } + for (sint32 i = 0; i < ACT_NICKNAME_LENGTH; i++) + { + screenname[i] = (uint16)_actAccountData[accountIndex].miiNickname[i]; + } + return true; + } + + bool getCountryIndex(uint8 slot, uint32* countryIndex) + { + sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); + if (_actAccountData[accountIndex].isValid == false) + { + *countryIndex = 0; + return false; + } + *countryIndex = _actAccountData[accountIndex].countryIndex; + return true; + } + + bool GetPersistentId(uint8 slot, uint32* persistentId) + { + sint32 accountIndex = iosuAct_getAccountIndexBySlot(slot); + if(!_actAccountData[accountIndex].isValid) + { + *persistentId = 0; + return false; + } + *persistentId = _actAccountData[accountIndex].persistentId; + return true; + } + + nnResult AcquireNexToken(uint8 accountSlot, uint64 titleId, uint16 titleVersion, uint32 serverId, uint8* tokenOut, uint32 tokenLen) + { + if (accountSlot != ACT_SLOT_CURRENT) + return ACTResult_InvalidValue; + LockedAccount account(accountSlot); + if (!account) + return ACTResult_AccountDoesNotExist; + if (!account->IsNetworkAccount()) + return ACTResult_NotANetworkAccount; + cemu_assert_debug(ActiveSettings::IsOnlineEnabled()); + if (tokenLen != sizeof(NexToken)) + return ACTResult_OutOfRange; + + NAPI::AuthInfo authInfo; + NAPI::NAPI_MakeAuthInfoFromCurrentAccount(authInfo); + NAPI::ACTGetNexTokenResult nexTokenResult = NAPI::ACT_GetNexToken_WithCache(authInfo, titleId, titleVersion, serverId); + if (nexTokenResult.isValid()) + { + memcpy(tokenOut, &nexTokenResult.nexToken, sizeof(NexToken)); + return ACTResult_Ok; + } + else if (nexTokenResult.apiError == NAPI_RESULT::SERVICE_ERROR) + { + nnResult returnCode = ServerActErrorCodeToNNResult(nexTokenResult.serviceError); + cemu_assert_debug((returnCode&0x80000000) != 0); + return returnCode; + } + return nnResultStatus(NN_RESULT_MODULE_NN_ACT, NN_ERROR_CODE::ACT_UNKNOWN_SERVER_ERROR); + } + + nnResult AcquireIndependentServiceToken(uint8 accountSlot, uint64 titleId, uint16 titleVersion, std::string_view clientId, uint8* tokenOut, uint32 tokenLen) + { + static constexpr size_t IndependentTokenMaxLength = 512+1; // 512 bytes + null terminator + if(accountSlot != ACT_SLOT_CURRENT) + return ACTResult_InvalidValue; + LockedAccount account(accountSlot); + if (!account) + return ACTResult_AccountDoesNotExist; + if (!account->IsNetworkAccount()) + return ACTResult_NotANetworkAccount; + cemu_assert_debug(ActiveSettings::IsOnlineEnabled()); + if (tokenLen < IndependentTokenMaxLength) + return ACTResult_OutOfRange; + NAPI::AuthInfo authInfo; + NAPI::NAPI_MakeAuthInfoFromCurrentAccount(authInfo); + account.Release(); + NAPI::ACTGetIndependentTokenResult tokenResult = NAPI::ACT_GetIndependentToken_WithCache(authInfo, titleId, titleVersion, clientId); + uint32 returnCode = 0; + if (tokenResult.isValid()) + { + for (size_t i = 0; i < std::min(tokenResult.token.size(), (size_t)IndependentTokenMaxLength); i++) + { + tokenOut[i] = tokenResult.token[i]; + tokenOut[i + 1] = '\0'; + } + returnCode = 0; + } + else + { + returnCode = 0x80000000; // todo - proper error codes + } + return returnCode; + } + + class ActService : public iosu::nn::IPCService + { + public: + ActService() : iosu::nn::IPCService("/dev/act") {} + + nnResult ServiceCall(uint32 serviceId, void* request, void* response) override + { + cemuLog_log(LogType::Force, "Unsupported service call to /dev/act"); + cemu_assert_unimplemented(); + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_ACT, 0); + } + }; + + ActService gActService; + + void Initialize() + { + gActService.Start(); + } + + void Stop() + { + gActService.Stop(); + } + } +} + + +// IOSU act IO + +typedef struct +{ + /* +0x00 */ uint32be ukn00; + /* +0x04 */ uint32be ukn04; + /* +0x08 */ uint32be ukn08; + /* +0x0C */ uint32be subcommandCode; + /* +0x10 */ uint8 ukn10; + /* +0x11 */ uint8 ukn11; + /* +0x12 */ uint8 ukn12; + /* +0x13 */ uint8 accountSlot; + /* +0x14 */ uint32be unique; // is this command specific? +}cmdActRequest00_t; + +typedef struct +{ + uint32be returnCode; + uint8 transferableIdBase[8]; +}cmdActGetTransferableIDResult_t; + +#define ACT_SUBCMD_GET_TRANSFERABLE_ID 4 +#define ACT_SUBCMD_INITIALIZE 0x14 + +#define _cancelIfAccountDoesNotExist() \ +if (_actAccountData[accountIndex].isValid == false) \ +{ \ + /* account does not exist*/ \ + ioctlReturnValue = 0; \ + actCemuRequest->setACTReturnCode(BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_ACT, NN_ACT_RESULT_ACCOUNT_DOES_NOT_EXIST)); /* 0xA071F480 */ \ + actCemuRequest->resultU64.u64 = 0; \ + iosuIoctl_completeRequest(ioQueueEntry, ioctlReturnValue); \ + continue; \ +} + int iosuAct_thread() { SetThreadName("iosuAct_thread"); @@ -674,47 +818,13 @@ int iosuAct_thread() } else if (actCemuRequest->requestCode == IOSU_ARC_ACQUIRENEXTOKEN) { - NAPI::AuthInfo authInfo; - NAPI::NAPI_MakeAuthInfoFromCurrentAccount(authInfo); - NAPI::ACTGetNexTokenResult nexTokenResult = NAPI::ACT_GetNexToken_WithCache(authInfo, actCemuRequest->titleId, actCemuRequest->titleVersion, actCemuRequest->serverId); - uint32 returnCode = 0; - if (nexTokenResult.isValid()) - { - *(NAPI::ACTNexToken*)actCemuRequest->resultBinary.binBuffer = nexTokenResult.nexToken; - returnCode = NN_RESULT_SUCCESS; - } - else if (nexTokenResult.apiError == NAPI_RESULT::SERVICE_ERROR) - { - returnCode = ServerActErrorCodeToNNResult(nexTokenResult.serviceError); - cemu_assert_debug((returnCode&0x80000000) != 0); - } - else - { - returnCode = nnResultStatus(NN_RESULT_MODULE_NN_ACT, NN_ERROR_CODE::ACT_UNKNOWN_SERVER_ERROR); - } - actCemuRequest->setACTReturnCode(returnCode); + nnResult r = iosu::act::AcquireNexToken(actCemuRequest->accountSlot, actCemuRequest->titleId, actCemuRequest->titleVersion, actCemuRequest->serverId, actCemuRequest->resultBinary.binBuffer, sizeof(NexToken)); + actCemuRequest->setACTReturnCode(r); } else if (actCemuRequest->requestCode == IOSU_ARC_ACQUIREINDEPENDENTTOKEN) { - NAPI::AuthInfo authInfo; - NAPI::NAPI_MakeAuthInfoFromCurrentAccount(authInfo); - NAPI::ACTGetIndependentTokenResult tokenResult = NAPI::ACT_GetIndependentToken_WithCache(authInfo, actCemuRequest->titleId, actCemuRequest->titleVersion, actCemuRequest->clientId); - - uint32 returnCode = 0; - if (tokenResult.isValid()) - { - for (size_t i = 0; i < std::min(tokenResult.token.size(), (size_t)200); i++) - { - actCemuRequest->resultBinary.binBuffer[i] = tokenResult.token[i]; - actCemuRequest->resultBinary.binBuffer[i + 1] = '\0'; - } - returnCode = 0; - } - else - { - returnCode = 0x80000000; // todo - proper error codes - } - actCemuRequest->setACTReturnCode(returnCode); + nnResult r = iosu::act::AcquireIndependentServiceToken(actCemuRequest->accountSlot, actCemuRequest->titleId, actCemuRequest->titleVersion, actCemuRequest->clientId, actCemuRequest->resultBinary.binBuffer, sizeof(actCemuRequest->resultBinary.binBuffer)); + actCemuRequest->setACTReturnCode(r); } else if (actCemuRequest->requestCode == IOSU_ARC_ACQUIREPIDBYNNID) { diff --git a/src/Cafe/IOSU/legacy/iosu_act.h b/src/Cafe/IOSU/legacy/iosu_act.h index d60966d4..8ed408a4 100644 --- a/src/Cafe/IOSU/legacy/iosu_act.h +++ b/src/Cafe/IOSU/legacy/iosu_act.h @@ -53,7 +53,7 @@ namespace iosu std::string getAccountId2(uint8 slot); - const uint8 ACT_SLOT_CURRENT = 0xFE; + static constexpr uint8 ACT_SLOT_CURRENT = 0xFE; void Initialize(); void Stop(); diff --git a/src/Cafe/OS/libs/nn_act/nn_act.cpp b/src/Cafe/OS/libs/nn_act/nn_act.cpp index af53edd7..f490ff19 100644 --- a/src/Cafe/OS/libs/nn_act/nn_act.cpp +++ b/src/Cafe/OS/libs/nn_act/nn_act.cpp @@ -114,6 +114,7 @@ namespace act { memset(token, 0, sizeof(independentServiceToken_t)); actPrepareRequest(); + actRequest->accountSlot = iosu::act::ACT_SLOT_CURRENT; actRequest->requestCode = IOSU_ARC_ACQUIREINDEPENDENTTOKEN; actRequest->titleId = CafeSystem::GetForegroundTitleId(); actRequest->titleVersion = CafeSystem::GetForegroundTitleVersion(); @@ -611,6 +612,7 @@ void nnActExport_AcquireNexServiceToken(PPCInterpreter_t* hCPU) ppcDefineParamU32(serverId, 1); memset(token, 0, sizeof(nexServiceToken_t)); actPrepareRequest(); + actRequest->accountSlot = iosu::act::ACT_SLOT_CURRENT; actRequest->requestCode = IOSU_ARC_ACQUIRENEXTOKEN; actRequest->titleId = CafeSystem::GetForegroundTitleId(); actRequest->titleVersion = CafeSystem::GetForegroundTitleVersion(); @@ -627,10 +629,8 @@ void nnActExport_AcquireNexServiceToken(PPCInterpreter_t* hCPU) void nnActExport_AcquireIndependentServiceToken(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(token, independentServiceToken_t, 0); - ppcDefineParamMEMPTR(serviceToken, const char, 1); - uint32 result = nn::act::AcquireIndependentServiceToken(token.GetPtr(), serviceToken.GetPtr(), 0); - cemuLog_logDebug(LogType::Force, "nn_act.AcquireIndependentServiceToken(0x{}, {}) -> {:x}", (void*)token.GetPtr(), serviceToken.GetPtr(), result); - cemuLog_logDebug(LogType::Force, "Token: {}", serviceToken.GetPtr()); + ppcDefineParamMEMPTR(clientId, const char, 1); + uint32 result = nn::act::AcquireIndependentServiceToken(token.GetPtr(), clientId.GetPtr(), 0); osLib_returnFromFunction(hCPU, result); } @@ -640,7 +640,6 @@ void nnActExport_AcquireIndependentServiceToken2(PPCInterpreter_t* hCPU) ppcDefineParamMEMPTR(clientId, const char, 1); ppcDefineParamU32(cacheDurationInSeconds, 2); uint32 result = nn::act::AcquireIndependentServiceToken(token, clientId.GetPtr(), cacheDurationInSeconds); - cemuLog_logDebug(LogType::Force, "Called nn_act.AcquireIndependentServiceToken2"); osLib_returnFromFunction(hCPU, result); } @@ -648,7 +647,6 @@ void nnActExport_AcquireEcServiceToken(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(pEcServiceToken, independentServiceToken_t, 0); uint32 result = nn::act::AcquireIndependentServiceToken(pEcServiceToken.GetPtr(), "71a6f5d6430ea0183e3917787d717c46", 0); - cemuLog_logDebug(LogType::Force, "Called nn_act.AcquireEcServiceToken"); osLib_returnFromFunction(hCPU, result); } From 7d6d4173549a55070683feac33afaad038383813 Mon Sep 17 00:00:00 2001 From: capitalistspz <keipitalists@proton.me> Date: Mon, 6 May 2024 02:27:30 +0100 Subject: [PATCH 104/299] Input: Improve setting of dpd_enable_fg (#1127) --- src/input/api/Controller.h | 2 ++ src/input/api/ControllerState.h | 6 ++++++ src/input/api/DSU/DSUController.cpp | 7 +++++++ src/input/api/DSU/DSUController.h | 1 + src/input/api/Wiimote/NativeWiimoteController.cpp | 5 +++++ src/input/api/Wiimote/NativeWiimoteController.h | 1 + src/input/api/Wiimote/WiimoteControllerProvider.cpp | 6 ++++++ src/input/api/Wiimote/WiimoteControllerProvider.h | 2 ++ src/input/emulated/EmulatedController.cpp | 11 +++++++++++ src/input/emulated/EmulatedController.h | 1 + src/input/emulated/WPADController.cpp | 12 +++++++++--- 11 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/input/api/Controller.h b/src/input/api/Controller.h index 8b1c8e62..e2475191 100644 --- a/src/input/api/Controller.h +++ b/src/input/api/Controller.h @@ -2,6 +2,7 @@ #include "input/InputManager.h" #include "input/motion/MotionSample.h" +#include "input/api/ControllerState.h" namespace pugi { @@ -118,6 +119,7 @@ public: virtual bool has_position() { return false; } virtual glm::vec2 get_position() { return {}; } virtual glm::vec2 get_prev_position() { return {}; } + virtual PositionVisibility GetPositionVisibility() {return PositionVisibility::NONE;}; virtual bool has_rumble() { return false; } virtual void start_rumble() {} diff --git a/src/input/api/ControllerState.h b/src/input/api/ControllerState.h index ce79a1e0..65bfec9f 100644 --- a/src/input/api/ControllerState.h +++ b/src/input/api/ControllerState.h @@ -3,6 +3,12 @@ #include <glm/vec2.hpp> #include "util/helpers/fspinlock.h" +enum class PositionVisibility { + NONE = 0, + FULL = 1, + PARTIAL = 2 +}; + // helper class for storing and managing button press states in a thread-safe manner struct ControllerButtonState { diff --git a/src/input/api/DSU/DSUController.cpp b/src/input/api/DSU/DSUController.cpp index f134440c..082f7e39 100644 --- a/src/input/api/DSU/DSUController.cpp +++ b/src/input/api/DSU/DSUController.cpp @@ -93,6 +93,13 @@ glm::vec2 DSUController::get_prev_position() return {}; } +PositionVisibility DSUController::GetPositionVisibility() +{ + const auto state = m_provider->get_prev_state(m_index); + + return (state.data.tpad1.active || state.data.tpad2.active) ? PositionVisibility::FULL : PositionVisibility::NONE; +} + std::string DSUController::get_button_name(uint64 button) const { switch (button) diff --git a/src/input/api/DSU/DSUController.h b/src/input/api/DSU/DSUController.h index 801f609c..e6e2936d 100644 --- a/src/input/api/DSU/DSUController.h +++ b/src/input/api/DSU/DSUController.h @@ -32,6 +32,7 @@ public: bool has_position() override; glm::vec2 get_position() override; glm::vec2 get_prev_position() override; + PositionVisibility GetPositionVisibility() override; std::string get_button_name(uint64 button) const override; diff --git a/src/input/api/Wiimote/NativeWiimoteController.cpp b/src/input/api/Wiimote/NativeWiimoteController.cpp index 3f9e82a5..9aa56d9c 100644 --- a/src/input/api/Wiimote/NativeWiimoteController.cpp +++ b/src/input/api/Wiimote/NativeWiimoteController.cpp @@ -98,6 +98,11 @@ glm::vec2 NativeWiimoteController::get_prev_position() const auto state = m_provider->get_state(m_index); return state.ir_camera.m_prev_position; } +PositionVisibility NativeWiimoteController::GetPositionVisibility() +{ + const auto state = m_provider->get_state(m_index); + return state.ir_camera.m_positionVisibility; +} bool NativeWiimoteController::has_low_battery() { diff --git a/src/input/api/Wiimote/NativeWiimoteController.h b/src/input/api/Wiimote/NativeWiimoteController.h index ed3caa08..8e9c0774 100644 --- a/src/input/api/Wiimote/NativeWiimoteController.h +++ b/src/input/api/Wiimote/NativeWiimoteController.h @@ -40,6 +40,7 @@ public: bool has_position() override; glm::vec2 get_position() override; glm::vec2 get_prev_position() override; + PositionVisibility GetPositionVisibility() override; bool has_motion() override { return true; } bool has_rumble() override { return true; } diff --git a/src/input/api/Wiimote/WiimoteControllerProvider.cpp b/src/input/api/Wiimote/WiimoteControllerProvider.cpp index 5aac3fe4..c80f3fbe 100644 --- a/src/input/api/Wiimote/WiimoteControllerProvider.cpp +++ b/src/input/api/Wiimote/WiimoteControllerProvider.cpp @@ -766,14 +766,20 @@ void WiimoteControllerProvider::calculate_ir_position(WiimoteState& wiimote_stat ir.middle = ir.position; ir.distance = glm::length(ir.dots[indices.first].pos - ir.dots[indices.second].pos); ir.indices = indices; + ir.m_positionVisibility = PositionVisibility::FULL; } else if (ir.dots[indices.first].visible) { ir.position = ir.middle + (ir.dots[indices.first].pos - ir.prev_dots[indices.first].pos); + ir.m_positionVisibility = PositionVisibility::PARTIAL; } else if (ir.dots[indices.second].visible) { ir.position = ir.middle + (ir.dots[indices.second].pos - ir.prev_dots[indices.second].pos); + ir.m_positionVisibility = PositionVisibility::PARTIAL; + } + else { + ir.m_positionVisibility = PositionVisibility::NONE; } } diff --git a/src/input/api/Wiimote/WiimoteControllerProvider.h b/src/input/api/Wiimote/WiimoteControllerProvider.h index 40fe878a..7629b641 100644 --- a/src/input/api/Wiimote/WiimoteControllerProvider.h +++ b/src/input/api/Wiimote/WiimoteControllerProvider.h @@ -5,6 +5,7 @@ #include "input/api/Wiimote/WiimoteMessages.h" #include "input/api/ControllerProvider.h" +#include "input/api/ControllerState.h" #include <list> #include <variant> @@ -61,6 +62,7 @@ public: std::array<IRDot, 4> dots{}, prev_dots{}; glm::vec2 position{}, m_prev_position{}; + PositionVisibility m_positionVisibility; glm::vec2 middle {}; float distance = 0; std::pair<sint32, sint32> indices{ 0,1 }; diff --git a/src/input/emulated/EmulatedController.cpp b/src/input/emulated/EmulatedController.cpp index e254db34..ad9b6ac1 100644 --- a/src/input/emulated/EmulatedController.cpp +++ b/src/input/emulated/EmulatedController.cpp @@ -207,6 +207,17 @@ glm::vec2 EmulatedController::get_prev_position() const return {}; } +PositionVisibility EmulatedController::GetPositionVisibility() const +{ + std::shared_lock lock(m_mutex); + for (const auto& controller : m_controllers) + { + if (controller->has_position()) + return controller->GetPositionVisibility(); + } + return PositionVisibility::NONE; +} + void EmulatedController::add_controller(std::shared_ptr<ControllerBase> controller) { controller->connect(); diff --git a/src/input/emulated/EmulatedController.h b/src/input/emulated/EmulatedController.h index b7bd8c6d..907be07e 100644 --- a/src/input/emulated/EmulatedController.h +++ b/src/input/emulated/EmulatedController.h @@ -67,6 +67,7 @@ public: bool has_position() const; glm::vec2 get_position() const; glm::vec2 get_prev_position() const; + PositionVisibility GetPositionVisibility() const; void add_controller(std::shared_ptr<ControllerBase> controller); void remove_controller(const std::shared_ptr<ControllerBase>& controller); diff --git a/src/input/emulated/WPADController.cpp b/src/input/emulated/WPADController.cpp index 819596ab..2eae0f86 100644 --- a/src/input/emulated/WPADController.cpp +++ b/src/input/emulated/WPADController.cpp @@ -1,3 +1,4 @@ +#include <api/Controller.h> #include "input/emulated/WPADController.h" #include "input/emulated/ClassicController.h" @@ -308,10 +309,13 @@ void WPADController::KPADRead(KPADStatus_t& status, const BtnRepeat& repeat) status.mpls.dir.Z.z = attitude[8]; } } - - if (has_position()) + auto visibility = GetPositionVisibility(); + if (has_position() && visibility != PositionVisibility::NONE) { - status.dpd_valid_fg = 1; + if (visibility == PositionVisibility::FULL) + status.dpd_valid_fg = 2; + else + status.dpd_valid_fg = -1; const auto position = get_position(); @@ -324,6 +328,8 @@ void WPADController::KPADRead(KPADStatus_t& status, const BtnRepeat& repeat) status.vec.y = delta.y; status.speed = glm::length(delta); } + else + status.dpd_valid_fg = 0; switch (type()) { From 065fb7eb58855ec2d8c009f2dfabc3e815b91915 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Mon, 6 May 2024 09:15:36 +0200 Subject: [PATCH 105/299] coreinit: Add reschedule special case to avoid a deadlock Fixes Just Dance 2019 locking up on boot --- src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp index 533360aa..fbf498db 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp @@ -763,6 +763,11 @@ namespace coreinit uint32 coreIndex = OSGetCoreId(); if (!newThread->context.hasCoreAffinitySet(coreIndex)) return false; + // special case: if current and new thread are running only on the same core then reschedule even if priority is equal + // this resolves a deadlock in Just Dance 2019 where one thread would always reacquire the same mutex within it's timeslice, blocking another thread on the same core from acquiring it + if ((1<<coreIndex) == newThread->context.affinity && currentThread->context.affinity == newThread->context.affinity && currentThread->effectivePriority == newThread->effectivePriority) + return true; + // otherwise reschedule if new thread has higher priority return newThread->effectivePriority < currentThread->effectivePriority; } From 3f8722f0a6789065f709daa3d6a636e2334b3bad Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Mon, 6 May 2024 18:18:42 +0200 Subject: [PATCH 106/299] Track online-enable and network-service settings per-account instead of globally --- src/config/ActiveSettings.cpp | 12 ++- src/config/CemuConfig.cpp | 58 +++++++++++++- src/config/CemuConfig.h | 10 ++- src/config/NetworkSettings.cpp | 9 ++- src/config/NetworkSettings.h | 4 +- src/gui/GeneralSettings2.cpp | 137 ++++++++++++++++++++------------- src/gui/GeneralSettings2.h | 4 +- src/gui/MainWindow.cpp | 32 -------- 8 files changed, 164 insertions(+), 102 deletions(-) diff --git a/src/config/ActiveSettings.cpp b/src/config/ActiveSettings.cpp index 81662ab5..07e6f16d 100644 --- a/src/config/ActiveSettings.cpp +++ b/src/config/ActiveSettings.cpp @@ -131,7 +131,12 @@ uint32 ActiveSettings::GetPersistentId() bool ActiveSettings::IsOnlineEnabled() { - return GetConfig().account.online_enabled && Account::GetAccount(GetPersistentId()).IsValidOnlineAccount() && HasRequiredOnlineFiles(); + if(!Account::GetAccount(GetPersistentId()).IsValidOnlineAccount()) + return false; + if(!HasRequiredOnlineFiles()) + return false; + NetworkService networkService = static_cast<NetworkService>(GetConfig().GetAccountNetworkService(GetPersistentId())); + return networkService == NetworkService::Nintendo || networkService == NetworkService::Pretendo || networkService == NetworkService::Custom; } bool ActiveSettings::HasRequiredOnlineFiles() @@ -139,8 +144,9 @@ bool ActiveSettings::HasRequiredOnlineFiles() return s_has_required_online_files; } -NetworkService ActiveSettings::GetNetworkService() { - return static_cast<NetworkService>(GetConfig().account.active_service.GetValue()); +NetworkService ActiveSettings::GetNetworkService() +{ + return GetConfig().GetAccountNetworkService(GetPersistentId()); } bool ActiveSettings::DumpShadersEnabled() diff --git a/src/config/CemuConfig.cpp b/src/config/CemuConfig.cpp index e4be97a7..4f1736e2 100644 --- a/src/config/CemuConfig.cpp +++ b/src/config/CemuConfig.cpp @@ -328,8 +328,22 @@ void CemuConfig::Load(XMLConfigParser& parser) // account auto acc = parser.get("Account"); account.m_persistent_id = acc.get("PersistentId", account.m_persistent_id); - account.online_enabled = acc.get("OnlineEnabled", account.online_enabled); - account.active_service = acc.get("ActiveService",account.active_service); + // legacy online settings, we only parse these for upgrading purposes + account.legacy_online_enabled = acc.get("OnlineEnabled", account.legacy_online_enabled); + account.legacy_active_service = acc.get("ActiveService",account.legacy_active_service); + // per-account online setting + auto accService = parser.get("AccountService"); + account.service_select.clear(); + for (auto element = accService.get("SelectedService"); element.valid(); element = accService.get("SelectedService", element)) + { + uint32 persistentId = element.get_attribute<uint32>("PersistentId", 0); + sint32 serviceIndex = element.get_attribute<sint32>("Service", 0); + NetworkService networkService = static_cast<NetworkService>(serviceIndex); + if (persistentId < Account::kMinPersistendId) + continue; + if(networkService == NetworkService::Offline || networkService == NetworkService::Nintendo || networkService == NetworkService::Pretendo || networkService == NetworkService::Custom) + account.service_select.emplace(persistentId, networkService); + } // debug auto debug = parser.get("Debug"); #if BOOST_OS_WINDOWS @@ -512,8 +526,17 @@ void CemuConfig::Save(XMLConfigParser& parser) // account auto acc = config.set("Account"); acc.set("PersistentId", account.m_persistent_id.GetValue()); - acc.set("OnlineEnabled", account.online_enabled.GetValue()); - acc.set("ActiveService",account.active_service.GetValue()); + // legacy online mode setting + acc.set("OnlineEnabled", account.legacy_online_enabled.GetValue()); + acc.set("ActiveService",account.legacy_active_service.GetValue()); + // per-account online setting + auto accService = config.set("AccountService"); + for(auto& it : account.service_select) + { + auto entry = accService.set("SelectedService"); + entry.set_attribute("PersistentId", it.first); + entry.set_attribute("Service", static_cast<sint32>(it.second)); + } // debug auto debug = config.set("Debug"); #if BOOST_OS_WINDOWS @@ -609,3 +632,30 @@ void CemuConfig::AddRecentNfcFile(std::string_view file) while (recent_nfc_files.size() > kMaxRecentEntries) recent_nfc_files.pop_back(); } + +NetworkService CemuConfig::GetAccountNetworkService(uint32 persistentId) +{ + auto it = account.service_select.find(persistentId); + if (it != account.service_select.end()) + { + NetworkService serviceIndex = it->second; + // make sure the returned service is valid + if (serviceIndex != NetworkService::Offline && + serviceIndex != NetworkService::Nintendo && + serviceIndex != NetworkService::Pretendo && + serviceIndex != NetworkService::Custom) + return NetworkService::Offline; + if( static_cast<NetworkService>(serviceIndex) == NetworkService::Custom && !NetworkConfig::XMLExists() ) + return NetworkService::Offline; // custom is selected but no custom config exists + return serviceIndex; + } + // if not found, return the legacy value + if(!account.legacy_online_enabled) + return NetworkService::Offline; + return static_cast<NetworkService>(account.legacy_active_service.GetValue() + 1); // +1 because "Offline" now takes index 0 +} + +void CemuConfig::SetAccountSelectedService(uint32 persistentId, NetworkService serviceIndex) +{ + account.service_select[persistentId] = serviceIndex; +} diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index 9f1e7983..cab7a1af 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -8,6 +8,8 @@ #include <wx/language.h> #include <wx/intl.h> +enum class NetworkService; + struct GameEntry { GameEntry() = default; @@ -483,8 +485,9 @@ struct CemuConfig struct { ConfigValueBounds<uint32> m_persistent_id{ Account::kMinPersistendId, Account::kMinPersistendId, 0xFFFFFFFF }; - ConfigValue<bool> online_enabled{false}; - ConfigValue<int> active_service{0}; + ConfigValue<bool> legacy_online_enabled{false}; + ConfigValue<int> legacy_active_service{0}; + std::unordered_map<uint32, NetworkService> service_select; // per-account service index. Key is persistentId }account{}; // input @@ -509,6 +512,9 @@ struct CemuConfig bool GetGameListCustomName(uint64 titleId, std::string& customName); void SetGameListCustomName(uint64 titleId, std::string customName); + NetworkService GetAccountNetworkService(uint32 persistentId); + void SetAccountSelectedService(uint32 persistentId, NetworkService serviceIndex); + private: GameEntry* GetGameEntryByTitleId(uint64 titleId); GameEntry* CreateGameEntry(uint64 titleId); diff --git a/src/config/NetworkSettings.cpp b/src/config/NetworkSettings.cpp index b086d0ae..42dc9996 100644 --- a/src/config/NetworkSettings.cpp +++ b/src/config/NetworkSettings.cpp @@ -34,14 +34,15 @@ void NetworkConfig::Load(XMLConfigParser& parser) bool NetworkConfig::XMLExists() { + static std::optional<bool> s_exists; // caches result of fs::exists + if(s_exists.has_value()) + return *s_exists; std::error_code ec; if (!fs::exists(ActiveSettings::GetConfigPath("network_services.xml"), ec)) { - if (static_cast<NetworkService>(GetConfig().account.active_service.GetValue()) == NetworkService::Custom) - { - GetConfig().account.active_service = 0; - } + s_exists = false; return false; } + s_exists = true; return true; } \ No newline at end of file diff --git a/src/config/NetworkSettings.h b/src/config/NetworkSettings.h index be311182..6e114a0e 100644 --- a/src/config/NetworkSettings.h +++ b/src/config/NetworkSettings.h @@ -5,9 +5,11 @@ enum class NetworkService { + Offline, Nintendo, Pretendo, - Custom + Custom, + COUNT = Custom }; struct NetworkConfig diff --git a/src/gui/GeneralSettings2.cpp b/src/gui/GeneralSettings2.cpp index dab30981..c0b54949 100644 --- a/src/gui/GeneralSettings2.cpp +++ b/src/gui/GeneralSettings2.cpp @@ -683,18 +683,6 @@ wxPanel* GeneralSettings2::AddAccountPage(wxNotebook* notebook) content->Add(m_delete_account, 0, wxEXPAND | wxALL | wxALIGN_RIGHT, 5); m_delete_account->Bind(wxEVT_BUTTON, &GeneralSettings2::OnAccountDelete, this); - wxString choices[] = { _("Nintendo"), _("Pretendo"), _("Custom") }; - m_active_service = new wxRadioBox(online_panel, wxID_ANY, _("Network Service"), wxDefaultPosition, wxDefaultSize, std::size(choices), choices, 3, wxRA_SPECIFY_COLS); - if (!NetworkConfig::XMLExists()) - m_active_service->Enable(2, false); - - m_active_service->SetItemToolTip(0, _("Connect to the official Nintendo Network Service")); - m_active_service->SetItemToolTip(1, _("Connect to the Pretendo Network Service")); - m_active_service->SetItemToolTip(2, _("Connect to a custom Network Service (configured via network_services.xml)")); - - m_active_service->Bind(wxEVT_RADIOBOX, &GeneralSettings2::OnAccountServiceChanged,this); - content->Add(m_active_service, 0, wxEXPAND | wxALL, 5); - box_sizer->Add(content, 1, wxEXPAND, 5); online_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); @@ -704,17 +692,33 @@ wxPanel* GeneralSettings2::AddAccountPage(wxNotebook* notebook) m_active_account->Enable(false); m_create_account->Enable(false); m_delete_account->Enable(false); + } + } + + + { + wxString choices[] = { _("Offline"), _("Nintendo"), _("Pretendo"), _("Custom") }; + m_active_service = new wxRadioBox(online_panel, wxID_ANY, _("Network Service"), wxDefaultPosition, wxDefaultSize, std::size(choices), choices, 4, wxRA_SPECIFY_COLS); + if (!NetworkConfig::XMLExists()) + m_active_service->Enable(3, false); + + m_active_service->SetItemToolTip(0, _("Online functionality disabled for this account")); + m_active_service->SetItemToolTip(1, _("Connect to the official Nintendo Network Service")); + m_active_service->SetItemToolTip(2, _("Connect to the Pretendo Network Service")); + m_active_service->SetItemToolTip(3, _("Connect to a custom Network Service (configured via network_services.xml)")); + + m_active_service->Bind(wxEVT_RADIOBOX, &GeneralSettings2::OnAccountServiceChanged,this); + online_panel_sizer->Add(m_active_service, 0, wxEXPAND | wxALL, 5); + + if (CafeSystem::IsTitleRunning()) + { m_active_service->Enable(false); } } - + { - auto* box = new wxStaticBox(online_panel, wxID_ANY, _("Online settings")); + auto* box = new wxStaticBox(online_panel, wxID_ANY, _("Online play requirements")); auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); - - m_online_enabled = new wxCheckBox(box, wxID_ANY, _("Enable online mode")); - m_online_enabled->Bind(wxEVT_CHECKBOX, &GeneralSettings2::OnOnlineEnable, this); - box_sizer->Add(m_online_enabled, 0, wxEXPAND | wxALL, 5); auto* row = new wxFlexGridSizer(0, 2, 0, 0); row->SetFlexibleDirection(wxBOTH); @@ -873,6 +877,14 @@ GeneralSettings2::GeneralSettings2(wxWindow* parent, bool game_launched) DisableSettings(game_launched); } +uint32 GeneralSettings2::GetSelectedAccountPersistentId() +{ + const auto active_account = m_active_account->GetSelection(); + if (active_account == wxNOT_FOUND) + return GetConfig().account.m_persistent_id.GetInitValue(); + return dynamic_cast<wxAccountData*>(m_active_account->GetClientObject(active_account))->GetAccount().GetPersistentId(); +} + void GeneralSettings2::StoreConfig() { auto* app = (CemuApp*)wxTheApp; @@ -1038,14 +1050,7 @@ void GeneralSettings2::StoreConfig() config.notification.friends = m_friends_data->GetValue(); // account - const auto active_account = m_active_account->GetSelection(); - if (active_account == wxNOT_FOUND) - config.account.m_persistent_id = config.account.m_persistent_id.GetInitValue(); - else - config.account.m_persistent_id = dynamic_cast<wxAccountData*>(m_active_account->GetClientObject(active_account))->GetAccount().GetPersistentId(); - - config.account.online_enabled = m_online_enabled->GetValue(); - config.account.active_service = m_active_service->GetSelection(); + config.account.m_persistent_id = GetSelectedAccountPersistentId(); // debug config.crash_dump = (CrashDump)m_crash_dump->GetSelection(); @@ -1371,14 +1376,13 @@ void GeneralSettings2::UpdateAccountInformation() { m_account_grid->SetSplitterPosition(100); - m_online_status->SetLabel(_("At least one issue has been found")); - const auto selection = m_active_account->GetSelection(); if(selection == wxNOT_FOUND) { m_validate_online->SetBitmap(wxBITMAP_PNG_FROM_DATA(PNG_ERROR).ConvertToImage().Scale(16, 16)); m_validate_online->SetWindowStyleFlag(m_validate_online->GetWindowStyleFlag() & ~wxBORDER_NONE); ResetAccountInformation(); + m_online_status->SetLabel(_("No account selected")); return; } @@ -1404,11 +1408,26 @@ void GeneralSettings2::UpdateAccountInformation() index = 0; country_property->SetChoiceSelection(index); - const bool online_valid = account.IsValidOnlineAccount() && ActiveSettings::HasRequiredOnlineFiles(); - if (online_valid) + const bool online_fully_valid = account.IsValidOnlineAccount() && ActiveSettings::HasRequiredOnlineFiles(); + if (ActiveSettings::HasRequiredOnlineFiles()) + { + if(account.IsValidOnlineAccount()) + m_online_status->SetLabel(_("Selected account is a valid online account")); + else + m_online_status->SetLabel(_("Selected account is not linked to a NNID or PNID")); + } + else + { + if(NCrypto::OTP_IsPresent() != NCrypto::SEEPROM_IsPresent()) + m_online_status->SetLabel(_("OTP.bin or SEEPROM.bin is missing")); + else if(NCrypto::OTP_IsPresent() && NCrypto::SEEPROM_IsPresent()) + m_online_status->SetLabel(_("OTP and SEEPROM present but no certificate files were found")); + else + m_online_status->SetLabel(_("Online play is not set up. Follow the guide below to get started")); + } + + if(online_fully_valid) { - - m_online_status->SetLabel(_("Your account is a valid online account")); m_validate_online->SetBitmap(wxBITMAP_PNG_FROM_DATA(PNG_CHECK_YES).ConvertToImage().Scale(16, 16)); m_validate_online->SetWindowStyleFlag(m_validate_online->GetWindowStyleFlag() | wxBORDER_NONE); } @@ -1417,7 +1436,28 @@ void GeneralSettings2::UpdateAccountInformation() m_validate_online->SetBitmap(wxBITMAP_PNG_FROM_DATA(PNG_ERROR).ConvertToImage().Scale(16, 16)); m_validate_online->SetWindowStyleFlag(m_validate_online->GetWindowStyleFlag() & ~wxBORDER_NONE); } - + + // enable/disable network service field depending on online requirements + m_active_service->Enable(online_fully_valid && !CafeSystem::IsTitleRunning()); + if(online_fully_valid) + { + NetworkService service = GetConfig().GetAccountNetworkService(account.GetPersistentId()); + m_active_service->SetSelection(static_cast<int>(service)); + // set the config option here for the selected service + // this will guarantee that it's actually written to settings.xml + // allowing us to eventually get rid of the legacy option in the (far) future + GetConfig().SetAccountSelectedService(account.GetPersistentId(), service); + } + else + { + m_active_service->SetSelection(0); // force offline + } + wxString tmp = _("Network service"); + tmp.append(" ("); + tmp.append(wxString::FromUTF8(boost::nowide::narrow(account.GetMiiName()))); + tmp.append(")"); + m_active_service->SetLabel(tmp); + // refresh pane size m_account_grid->InvalidateBestSize(); //m_account_grid->GetParent()->FitInside(); @@ -1663,9 +1703,8 @@ void GeneralSettings2::ApplyConfig() break; } } - - m_online_enabled->SetValue(config.account.online_enabled); - m_active_service->SetSelection(config.account.active_service); + m_active_service->SetSelection((int)config.GetAccountNetworkService(ActiveSettings::GetPersistentId())); + UpdateAccountInformation(); // debug @@ -1673,20 +1712,6 @@ void GeneralSettings2::ApplyConfig() m_gdb_port->SetValue(config.gdb_port.GetValue()); } -void GeneralSettings2::OnOnlineEnable(wxCommandEvent& event) -{ - event.Skip(); - if (!m_online_enabled->GetValue()) - return; - - // show warning if player enables online mode - const auto result = wxMessageBox(_("Please be aware that online mode lets you connect to OFFICIAL servers and therefore there is a risk of getting banned.\nOnly proceed if you are willing to risk losing online access with your Wii U and/or NNID."), - _("Warning"), wxYES_NO | wxCENTRE | wxICON_EXCLAMATION, this); - if (result == wxNO) - m_online_enabled->SetValue(false); -} - - void GeneralSettings2::OnAudioAPISelected(wxCommandEvent& event) { IAudioAPI::AudioAPI api; @@ -1952,6 +1977,9 @@ void GeneralSettings2::OnActiveAccountChanged(wxCommandEvent& event) void GeneralSettings2::OnAccountServiceChanged(wxCommandEvent& event) { + auto& config = GetConfig(); + uint32 peristentId = GetSelectedAccountPersistentId(); + config.SetAccountSelectedService(peristentId, static_cast<NetworkService>(m_active_service->GetSelection())); UpdateAccountInformation(); } @@ -2005,12 +2033,12 @@ void GeneralSettings2::OnShowOnlineValidator(wxCommandEvent& event) err << _("The following error(s) have been found:") << '\n'; if (validator.otp == OnlineValidator::FileState::Missing) - err << _("otp.bin missing in Cemu root directory") << '\n'; + err << _("otp.bin missing in Cemu directory") << '\n'; else if(validator.otp == OnlineValidator::FileState::Corrupted) err << _("otp.bin is invalid") << '\n'; if (validator.seeprom == OnlineValidator::FileState::Missing) - err << _("seeprom.bin missing in Cemu root directory") << '\n'; + err << _("seeprom.bin missing in Cemu directory") << '\n'; else if(validator.seeprom == OnlineValidator::FileState::Corrupted) err << _("seeprom.bin is invalid") << '\n'; @@ -2045,9 +2073,10 @@ void GeneralSettings2::OnShowOnlineValidator(wxCommandEvent& event) wxString GeneralSettings2::GetOnlineAccountErrorMessage(OnlineAccountError error) { - switch (error) { + switch (error) + { case OnlineAccountError::kNoAccountId: - return _("AccountId missing (The account is not connected to a NNID)"); + return _("AccountId missing (The account is not connected to a NNID/PNID)"); case OnlineAccountError::kNoPasswordCached: return _("IsPasswordCacheEnabled is set to false (The remember password option on your Wii U must be enabled for this account before dumping it)"); case OnlineAccountError::kPasswordCacheEmpty: diff --git a/src/gui/GeneralSettings2.h b/src/gui/GeneralSettings2.h index 2846af38..b34c9222 100644 --- a/src/gui/GeneralSettings2.h +++ b/src/gui/GeneralSettings2.h @@ -71,7 +71,6 @@ private: wxButton* m_create_account, * m_delete_account; wxChoice* m_active_account; wxRadioBox* m_active_service; - wxCheckBox* m_online_enabled; wxCollapsiblePane* m_account_information; wxPropertyGrid* m_account_grid; wxBitmapButton* m_validate_online; @@ -99,10 +98,11 @@ private: void OnMLCPathSelect(wxCommandEvent& event); void OnMLCPathChar(wxKeyEvent& event); void OnShowOnlineValidator(wxCommandEvent& event); - void OnOnlineEnable(wxCommandEvent& event); void OnAccountServiceChanged(wxCommandEvent& event); static wxString GetOnlineAccountErrorMessage(OnlineAccountError error); + uint32 GetSelectedAccountPersistentId(); + // updates cemu audio devices void UpdateAudioDevice(); // refreshes audio device list for dropdown diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index c34c5477..097d506e 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -948,38 +948,6 @@ void MainWindow::OnAccountSelect(wxCommandEvent& event) g_config.Save(); } -//void MainWindow::OnConsoleRegion(wxCommandEvent& event) -//{ -// switch (event.GetId()) -// { -// case MAINFRAME_MENU_ID_OPTIONS_REGION_AUTO: -// GetConfig().console_region = ConsoleRegion::Auto; -// break; -// case MAINFRAME_MENU_ID_OPTIONS_REGION_JPN: -// GetConfig().console_region = ConsoleRegion::JPN; -// break; -// case MAINFRAME_MENU_ID_OPTIONS_REGION_USA: -// GetConfig().console_region = ConsoleRegion::USA; -// break; -// case MAINFRAME_MENU_ID_OPTIONS_REGION_EUR: -// GetConfig().console_region = ConsoleRegion::EUR; -// break; -// case MAINFRAME_MENU_ID_OPTIONS_REGION_CHN: -// GetConfig().console_region = ConsoleRegion::CHN; -// break; -// case MAINFRAME_MENU_ID_OPTIONS_REGION_KOR: -// GetConfig().console_region = ConsoleRegion::KOR; -// break; -// case MAINFRAME_MENU_ID_OPTIONS_REGION_TWN: -// GetConfig().console_region = ConsoleRegion::TWN; -// break; -// default: -// cemu_assert_debug(false); -// } -// -// g_config.Save(); -//} - void MainWindow::OnConsoleLanguage(wxCommandEvent& event) { switch (event.GetId()) From 10d553e1c9ba0b669ee8d4543741eea14725ce24 Mon Sep 17 00:00:00 2001 From: GaryOderNichts <12049776+GaryOderNichts@users.noreply.github.com> Date: Tue, 7 May 2024 11:56:28 +0200 Subject: [PATCH 107/299] zlib125: Implement `deflateInit_` (#1194) --- src/Cafe/OS/libs/zlib125/zlib125.cpp | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/Cafe/OS/libs/zlib125/zlib125.cpp b/src/Cafe/OS/libs/zlib125/zlib125.cpp index 25df6a9d..aec6e8c3 100644 --- a/src/Cafe/OS/libs/zlib125/zlib125.cpp +++ b/src/Cafe/OS/libs/zlib125/zlib125.cpp @@ -213,6 +213,32 @@ void zlib125Export_inflateReset2(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, r); } +void zlib125Export_deflateInit_(PPCInterpreter_t* hCPU) +{ + ppcDefineParamStructPtr(zstream, z_stream_ppc2, 0); + ppcDefineParamS32(level, 1); + ppcDefineParamStr(version, 2); + ppcDefineParamS32(streamsize, 3); + + z_stream hzs; + zlib125_setupHostZStream(zstream, &hzs, false); + + // setup internal memory allocator if requested + if (zstream->zalloc == nullptr) + zstream->zalloc = PPCInterpreter_makeCallableExportDepr(zlib125_zcalloc); + if (zstream->zfree == nullptr) + zstream->zfree = PPCInterpreter_makeCallableExportDepr(zlib125_zcfree); + + if (streamsize != sizeof(z_stream_ppc2)) + assert_dbg(); + + sint32 r = deflateInit_(&hzs, level, version, sizeof(z_stream)); + + zlib125_setupUpdateZStream(&hzs, zstream); + + osLib_returnFromFunction(hCPU, r); +} + void zlib125Export_deflateInit2_(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(zstream, z_stream_ppc2, 0); @@ -345,6 +371,7 @@ namespace zlib osLib_addFunction("zlib125", "inflateReset", zlib125Export_inflateReset); osLib_addFunction("zlib125", "inflateReset2", zlib125Export_inflateReset2); + osLib_addFunction("zlib125", "deflateInit_", zlib125Export_deflateInit_); osLib_addFunction("zlib125", "deflateInit2_", zlib125Export_deflateInit2_); osLib_addFunction("zlib125", "deflateBound", zlib125Export_deflateBound); osLib_addFunction("zlib125", "deflate", zlib125Export_deflate); From b2a6cccc89fd42b63bb718c8e9743cb52fca9008 Mon Sep 17 00:00:00 2001 From: GaryOderNichts <12049776+GaryOderNichts@users.noreply.github.com> Date: Thu, 9 May 2024 12:12:34 +0200 Subject: [PATCH 108/299] nn_act: Implement GetTransferableId (#1197) --- src/Cafe/OS/libs/nn_act/nn_act.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/Cafe/OS/libs/nn_act/nn_act.cpp b/src/Cafe/OS/libs/nn_act/nn_act.cpp index f490ff19..f9e74355 100644 --- a/src/Cafe/OS/libs/nn_act/nn_act.cpp +++ b/src/Cafe/OS/libs/nn_act/nn_act.cpp @@ -308,6 +308,22 @@ void nnActExport_GetPrincipalIdEx(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, 0); // ResultSuccess } +void nnActExport_GetTransferableId(PPCInterpreter_t* hCPU) +{ + ppcDefineParamU32(unique, 0); + + cemuLog_logDebug(LogType::Force, "nn_act.GetTransferableId(0x{:08x})", hCPU->gpr[3]); + + uint64 transferableId; + uint32 r = nn::act::GetTransferableIdEx(&transferableId, unique, iosu::act::ACT_SLOT_CURRENT); + if (NN_RESULT_IS_FAILURE(r)) + { + transferableId = 0; + } + + osLib_returnFromFunction64(hCPU, _swapEndianU64(transferableId)); +} + void nnActExport_GetTransferableIdEx(PPCInterpreter_t* hCPU) { ppcDefineParamStructPtr(transferableId, uint64, 0); @@ -691,6 +707,7 @@ void nnAct_load() osLib_addFunction("nn_act", "GetPrincipalId__Q2_2nn3actFv", nnActExport_GetPrincipalId); osLib_addFunction("nn_act", "GetPrincipalIdEx__Q2_2nn3actFPUiUc", nnActExport_GetPrincipalIdEx); // transferable id + osLib_addFunction("nn_act", "GetTransferableId__Q2_2nn3actFUi", nnActExport_GetTransferableId); osLib_addFunction("nn_act", "GetTransferableIdEx__Q2_2nn3actFPULUiUc", nnActExport_GetTransferableIdEx); // persistent id osLib_addFunction("nn_act", "GetPersistentId__Q2_2nn3actFv", nnActExport_GetPersistentId); From 97d8cf4ba330ed671a9b40d8aaab740d7bcbeffb Mon Sep 17 00:00:00 2001 From: Xphalnos <164882787+Xphalnos@users.noreply.github.com> Date: Fri, 10 May 2024 09:32:06 +0200 Subject: [PATCH 109/299] vcpkg: Update libraries (#1198) --- dependencies/vcpkg | 2 +- vcpkg.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dependencies/vcpkg b/dependencies/vcpkg index 53bef899..cbf4a664 160000 --- a/dependencies/vcpkg +++ b/dependencies/vcpkg @@ -1 +1 @@ -Subproject commit 53bef8994c541b6561884a8395ea35715ece75db +Subproject commit cbf4a6641528cee6f172328984576f51698de726 diff --git a/vcpkg.json b/vcpkg.json index 48742b4a..b27a7095 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,7 +1,7 @@ { "name": "cemu", "version-string": "1.0", - "builtin-baseline": "53bef8994c541b6561884a8395ea35715ece75db", + "builtin-baseline": "cbf4a6641528cee6f172328984576f51698de726", "dependencies": [ "pugixml", "zlib", From cf41c3b136ab7272e6801991d081c9d2c69c7143 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Fri, 10 May 2024 09:33:32 +0200 Subject: [PATCH 110/299] CI: Use submodule commit of vcpkg --- .github/workflows/build.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d188b4a1..a2342c27 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,7 +28,6 @@ jobs: run: | cd dependencies/vcpkg git fetch --unshallow - git checkout 431eb6bda0950874c8d4ed929cc66e15d8aae46f - name: Setup release mode parameters (for deploy) if: ${{ inputs.deploymode == 'release' }} @@ -138,7 +137,6 @@ jobs: run: | cd dependencies/vcpkg git fetch --unshallow - git checkout 431eb6bda0950874c8d4ed929cc66e15d8aae46f - name: Setup release mode parameters (for deploy) if: ${{ inputs.deploymode == 'release' }} @@ -218,7 +216,6 @@ jobs: run: | cd dependencies/vcpkg git fetch --unshallow - git pull --all - name: Setup release mode parameters (for deploy) if: ${{ inputs.deploymode == 'release' }} From 13b90874f9934f0a79a9ab2b9c4e1288ed2e6764 Mon Sep 17 00:00:00 2001 From: splatoon1enjoyer <131005903+splatoon1enjoyer@users.noreply.github.com> Date: Mon, 13 May 2024 14:52:25 +0000 Subject: [PATCH 111/299] Fix commas edge case in strings when parsing an assembly line (#1201) --- src/Cemu/PPCAssembler/ppcAssembler.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Cemu/PPCAssembler/ppcAssembler.cpp b/src/Cemu/PPCAssembler/ppcAssembler.cpp index 5bab7b8b..df20b21d 100644 --- a/src/Cemu/PPCAssembler/ppcAssembler.cpp +++ b/src/Cemu/PPCAssembler/ppcAssembler.cpp @@ -2418,6 +2418,9 @@ bool ppcAssembler_assembleSingleInstruction(char const* text, PPCAssemblerInOut* _ppcAssembler_translateAlias(instructionName); // parse operands internalInfo.listOperandStr.clear(); + + bool isInString = false; + while (currentPtr < endPtr) { currentPtr++; @@ -2425,7 +2428,10 @@ bool ppcAssembler_assembleSingleInstruction(char const* text, PPCAssemblerInOut* // find end of operand while (currentPtr < endPtr) { - if (*currentPtr == ',') + if (*currentPtr == '"') + isInString=!isInString; + + if (*currentPtr == ',' && !isInString) break; currentPtr++; } From 84e78088fb2d3d25797032fe963967aa2d1b5af0 Mon Sep 17 00:00:00 2001 From: GaryOderNichts <12049776+GaryOderNichts@users.noreply.github.com> Date: Sat, 4 May 2024 14:46:12 +0200 Subject: [PATCH 112/299] PPCCoreCallback: Add support for stack args if GPR limit is reached --- src/Cafe/HW/Espresso/PPCCallback.h | 37 ++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/src/Cafe/HW/Espresso/PPCCallback.h b/src/Cafe/HW/Espresso/PPCCallback.h index 19fcd4d1..3d5393b1 100644 --- a/src/Cafe/HW/Espresso/PPCCallback.h +++ b/src/Cafe/HW/Espresso/PPCCallback.h @@ -5,8 +5,28 @@ struct PPCCoreCallbackData_t { sint32 gprCount = 0; sint32 floatCount = 0; + sint32 stackCount = 0; }; +inline void _PPCCoreCallback_writeGPRArg(PPCCoreCallbackData_t& data, PPCInterpreter_t* hCPU, uint32 value) +{ + if (data.gprCount < 8) + { + hCPU->gpr[3 + data.gprCount] = value; + data.gprCount++; + } + else + { + uint32 stackOffset = 8 + data.stackCount * 4; + + // PPCCore_executeCallbackInternal does -16*4 to save the current stack area + stackOffset -= 16 * 4; + + memory_writeU32(hCPU->gpr[1] + stackOffset, value); + data.stackCount++; + } +} + // callback functions inline uint32 PPCCoreCallback(MPTR function, const PPCCoreCallbackData_t& data) { @@ -16,23 +36,21 @@ inline uint32 PPCCoreCallback(MPTR function, const PPCCoreCallbackData_t& data) template <typename T, typename... TArgs> uint32 PPCCoreCallback(MPTR function, PPCCoreCallbackData_t& data, T currentArg, TArgs... args) { - cemu_assert_debug(data.gprCount <= 8); - cemu_assert_debug(data.floatCount <= 8); + // TODO float arguments on stack + cemu_assert_debug(data.floatCount < 8); + PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); if constexpr (std::is_pointer_v<T>) { - hCPU->gpr[3 + data.gprCount] = MEMPTR(currentArg).GetMPTR(); - data.gprCount++; + _PPCCoreCallback_writeGPRArg(data, hCPU, MEMPTR(currentArg).GetMPTR()); } else if constexpr (std::is_base_of_v<MEMPTRBase, std::remove_reference_t<T>>) { - hCPU->gpr[3 + data.gprCount] = currentArg.GetMPTR(); - data.gprCount++; + _PPCCoreCallback_writeGPRArg(data, hCPU, currentArg.GetMPTR()); } else if constexpr (std::is_reference_v<T>) { - hCPU->gpr[3 + data.gprCount] = MEMPTR(¤tArg).GetMPTR(); - data.gprCount++; + _PPCCoreCallback_writeGPRArg(data, hCPU, MEMPTR(¤tArg).GetMPTR()); } else if constexpr(std::is_enum_v<T>) { @@ -53,8 +71,7 @@ uint32 PPCCoreCallback(MPTR function, PPCCoreCallbackData_t& data, T currentArg, } else { - hCPU->gpr[3 + data.gprCount] = (uint32)currentArg; - data.gprCount++; + _PPCCoreCallback_writeGPRArg(data, hCPU, (uint32)currentArg); } return PPCCoreCallback(function, data, args...); From 1c6b209692953bcf5a958499ba3ebba0e24d5c6f Mon Sep 17 00:00:00 2001 From: GaryOderNichts <12049776+GaryOderNichts@users.noreply.github.com> Date: Sat, 4 May 2024 14:49:23 +0200 Subject: [PATCH 113/299] Add initial ntag and nfc implementation --- src/Cafe/CMakeLists.txt | 14 + src/Cafe/CafeSystem.cpp | 6 + src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp | 406 +++++++++++++++++ src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h | 31 ++ src/Cafe/OS/libs/nfc/TLV.cpp | 139 ++++++ src/Cafe/OS/libs/nfc/TLV.h | 37 ++ src/Cafe/OS/libs/nfc/TagV0.cpp | 301 +++++++++++++ src/Cafe/OS/libs/nfc/TagV0.h | 39 ++ src/Cafe/OS/libs/nfc/ndef.cpp | 277 ++++++++++++ src/Cafe/OS/libs/nfc/ndef.h | 88 ++++ src/Cafe/OS/libs/nfc/nfc.cpp | 596 +++++++++++++++++++++++++ src/Cafe/OS/libs/nfc/nfc.h | 62 +++ src/Cafe/OS/libs/nfc/stream.cpp | 201 +++++++++ src/Cafe/OS/libs/nfc/stream.h | 139 ++++++ src/Cafe/OS/libs/nn_nfp/nn_nfp.cpp | 83 ++-- src/Cafe/OS/libs/nn_nfp/nn_nfp.h | 8 +- src/Cafe/OS/libs/ntag/ntag.cpp | 438 ++++++++++++++++++ src/Cafe/OS/libs/ntag/ntag.h | 94 ++++ src/Cemu/Logging/CemuLogging.cpp | 2 + src/Cemu/Logging/CemuLogging.h | 3 + src/gui/MainWindow.cpp | 10 +- 21 files changed, 2927 insertions(+), 47 deletions(-) create mode 100644 src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp create mode 100644 src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h create mode 100644 src/Cafe/OS/libs/nfc/TLV.cpp create mode 100644 src/Cafe/OS/libs/nfc/TLV.h create mode 100644 src/Cafe/OS/libs/nfc/TagV0.cpp create mode 100644 src/Cafe/OS/libs/nfc/TagV0.h create mode 100644 src/Cafe/OS/libs/nfc/ndef.cpp create mode 100644 src/Cafe/OS/libs/nfc/ndef.h create mode 100644 src/Cafe/OS/libs/nfc/nfc.cpp create mode 100644 src/Cafe/OS/libs/nfc/nfc.h create mode 100644 src/Cafe/OS/libs/nfc/stream.cpp create mode 100644 src/Cafe/OS/libs/nfc/stream.h create mode 100644 src/Cafe/OS/libs/ntag/ntag.cpp create mode 100644 src/Cafe/OS/libs/ntag/ntag.h diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index 851854fc..b5090dcf 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -218,6 +218,8 @@ add_library(CemuCafe HW/SI/SI.cpp HW/SI/si.h HW/VI/VI.cpp + IOSU/ccr_nfc/iosu_ccr_nfc.cpp + IOSU/ccr_nfc/iosu_ccr_nfc.h IOSU/fsa/fsa_types.h IOSU/fsa/iosu_fsa.cpp IOSU/fsa/iosu_fsa.h @@ -378,6 +380,16 @@ add_library(CemuCafe OS/libs/h264_avc/parser/H264Parser.h OS/libs/mic/mic.cpp OS/libs/mic/mic.h + OS/libs/nfc/ndef.cpp + OS/libs/nfc/ndef.h + OS/libs/nfc/nfc.cpp + OS/libs/nfc/nfc.h + OS/libs/nfc/stream.cpp + OS/libs/nfc/stream.h + OS/libs/nfc/TagV0.cpp + OS/libs/nfc/TagV0.h + OS/libs/nfc/TLV.cpp + OS/libs/nfc/TLV.h OS/libs/nlibcurl/nlibcurl.cpp OS/libs/nlibcurl/nlibcurlDebug.hpp OS/libs/nlibcurl/nlibcurl.h @@ -453,6 +465,8 @@ add_library(CemuCafe OS/libs/nsyskbd/nsyskbd.h OS/libs/nsysnet/nsysnet.cpp OS/libs/nsysnet/nsysnet.h + OS/libs/ntag/ntag.cpp + OS/libs/ntag/ntag.h OS/libs/padscore/padscore.cpp OS/libs/padscore/padscore.h OS/libs/proc_ui/proc_ui.cpp diff --git a/src/Cafe/CafeSystem.cpp b/src/Cafe/CafeSystem.cpp index 3c62a686..958a5a57 100644 --- a/src/Cafe/CafeSystem.cpp +++ b/src/Cafe/CafeSystem.cpp @@ -35,6 +35,7 @@ #include "Cafe/IOSU/legacy/iosu_boss.h" #include "Cafe/IOSU/legacy/iosu_nim.h" #include "Cafe/IOSU/PDM/iosu_pdm.h" +#include "Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h" // IOSU initializer functions #include "Cafe/IOSU/kernel/iosu_kernel.h" @@ -51,6 +52,8 @@ #include "Cafe/OS/libs/gx2/GX2.h" #include "Cafe/OS/libs/gx2/GX2_Misc.h" #include "Cafe/OS/libs/mic/mic.h" +#include "Cafe/OS/libs/nfc/nfc.h" +#include "Cafe/OS/libs/ntag/ntag.h" #include "Cafe/OS/libs/nn_aoc/nn_aoc.h" #include "Cafe/OS/libs/nn_pdm/nn_pdm.h" #include "Cafe/OS/libs/nn_cmpt/nn_cmpt.h" @@ -533,6 +536,7 @@ namespace CafeSystem iosu::acp::GetModule(), iosu::fpd::GetModule(), iosu::pdm::GetModule(), + iosu::ccr_nfc::GetModule(), }; // initialize all subsystems which are persistent and don't depend on a game running @@ -587,6 +591,8 @@ namespace CafeSystem H264::Initialize(); snd_core::Initialize(); mic::Initialize(); + nfc::Initialize(); + ntag::Initialize(); // init hardware register interfaces HW_SI::Initialize(); } diff --git a/src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp b/src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp new file mode 100644 index 00000000..ff8ba2b1 --- /dev/null +++ b/src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp @@ -0,0 +1,406 @@ +#include "iosu_ccr_nfc.h" +#include "Cafe/IOSU/kernel/iosu_kernel.h" +#include "util/crypto/aes128.h" +#include <openssl/evp.h> +#include <openssl/hmac.h> + +namespace iosu +{ + namespace ccr_nfc + { + IOSMsgQueueId sCCRNFCMsgQueue; + SysAllocator<iosu::kernel::IOSMessage, 0x20> sCCRNFCMsgQueueMsgBuffer; + std::thread sCCRNFCThread; + + constexpr uint8 sNfcKey[] = { 0xC1, 0x2B, 0x07, 0x10, 0xD7, 0x2C, 0xEB, 0x5D, 0x43, 0x49, 0xB7, 0x43, 0xE3, 0xCA, 0xD2, 0x24 }; + constexpr uint8 sNfcKeyIV[] = { 0x4F, 0xD3, 0x9A, 0x6E, 0x79, 0xFC, 0xEA, 0xAD, 0x99, 0x90, 0x4D, 0xB8, 0xEE, 0x38, 0xE9, 0xDB }; + + constexpr uint8 sUnfixedInfosMagicBytes[] = { 0x00, 0x00, 0xDB, 0x4B, 0x9E, 0x3F, 0x45, 0x27, 0x8F, 0x39, 0x7E, 0xFF, 0x9B, 0x4F, 0xB9, 0x93 }; + constexpr uint8 sLockedSecretMagicBytes[] = { 0xFD, 0xC8, 0xA0, 0x76, 0x94, 0xB8, 0x9E, 0x4C, 0x47, 0xD3, 0x7D, 0xE8, 0xCE, 0x5C, 0x74, 0xC1 }; + constexpr uint8 sUnfixedInfosString[] = { 0x75, 0x6E, 0x66, 0x69, 0x78, 0x65, 0x64, 0x20, 0x69, 0x6E, 0x66, 0x6F, 0x73, 0x00, 0x00, 0x00 }; + constexpr uint8 sLockedSecretString[] = { 0x6C, 0x6F, 0x63, 0x6B, 0x65, 0x64, 0x20, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x00, 0x00, 0x00 }; + + constexpr uint8 sLockedSecretHmacKey[] = { 0x7F, 0x75, 0x2D, 0x28, 0x73, 0xA2, 0x00, 0x17, 0xFE, 0xF8, 0x5C, 0x05, 0x75, 0x90, 0x4B, 0x6D }; + constexpr uint8 sUnfixedInfosHmacKey[] = { 0x1D, 0x16, 0x4B, 0x37, 0x5B, 0x72, 0xA5, 0x57, 0x28, 0xB9, 0x1D, 0x64, 0xB6, 0xA3, 0xC2, 0x05 }; + + uint8 sLockedSecretInternalKey[0x10]; + uint8 sLockedSecretInternalNonce[0x10]; + uint8 sLockedSecretInternalHmacKey[0x10]; + + uint8 sUnfixedInfosInternalKey[0x10]; + uint8 sUnfixedInfosInternalNonce[0x10]; + uint8 sUnfixedInfosInternalHmacKey[0x10]; + + sint32 __CCRNFCValidateCryptData(CCRNFCCryptData* data, uint32 size, bool validateOffsets) + { + if (!data) + { + return CCR_NFC_ERROR; + } + + if (size != sizeof(CCRNFCCryptData)) + { + return CCR_NFC_ERROR; + } + + if (!validateOffsets) + { + return 0; + } + + // Make sure all offsets are within bounds + if (data->version == 0) + { + if (data->unfixedInfosHmacOffset < 0x1C9 && data->unfixedInfosOffset < 0x1C9 && + data->lockedSecretHmacOffset < 0x1C9 && data->lockedSecretOffset < 0x1C9 && + data->lockedSecretSize < 0x1C9 && data->unfixedInfosSize < 0x1C9) + { + return 0; + } + } + else if (data->version == 2) + { + if (data->unfixedInfosHmacOffset < 0x21D && data->unfixedInfosOffset < 0x21D && + data->lockedSecretHmacOffset < 0x21D && data->lockedSecretOffset < 0x21D && + data->lockedSecretSize < 0x21D && data->unfixedInfosSize < 0x21D) + { + return 0; + } + } + + return CCR_NFC_ERROR; + } + + sint32 CCRNFCAESCTRCrypt(const uint8* key, const void* ivNonce, const void* inData, uint32_t inSize, void* outData, uint32_t outSize) + { + uint8_t tmpIv[0x10]; + memcpy(tmpIv, ivNonce, sizeof(tmpIv)); + + memcpy(outData, inData, inSize); + AES128CTR_transform((uint8*)outData, outSize, (uint8*)key, tmpIv); + return 0; + } + + sint32 __CCRNFCGenerateKey(const uint8* hmacKey, uint32 hmacKeySize, const uint8* name, uint32_t nameSize, const uint8* inData, uint32_t inSize, uint8* outData, uint32_t outSize) + { + if (nameSize != 0xe || outSize != 0x40) + { + return CCR_NFC_ERROR; + } + + // Create a buffer containing 2 counter bytes, the key name, and the key data + uint8_t buffer[0x50]; + buffer[0] = 0; + buffer[1] = 0; + memcpy(buffer + 2, name, nameSize); + memcpy(buffer + nameSize + 2, inData, inSize); + + uint16_t counter = 0; + while (outSize > 0) + { + // Set counter bytes and increment counter + buffer[0] = (counter >> 8) & 0xFF; + buffer[1] = counter & 0xFF; + counter++; + + uint32 dataSize = outSize; + if (!HMAC(EVP_sha256(), hmacKey, hmacKeySize, buffer, sizeof(buffer), outData, &dataSize)) + { + return CCR_NFC_ERROR; + } + + outSize -= 0x20; + outData += 0x20; + } + + return 0; + } + + sint32 __CCRNFCGenerateInternalKeys(const CCRNFCCryptData* in, const uint8* keyGenSalt) + { + uint8_t lockedSecretBuffer[0x40] = { 0 }; + uint8_t unfixedInfosBuffer[0x40] = { 0 }; + uint8_t outBuffer[0x40] = { 0 }; + + // Fill the locked secret buffer + memcpy(lockedSecretBuffer, sLockedSecretMagicBytes, sizeof(sLockedSecretMagicBytes)); + if (in->version == 0) + { + // For Version 0 this is the 16-byte Format Info + memcpy(lockedSecretBuffer + 0x10, in->data + in->uuidOffset, 0x10); + } + else if (in->version == 2) + { + // For Version 2 this is 2 times the 7-byte UID + 1 check byte + memcpy(lockedSecretBuffer + 0x10, in->data + in->uuidOffset, 8); + memcpy(lockedSecretBuffer + 0x18, in->data + in->uuidOffset, 8); + } + else + { + return CCR_NFC_ERROR; + } + // Append key generation salt + memcpy(lockedSecretBuffer + 0x20, keyGenSalt, 0x20); + + // Generate the key output + sint32 res = __CCRNFCGenerateKey(sLockedSecretHmacKey, sizeof(sLockedSecretHmacKey), sLockedSecretString, 0xe, lockedSecretBuffer, sizeof(lockedSecretBuffer), outBuffer, sizeof(outBuffer)); + if (res != 0) + { + return res; + } + + // Unpack the key buffer + memcpy(sLockedSecretInternalKey, outBuffer, 0x10); + memcpy(sLockedSecretInternalNonce, outBuffer + 0x10, 0x10); + memcpy(sLockedSecretInternalHmacKey, outBuffer + 0x20, 0x10); + + // Fill the unfixed infos buffer + memcpy(unfixedInfosBuffer, in->data + in->seedOffset, 2); + memcpy(unfixedInfosBuffer + 2, sUnfixedInfosMagicBytes + 2, 0xe); + if (in->version == 0) + { + // For Version 0 this is the 16-byte Format Info + memcpy(unfixedInfosBuffer + 0x10, in->data + in->uuidOffset, 0x10); + } + else if (in->version == 2) + { + // For Version 2 this is 2 times the 7-byte UID + 1 check byte + memcpy(unfixedInfosBuffer + 0x10, in->data + in->uuidOffset, 8); + memcpy(unfixedInfosBuffer + 0x18, in->data + in->uuidOffset, 8); + } + else + { + return CCR_NFC_ERROR; + } + // Append key generation salt + memcpy(unfixedInfosBuffer + 0x20, keyGenSalt, 0x20); + + // Generate the key output + res = __CCRNFCGenerateKey(sUnfixedInfosHmacKey, sizeof(sUnfixedInfosHmacKey), sUnfixedInfosString, 0xe, unfixedInfosBuffer, sizeof(unfixedInfosBuffer), outBuffer, sizeof(outBuffer)); + if (res != 0) + { + return res; + } + + // Unpack the key buffer + memcpy(sUnfixedInfosInternalKey, outBuffer, 0x10); + memcpy(sUnfixedInfosInternalNonce, outBuffer + 0x10, 0x10); + memcpy(sUnfixedInfosInternalHmacKey, outBuffer + 0x20, 0x10); + + return 0; + } + + sint32 __CCRNFCCryptData(const CCRNFCCryptData* in, CCRNFCCryptData* out, bool decrypt) + { + // Decrypt key generation salt + uint8_t keyGenSalt[0x20]; + sint32 res = CCRNFCAESCTRCrypt(sNfcKey, sNfcKeyIV, in->data + in->keyGenSaltOffset, 0x20, keyGenSalt, sizeof(keyGenSalt)); + if (res != 0) + { + return res; + } + + // Prepare internal keys + res = __CCRNFCGenerateInternalKeys(in, keyGenSalt); + if (res != 0) + { + return res; + } + + if (decrypt) + { + // Only version 0 tags have an encrypted locked secret area + if (in->version == 0) + { + res = CCRNFCAESCTRCrypt(sLockedSecretInternalKey, sLockedSecretInternalNonce, in->data + in->lockedSecretOffset, in->lockedSecretSize, out->data + in->lockedSecretOffset, in->lockedSecretSize); + if (res != 0) + { + return res; + } + } + + // Decrypt unfxied infos + res = CCRNFCAESCTRCrypt(sUnfixedInfosInternalKey, sUnfixedInfosInternalNonce, in->data + in->unfixedInfosOffset, in->unfixedInfosSize, out->data + in->unfixedInfosOffset, in->unfixedInfosSize); + if (res != 0) + { + return res; + } + + // Verify HMACs + uint8_t hmacBuffer[0x20]; + uint32 hmacLen = sizeof(hmacBuffer); + + if (!HMAC(EVP_sha256(), sLockedSecretInternalHmacKey, sizeof(sLockedSecretInternalHmacKey), out->data + in->lockedSecretHmacOffset + 0x20, (in->dataSize - in->lockedSecretHmacOffset) - 0x20, hmacBuffer, &hmacLen)) + { + return CCR_NFC_ERROR; + } + + if (memcmp(in->data + in->lockedSecretHmacOffset, hmacBuffer, 0x20) != 0) + { + return CCR_NFC_INVALID_LOCKED_SECRET; + } + + if (in->version == 0) + { + hmacLen = sizeof(hmacBuffer); + res = HMAC(EVP_sha256(), sUnfixedInfosInternalHmacKey, sizeof(sUnfixedInfosInternalHmacKey), out->data + in->unfixedInfosHmacOffset + 0x20, (in->dataSize - in->unfixedInfosHmacOffset) - 0x20, hmacBuffer, &hmacLen) ? 0 : CCR_NFC_ERROR; + } + else + { + hmacLen = sizeof(hmacBuffer); + res = HMAC(EVP_sha256(), sUnfixedInfosInternalHmacKey, sizeof(sUnfixedInfosInternalHmacKey), out->data + in->unfixedInfosHmacOffset + 0x21, (in->dataSize - in->unfixedInfosHmacOffset) - 0x21, hmacBuffer, &hmacLen) ? 0 : CCR_NFC_ERROR; + } + + if (memcmp(in->data + in->unfixedInfosHmacOffset, hmacBuffer, 0x20) != 0) + { + return CCR_NFC_INVALID_UNFIXED_INFOS; + } + } + else + { + uint8_t hmacBuffer[0x20]; + uint32 hmacLen = sizeof(hmacBuffer); + + if (!HMAC(EVP_sha256(), sLockedSecretInternalHmacKey, sizeof(sLockedSecretInternalHmacKey), out->data + in->lockedSecretHmacOffset + 0x20, (in->dataSize - in->lockedSecretHmacOffset) - 0x20, hmacBuffer, &hmacLen)) + { + return CCR_NFC_ERROR; + } + + if (memcmp(in->data + in->lockedSecretHmacOffset, hmacBuffer, 0x20) != 0) + { + return CCR_NFC_INVALID_LOCKED_SECRET; + } + + // Only version 0 tags have an encrypted locked secret area + if (in->version == 0) + { + uint32 hmacLen = 0x20; + if (!HMAC(EVP_sha256(), sUnfixedInfosInternalHmacKey, sizeof(sUnfixedInfosInternalHmacKey), out->data + in->unfixedInfosHmacOffset + 0x20, (in->dataSize - in->unfixedInfosHmacOffset) - 0x20, out->data + in->unfixedInfosHmacOffset, &hmacLen)) + { + return CCR_NFC_ERROR; + } + + res = CCRNFCAESCTRCrypt(sLockedSecretInternalKey, sLockedSecretInternalNonce, in->data + in->lockedSecretOffset, in->lockedSecretSize, out->data + in->lockedSecretOffset, in->lockedSecretSize); + if (res != 0) + { + return res; + } + } + else + { + uint32 hmacLen = 0x20; + if (!HMAC(EVP_sha256(), sUnfixedInfosInternalHmacKey, sizeof(sUnfixedInfosInternalHmacKey), out->data + in->unfixedInfosHmacOffset + 0x21, (in->dataSize - in->unfixedInfosHmacOffset) - 0x21, out->data + in->unfixedInfosHmacOffset, &hmacLen)) + { + return CCR_NFC_ERROR; + } + } + + res = CCRNFCAESCTRCrypt(sUnfixedInfosInternalKey, sUnfixedInfosInternalNonce, in->data + in->unfixedInfosOffset, in->unfixedInfosSize, out->data + in->unfixedInfosOffset, in->unfixedInfosSize); + if (res != 0) + { + return res; + } + } + + return res; + } + + void CCRNFCThread() + { + iosu::kernel::IOSMessage msg; + while (true) + { + IOS_ERROR error = iosu::kernel::IOS_ReceiveMessage(sCCRNFCMsgQueue, &msg, 0); + cemu_assert(!IOS_ResultIsError(error)); + + // Check for system exit + if (msg == 0xf00dd00d) + { + return; + } + + IPCCommandBody* cmd = MEMPTR<IPCCommandBody>(msg).GetPtr(); + if (cmd->cmdId == IPCCommandId::IOS_OPEN) + { + iosu::kernel::IOS_ResourceReply(cmd, IOS_ERROR_OK); + } + else if (cmd->cmdId == IPCCommandId::IOS_CLOSE) + { + iosu::kernel::IOS_ResourceReply(cmd, IOS_ERROR_OK); + } + else if (cmd->cmdId == IPCCommandId::IOS_IOCTL) + { + sint32 result; + uint32 requestId = cmd->args[0]; + void* ptrIn = MEMPTR<void>(cmd->args[1]); + uint32 sizeIn = cmd->args[2]; + void* ptrOut = MEMPTR<void>(cmd->args[3]); + uint32 sizeOut = cmd->args[4]; + + if ((result = __CCRNFCValidateCryptData(static_cast<CCRNFCCryptData*>(ptrIn), sizeIn, true)) == 0 && + (result = __CCRNFCValidateCryptData(static_cast<CCRNFCCryptData*>(ptrOut), sizeOut, false)) == 0) + { + // Initialize outData with inData + memcpy(ptrOut, ptrIn, sizeIn); + + switch (requestId) + { + case 1: // encrypt + result = __CCRNFCCryptData(static_cast<CCRNFCCryptData*>(ptrIn), static_cast<CCRNFCCryptData*>(ptrOut), false); + break; + case 2: // decrypt + result = __CCRNFCCryptData(static_cast<CCRNFCCryptData*>(ptrIn), static_cast<CCRNFCCryptData*>(ptrOut), true); + break; + default: + cemuLog_log(LogType::Force, "/dev/ccr_nfc: Unsupported IOCTL requestId"); + cemu_assert_suspicious(); + result = IOS_ERROR_INVALID; + break; + } + } + + iosu::kernel::IOS_ResourceReply(cmd, static_cast<IOS_ERROR>(result)); + } + else + { + cemuLog_log(LogType::Force, "/dev/ccr_nfc: Unsupported IPC cmdId"); + cemu_assert_suspicious(); + iosu::kernel::IOS_ResourceReply(cmd, IOS_ERROR_INVALID); + } + } + } + + class : public ::IOSUModule + { + void SystemLaunch() override + { + sCCRNFCMsgQueue = iosu::kernel::IOS_CreateMessageQueue(sCCRNFCMsgQueueMsgBuffer.GetPtr(), sCCRNFCMsgQueueMsgBuffer.GetCount()); + cemu_assert(!IOS_ResultIsError(static_cast<IOS_ERROR>(sCCRNFCMsgQueue))); + + IOS_ERROR error = iosu::kernel::IOS_RegisterResourceManager("/dev/ccr_nfc", sCCRNFCMsgQueue); + cemu_assert(!IOS_ResultIsError(error)); + + sCCRNFCThread = std::thread(CCRNFCThread); + } + + void SystemExit() override + { + if (sCCRNFCMsgQueue < 0) + { + return; + } + + iosu::kernel::IOS_SendMessage(sCCRNFCMsgQueue, 0xf00dd00d, 0); + sCCRNFCThread.join(); + + iosu::kernel::IOS_DestroyMessageQueue(sCCRNFCMsgQueue); + sCCRNFCMsgQueue = -1; + } + } sIOSUModuleCCRNFC; + + IOSUModule* GetModule() + { + return &sIOSUModuleCCRNFC; + } + } +} diff --git a/src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h b/src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h new file mode 100644 index 00000000..ae99d645 --- /dev/null +++ b/src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h @@ -0,0 +1,31 @@ +#pragma once +#include "Cafe/IOSU/iosu_types_common.h" + +#define CCR_NFC_ERROR (-0x2F001E) +#define CCR_NFC_INVALID_LOCKED_SECRET (-0x2F0029) +#define CCR_NFC_INVALID_UNFIXED_INFOS (-0x2F002A) + +namespace iosu +{ + namespace ccr_nfc + { + struct CCRNFCCryptData + { + uint32 version; + uint32 dataSize; + uint32 seedOffset; + uint32 keyGenSaltOffset; + uint32 uuidOffset; + uint32 unfixedInfosOffset; + uint32 unfixedInfosSize; + uint32 lockedSecretOffset; + uint32 lockedSecretSize; + uint32 unfixedInfosHmacOffset; + uint32 lockedSecretHmacOffset; + uint8 data[540]; + }; + static_assert(sizeof(CCRNFCCryptData) == 0x248); + + IOSUModule* GetModule(); + } +} diff --git a/src/Cafe/OS/libs/nfc/TLV.cpp b/src/Cafe/OS/libs/nfc/TLV.cpp new file mode 100644 index 00000000..99536428 --- /dev/null +++ b/src/Cafe/OS/libs/nfc/TLV.cpp @@ -0,0 +1,139 @@ +#include "TLV.h" +#include "stream.h" + +#include <cassert> + +TLV::TLV() +{ +} + +TLV::TLV(Tag tag, std::vector<std::byte> value) + : mTag(tag), mValue(std::move(value)) +{ +} + +TLV::~TLV() +{ +} + +std::vector<TLV> TLV::FromBytes(const std::span<std::byte>& data) +{ + bool hasTerminator = false; + std::vector<TLV> tlvs; + SpanStream stream(data, std::endian::big); + + while (stream.GetRemaining() > 0 && !hasTerminator) + { + // Read the tag + uint8_t byte; + stream >> byte; + Tag tag = static_cast<Tag>(byte); + + switch (tag) + { + case TLV::TAG_NULL: + // Don't need to do anything for NULL tags + break; + + case TLV::TAG_TERMINATOR: + tlvs.emplace_back(tag, std::vector<std::byte>{}); + hasTerminator = true; + break; + + default: + { + // Read the length + uint16_t length; + stream >> byte; + length = byte; + + // If the length is 0xff, 2 bytes with length follow + if (length == 0xff) { + stream >> length; + } + + std::vector<std::byte> value; + value.resize(length); + stream.Read(value); + + tlvs.emplace_back(tag, value); + break; + } + } + + if (stream.GetError() != Stream::ERROR_OK) + { + cemuLog_log(LogType::Force, "Error: TLV parsing read past end of stream"); + // Clear tlvs to prevent further havoc while parsing ndef data + tlvs.clear(); + break; + } + } + + // This seems to be okay, at least NTAGs don't add a terminator tag + // if (!hasTerminator) + // { + // cemuLog_log(LogType::Force, "Warning: TLV parsing reached end of stream without terminator tag"); + // } + + return tlvs; +} + +std::vector<std::byte> TLV::ToBytes() const +{ + std::vector<std::byte> bytes; + VectorStream stream(bytes, std::endian::big); + + // Write tag + stream << std::uint8_t(mTag); + + switch (mTag) + { + case TLV::TAG_NULL: + case TLV::TAG_TERMINATOR: + // Nothing to do here + break; + + default: + { + // Write length (decide if as a 8-bit or 16-bit value) + if (mValue.size() >= 0xff) + { + stream << std::uint8_t(0xff); + stream << std::uint16_t(mValue.size()); + } + else + { + stream << std::uint8_t(mValue.size()); + } + + // Write value + stream.Write(mValue); + } + } + + return bytes; +} + +TLV::Tag TLV::GetTag() const +{ + return mTag; +} + +const std::vector<std::byte>& TLV::GetValue() const +{ + return mValue; +} + +void TLV::SetTag(Tag tag) +{ + mTag = tag; +} + +void TLV::SetValue(const std::span<const std::byte>& value) +{ + // Can only write max 16-bit lengths into TLV + cemu_assert(value.size() < 0x10000); + + mValue.assign(value.begin(), value.end()); +} diff --git a/src/Cafe/OS/libs/nfc/TLV.h b/src/Cafe/OS/libs/nfc/TLV.h new file mode 100644 index 00000000..f582128f --- /dev/null +++ b/src/Cafe/OS/libs/nfc/TLV.h @@ -0,0 +1,37 @@ +#pragma once + +#include <cstdint> +#include <span> +#include <vector> + +class TLV +{ +public: + enum Tag + { + TAG_NULL = 0x00, + TAG_LOCK_CTRL = 0x01, + TAG_MEM_CTRL = 0x02, + TAG_NDEF = 0x03, + TAG_PROPRIETARY = 0xFD, + TAG_TERMINATOR = 0xFE, + }; + +public: + TLV(); + TLV(Tag tag, std::vector<std::byte> value); + virtual ~TLV(); + + static std::vector<TLV> FromBytes(const std::span<std::byte>& data); + std::vector<std::byte> ToBytes() const; + + Tag GetTag() const; + const std::vector<std::byte>& GetValue() const; + + void SetTag(Tag tag); + void SetValue(const std::span<const std::byte>& value); + +private: + Tag mTag; + std::vector<std::byte> mValue; +}; diff --git a/src/Cafe/OS/libs/nfc/TagV0.cpp b/src/Cafe/OS/libs/nfc/TagV0.cpp new file mode 100644 index 00000000..8b5a8143 --- /dev/null +++ b/src/Cafe/OS/libs/nfc/TagV0.cpp @@ -0,0 +1,301 @@ +#include "TagV0.h" +#include "TLV.h" + +#include <algorithm> + +namespace +{ + +constexpr std::size_t kTagSize = 512u; +constexpr std::size_t kMaxBlockCount = kTagSize / sizeof(TagV0::Block); + +constexpr std::uint8_t kLockbyteBlock0 = 0xe; +constexpr std::uint8_t kLockbytesStart0 = 0x0; +constexpr std::uint8_t kLockbytesEnd0 = 0x2; +constexpr std::uint8_t kLockbyteBlock1 = 0xf; +constexpr std::uint8_t kLockbytesStart1 = 0x2; +constexpr std::uint8_t kLockbytesEnd1 = 0x8; + +constexpr std::uint8_t kNDEFMagicNumber = 0xe1; + +// These blocks are not part of the locked area +constexpr bool IsBlockLockedOrReserved(std::uint8_t blockIdx) +{ + // Block 0 is the UID + if (blockIdx == 0x0) + { + return true; + } + + // Block 0xd is reserved + if (blockIdx == 0xd) + { + return true; + } + + // Block 0xe and 0xf contains lock / reserved bytes + if (blockIdx == 0xe || blockIdx == 0xf) + { + return true; + } + + return false; +} + +} // namespace + +TagV0::TagV0() +{ +} + +TagV0::~TagV0() +{ +} + +std::shared_ptr<TagV0> TagV0::FromBytes(const std::span<const std::byte>& data) +{ + // Version 0 tags need at least 512 bytes + if (data.size() != kTagSize) + { + cemuLog_log(LogType::Force, "Error: Version 0 tags should be {} bytes in size", kTagSize); + return {}; + } + + std::shared_ptr<TagV0> tag = std::make_shared<TagV0>(); + + // Parse the locked area before continuing + if (!tag->ParseLockedArea(data)) + { + cemuLog_log(LogType::Force, "Error: Failed to parse locked area"); + return {}; + } + + // Now that the locked area is known, parse the data area + std::vector<std::byte> dataArea; + if (!tag->ParseDataArea(data, dataArea)) + { + cemuLog_log(LogType::Force, "Error: Failed to parse data area"); + return {}; + } + + // The first few bytes in the dataArea make up the capability container + std::copy_n(dataArea.begin(), tag->mCapabilityContainer.size(), std::as_writable_bytes(std::span(tag->mCapabilityContainer)).begin()); + if (!tag->ValidateCapabilityContainer()) + { + cemuLog_log(LogType::Force, "Error: Failed to validate capability container"); + return {}; + } + + // The rest of the dataArea contains the TLVs + tag->mTLVs = TLV::FromBytes(std::span(dataArea).subspan(tag->mCapabilityContainer.size())); + if (tag->mTLVs.empty()) + { + cemuLog_log(LogType::Force, "Error: Tag contains no TLVs"); + return {}; + } + + // Look for the NDEF tlv + tag->mNdefTlvIdx = static_cast<size_t>(-1); + for (std::size_t i = 0; i < tag->mTLVs.size(); i++) + { + if (tag->mTLVs[i].GetTag() == TLV::TAG_NDEF) + { + tag->mNdefTlvIdx = i; + break; + } + } + + if (tag->mNdefTlvIdx == static_cast<size_t>(-1)) + { + cemuLog_log(LogType::Force, "Error: Tag contains no NDEF TLV"); + return {}; + } + + // Append locked data + for (const auto& [key, value] : tag->mLockedBlocks) + { + tag->mLockedArea.insert(tag->mLockedArea.end(), value.begin(), value.end()); + } + + return tag; +} + +std::vector<std::byte> TagV0::ToBytes() const +{ + std::vector<std::byte> bytes(kTagSize); + + // Insert locked or reserved blocks + for (const auto& [key, value] : mLockedOrReservedBlocks) + { + std::copy(value.begin(), value.end(), bytes.begin() + key * sizeof(Block)); + } + + // Insert locked area + auto lockedDataIterator = mLockedArea.begin(); + for (const auto& [key, value] : mLockedBlocks) + { + std::copy_n(lockedDataIterator, sizeof(Block), bytes.begin() + key * sizeof(Block)); + lockedDataIterator += sizeof(Block); + } + + // Pack the dataArea into a linear buffer + std::vector<std::byte> dataArea; + const auto ccBytes = std::as_bytes(std::span(mCapabilityContainer)); + dataArea.insert(dataArea.end(), ccBytes.begin(), ccBytes.end()); + for (const TLV& tlv : mTLVs) + { + const auto tlvBytes = tlv.ToBytes(); + dataArea.insert(dataArea.end(), tlvBytes.begin(), tlvBytes.end()); + } + + // Make sure the dataArea is block size aligned + dataArea.resize((dataArea.size() + (sizeof(Block)-1)) & ~(sizeof(Block)-1)); + + // The rest will be the data area + auto dataIterator = dataArea.begin(); + for (std::uint8_t currentBlock = 0; currentBlock < kMaxBlockCount; currentBlock++) + { + // All blocks which aren't locked make up the dataArea + if (!IsBlockLocked(currentBlock)) + { + std::copy_n(dataIterator, sizeof(Block), bytes.begin() + currentBlock * sizeof(Block)); + dataIterator += sizeof(Block); + } + } + + return bytes; +} + +const TagV0::Block& TagV0::GetUIDBlock() const +{ + return mLockedOrReservedBlocks.at(0); +} + +const std::vector<std::byte>& TagV0::GetNDEFData() const +{ + return mTLVs[mNdefTlvIdx].GetValue(); +} + +const std::vector<std::byte>& TagV0::GetLockedArea() const +{ + return mLockedArea; +} + +void TagV0::SetNDEFData(const std::span<const std::byte>& data) +{ + // Update the ndef value + mTLVs[mNdefTlvIdx].SetValue(data); +} + +bool TagV0::ParseLockedArea(const std::span<const std::byte>& data) +{ + std::uint8_t currentBlock = 0; + + // Start by parsing the first set of lock bytes + for (std::uint8_t i = kLockbytesStart0; i < kLockbytesEnd0; i++) + { + std::uint8_t lockByte = std::uint8_t(data[kLockbyteBlock0 * sizeof(Block) + i]); + + // Iterate over the individual bits in the lock byte + for (std::uint8_t j = 0; j < 8; j++) + { + // Is block locked? + if (lockByte & (1u << j)) + { + Block blk; + std::copy_n(data.begin() + currentBlock * sizeof(Block), sizeof(Block), blk.begin()); + + // The lock bytes themselves are not part of the locked area + if (!IsBlockLockedOrReserved(currentBlock)) + { + mLockedBlocks.emplace(currentBlock, blk); + } + else + { + mLockedOrReservedBlocks.emplace(currentBlock, blk); + } + } + + currentBlock++; + } + } + + // Parse the second set of lock bytes + for (std::uint8_t i = kLockbytesStart1; i < kLockbytesEnd1; i++) { + std::uint8_t lockByte = std::uint8_t(data[kLockbyteBlock1 * sizeof(Block) + i]); + + // Iterate over the individual bits in the lock byte + for (std::uint8_t j = 0; j < 8; j++) + { + // Is block locked? + if (lockByte & (1u << j)) + { + Block blk; + std::copy_n(data.begin() + currentBlock * sizeof(Block), sizeof(Block), blk.begin()); + + // The lock bytes themselves are not part of the locked area + if (!IsBlockLockedOrReserved(currentBlock)) + { + mLockedBlocks.emplace(currentBlock, blk); + } + else + { + mLockedOrReservedBlocks.emplace(currentBlock, blk); + } + } + + currentBlock++; + } + } + + return true; +} + +bool TagV0::IsBlockLocked(std::uint8_t blockIdx) const +{ + return mLockedBlocks.contains(blockIdx) || IsBlockLockedOrReserved(blockIdx); +} + +bool TagV0::ParseDataArea(const std::span<const std::byte>& data, std::vector<std::byte>& dataArea) +{ + for (std::uint8_t currentBlock = 0; currentBlock < kMaxBlockCount; currentBlock++) + { + // All blocks which aren't locked make up the dataArea + if (!IsBlockLocked(currentBlock)) + { + auto blockOffset = data.begin() + sizeof(Block) * currentBlock; + dataArea.insert(dataArea.end(), blockOffset, blockOffset + sizeof(Block)); + } + } + + return true; +} + +bool TagV0::ValidateCapabilityContainer() +{ + // NDEF Magic Number + std::uint8_t nmn = mCapabilityContainer[0]; + if (nmn != kNDEFMagicNumber) + { + cemuLog_log(LogType::Force, "Error: CC: Invalid NDEF Magic Number"); + return false; + } + + // Version Number + std::uint8_t vno = mCapabilityContainer[1]; + if (vno >> 4 != 1) + { + cemuLog_log(LogType::Force, "Error: CC: Invalid Version Number"); + return false; + } + + // Tag memory size + std::uint8_t tms = mCapabilityContainer[2]; + if (8u * (tms + 1) < kTagSize) + { + cemuLog_log(LogType::Force, "Error: CC: Incomplete tag memory size"); + return false; + } + + return true; +} diff --git a/src/Cafe/OS/libs/nfc/TagV0.h b/src/Cafe/OS/libs/nfc/TagV0.h new file mode 100644 index 00000000..1d0e88d7 --- /dev/null +++ b/src/Cafe/OS/libs/nfc/TagV0.h @@ -0,0 +1,39 @@ +#pragma once + +#include <memory> +#include <span> +#include <map> + +#include "TLV.h" + +class TagV0 +{ +public: + using Block = std::array<std::byte, 0x8>; + +public: + TagV0(); + virtual ~TagV0(); + + static std::shared_ptr<TagV0> FromBytes(const std::span<const std::byte>& data); + std::vector<std::byte> ToBytes() const; + + const Block& GetUIDBlock() const; + const std::vector<std::byte>& GetNDEFData() const; + const std::vector<std::byte>& GetLockedArea() const; + + void SetNDEFData(const std::span<const std::byte>& data); + +private: + bool ParseLockedArea(const std::span<const std::byte>& data); + bool IsBlockLocked(std::uint8_t blockIdx) const; + bool ParseDataArea(const std::span<const std::byte>& data, std::vector<std::byte>& dataArea); + bool ValidateCapabilityContainer(); + + std::map<std::uint8_t, Block> mLockedOrReservedBlocks; + std::map<std::uint8_t, Block> mLockedBlocks; + std::array<std::uint8_t, 0x4> mCapabilityContainer; + std::vector<TLV> mTLVs; + std::size_t mNdefTlvIdx; + std::vector<std::byte> mLockedArea; +}; diff --git a/src/Cafe/OS/libs/nfc/ndef.cpp b/src/Cafe/OS/libs/nfc/ndef.cpp new file mode 100644 index 00000000..f8d87fb8 --- /dev/null +++ b/src/Cafe/OS/libs/nfc/ndef.cpp @@ -0,0 +1,277 @@ +#include "ndef.h" + +#include <cassert> + +namespace ndef +{ + + Record::Record() + { + } + + Record::~Record() + { + } + + std::optional<Record> Record::FromStream(Stream& stream) + { + Record rec; + + // Read record header + uint8_t recHdr; + stream >> recHdr; + rec.mFlags = recHdr & ~NDEF_TNF_MASK; + rec.mTNF = static_cast<TypeNameFormat>(recHdr & NDEF_TNF_MASK); + + // Type length + uint8_t typeLen; + stream >> typeLen; + + // Payload length; + uint32_t payloadLen; + if (recHdr & NDEF_SR) + { + uint8_t len; + stream >> len; + payloadLen = len; + } + else + { + stream >> payloadLen; + } + + // Some sane limits for the payload size + if (payloadLen > 2 * 1024 * 1024) + { + return {}; + } + + // ID length + uint8_t idLen = 0; + if (recHdr & NDEF_IL) + { + stream >> idLen; + } + + // Make sure we didn't read past the end of the stream yet + if (stream.GetError() != Stream::ERROR_OK) + { + return {}; + } + + // Type + rec.mType.resize(typeLen); + stream.Read(rec.mType); + + // ID + rec.mID.resize(idLen); + stream.Read(rec.mID); + + // Payload + rec.mPayload.resize(payloadLen); + stream.Read(rec.mPayload); + + // Make sure we didn't read past the end of the stream again + if (stream.GetError() != Stream::ERROR_OK) + { + return {}; + } + + return rec; + } + + std::vector<std::byte> Record::ToBytes(uint8_t flags) const + { + std::vector<std::byte> bytes; + VectorStream stream(bytes, std::endian::big); + + // Combine flags (clear message begin and end flags) + std::uint8_t finalFlags = mFlags & ~(NDEF_MB | NDEF_ME); + finalFlags |= flags; + + // Write flags + tnf + stream << std::uint8_t(finalFlags | std::uint8_t(mTNF)); + + // Type length + stream << std::uint8_t(mType.size()); + + // Payload length + if (IsShort()) + { + stream << std::uint8_t(mPayload.size()); + } + else + { + stream << std::uint32_t(mPayload.size()); + } + + // ID length + if (mFlags & NDEF_IL) + { + stream << std::uint8_t(mID.size()); + } + + // Type + stream.Write(mType); + + // ID + stream.Write(mID); + + // Payload + stream.Write(mPayload); + + return bytes; + } + + Record::TypeNameFormat Record::GetTNF() const + { + return mTNF; + } + + const std::vector<std::byte>& Record::GetID() const + { + return mID; + } + + const std::vector<std::byte>& Record::GetType() const + { + return mType; + } + + const std::vector<std::byte>& Record::GetPayload() const + { + return mPayload; + } + + void Record::SetTNF(TypeNameFormat tnf) + { + mTNF = tnf; + } + + void Record::SetID(const std::span<const std::byte>& id) + { + cemu_assert(id.size() < 0x100); + + if (id.size() > 0) + { + mFlags |= NDEF_IL; + } + else + { + mFlags &= ~NDEF_IL; + } + + mID.assign(id.begin(), id.end()); + } + + void Record::SetType(const std::span<const std::byte>& type) + { + cemu_assert(type.size() < 0x100); + + mType.assign(type.begin(), type.end()); + } + + void Record::SetPayload(const std::span<const std::byte>& payload) + { + // Update short record flag + if (payload.size() < 0xff) + { + mFlags |= NDEF_SR; + } + else + { + mFlags &= ~NDEF_SR; + } + + mPayload.assign(payload.begin(), payload.end()); + } + + bool Record::IsLast() const + { + return mFlags & NDEF_ME; + } + + bool Record::IsShort() const + { + return mFlags & NDEF_SR; + } + + Message::Message() + { + } + + Message::~Message() + { + } + + std::optional<Message> Message::FromBytes(const std::span<const std::byte>& data) + { + Message msg; + SpanStream stream(data, std::endian::big); + + while (stream.GetRemaining() > 0) + { + std::optional<Record> rec = Record::FromStream(stream); + if (!rec) + { + cemuLog_log(LogType::Force, "Warning: Failed to parse NDEF Record #{}." + "Ignoring the remaining {} bytes in NDEF message", msg.mRecords.size(), stream.GetRemaining()); + break; + } + + msg.mRecords.emplace_back(*rec); + + if ((*rec).IsLast() && stream.GetRemaining() > 0) + { + cemuLog_log(LogType::Force, "Warning: Ignoring {} bytes in NDEF message", stream.GetRemaining()); + break; + } + } + + if (msg.mRecords.empty()) + { + return {}; + } + + if (!msg.mRecords.back().IsLast()) + { + cemuLog_log(LogType::Force, "Error: NDEF message missing end record"); + return {}; + } + + return msg; + } + + std::vector<std::byte> Message::ToBytes() const + { + std::vector<std::byte> bytes; + + for (std::size_t i = 0; i < mRecords.size(); i++) + { + std::uint8_t flags = 0; + + // Add message begin flag to first record + if (i == 0) + { + flags |= Record::NDEF_MB; + } + + // Add message end flag to last record + if (i == mRecords.size() - 1) + { + flags |= Record::NDEF_ME; + } + + std::vector<std::byte> recordBytes = mRecords[i].ToBytes(flags); + bytes.insert(bytes.end(), recordBytes.begin(), recordBytes.end()); + } + + return bytes; + } + + void Message::append(const Record& r) + { + mRecords.push_back(r); + } + +} // namespace ndef diff --git a/src/Cafe/OS/libs/nfc/ndef.h b/src/Cafe/OS/libs/nfc/ndef.h new file mode 100644 index 00000000..b5f38b17 --- /dev/null +++ b/src/Cafe/OS/libs/nfc/ndef.h @@ -0,0 +1,88 @@ +#pragma once + +#include <span> +#include <vector> +#include <optional> + +#include "stream.h" + +namespace ndef +{ + + class Record + { + public: + enum HeaderFlag + { + NDEF_IL = 0x08, + NDEF_SR = 0x10, + NDEF_CF = 0x20, + NDEF_ME = 0x40, + NDEF_MB = 0x80, + NDEF_TNF_MASK = 0x07, + }; + + enum TypeNameFormat + { + NDEF_TNF_EMPTY = 0, + NDEF_TNF_WKT = 1, + NDEF_TNF_MEDIA = 2, + NDEF_TNF_URI = 3, + NDEF_TNF_EXT = 4, + NDEF_TNF_UNKNOWN = 5, + NDEF_TNF_UNCHANGED = 6, + NDEF_TNF_RESERVED = 7, + }; + + public: + Record(); + virtual ~Record(); + + static std::optional<Record> FromStream(Stream& stream); + std::vector<std::byte> ToBytes(uint8_t flags = 0) const; + + TypeNameFormat GetTNF() const; + const std::vector<std::byte>& GetID() const; + const std::vector<std::byte>& GetType() const; + const std::vector<std::byte>& GetPayload() const; + + void SetTNF(TypeNameFormat tnf); + void SetID(const std::span<const std::byte>& id); + void SetType(const std::span<const std::byte>& type); + void SetPayload(const std::span<const std::byte>& payload); + + bool IsLast() const; + bool IsShort() const; + + private: + uint8_t mFlags; + TypeNameFormat mTNF; + std::vector<std::byte> mID; + std::vector<std::byte> mType; + std::vector<std::byte> mPayload; + }; + + class Message + { + public: + Message(); + virtual ~Message(); + + static std::optional<Message> FromBytes(const std::span<const std::byte>& data); + std::vector<std::byte> ToBytes() const; + + Record& operator[](int i) { return mRecords[i]; } + const Record& operator[](int i) const { return mRecords[i]; } + + void append(const Record& r); + + auto begin() { return mRecords.begin(); } + auto end() { return mRecords.end(); } + auto begin() const { return mRecords.begin(); } + auto end() const { return mRecords.end(); } + + private: + std::vector<Record> mRecords; + }; + +} // namespace ndef diff --git a/src/Cafe/OS/libs/nfc/nfc.cpp b/src/Cafe/OS/libs/nfc/nfc.cpp new file mode 100644 index 00000000..21e9e91b --- /dev/null +++ b/src/Cafe/OS/libs/nfc/nfc.cpp @@ -0,0 +1,596 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/RPL/rpl.h" +#include "Cafe/OS/libs/nfc/nfc.h" +#include "Cafe/OS/libs/nn_nfp/nn_nfp.h" +#include "Common/FileStream.h" + +#include "TagV0.h" +#include "ndef.h" + +// TODO move errors to header and allow ntag to convert them + +#define NFC_MODE_INVALID -1 +#define NFC_MODE_IDLE 0 +#define NFC_MODE_ACTIVE 1 + +#define NFC_STATE_UNINITIALIZED 0x0 +#define NFC_STATE_INITIALIZED 0x1 +#define NFC_STATE_IDLE 0x2 +#define NFC_STATE_READ 0x3 +#define NFC_STATE_WRITE 0x4 +#define NFC_STATE_ABORT 0x5 +#define NFC_STATE_FORMAT 0x6 +#define NFC_STATE_SET_READ_ONLY 0x7 +#define NFC_STATE_TAG_PRESENT 0x8 +#define NFC_STATE_DETECT 0x9 +#define NFC_STATE_RAW 0xA + +#define NFC_STATUS_COMMAND_COMPLETE 0x1 +#define NFC_STATUS_READY 0x2 +#define NFC_STATUS_HAS_TAG 0x4 + +namespace nfc +{ + struct NFCContext + { + bool isInitialized; + uint32 state; + sint32 mode; + bool hasTag; + + uint32 nfcStatus; + std::chrono::time_point<std::chrono::system_clock> discoveryTimeout; + + MPTR tagDetectCallback; + void* tagDetectContext; + MPTR abortCallback; + void* abortContext; + MPTR rawCallback; + void* rawContext; + MPTR readCallback; + void* readContext; + MPTR writeCallback; + void* writeContext; + MPTR getTagInfoCallback; + + SysAllocator<NFCTagInfo> tagInfo; + + fs::path tagPath; + std::shared_ptr<TagV0> tag; + + ndef::Message writeMessage; + }; + NFCContext gNFCContexts[2]; + + sint32 NFCInit(uint32 chan) + { + return NFCInitEx(chan, 0); + } + + void __NFCClearContext(NFCContext* context) + { + context->isInitialized = false; + context->state = NFC_STATE_UNINITIALIZED; + context->mode = NFC_MODE_IDLE; + context->hasTag = false; + + context->nfcStatus = NFC_STATUS_READY; + context->discoveryTimeout = {}; + + context->tagDetectCallback = MPTR_NULL; + context->tagDetectContext = nullptr; + context->abortCallback = MPTR_NULL; + context->abortContext = nullptr; + context->rawCallback = MPTR_NULL; + context->rawContext = nullptr; + context->readCallback = MPTR_NULL; + context->readContext = nullptr; + context->writeCallback = MPTR_NULL; + context->writeContext = nullptr; + + context->tagPath = ""; + context->tag = {}; + } + + sint32 NFCInitEx(uint32 chan, uint32 powerMode) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + __NFCClearContext(ctx); + ctx->isInitialized = true; + ctx->state = NFC_STATE_INITIALIZED; + + return 0; + } + + sint32 NFCShutdown(uint32 chan) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + __NFCClearContext(ctx); + + return 0; + } + + bool NFCIsInit(uint32 chan) + { + cemu_assert(chan < 2); + + return gNFCContexts[chan].isInitialized; + } + + void __NFCHandleRead(uint32 chan) + { + NFCContext* ctx = &gNFCContexts[chan]; + + ctx->state = NFC_STATE_IDLE; + + sint32 result; + StackAllocator<NFCUid> uid; + bool readOnly = false; + uint32 dataSize = 0; + StackAllocator<uint8_t, 0x200> data; + uint32 lockedDataSize = 0; + StackAllocator<uint8_t, 0x200> lockedData; + + if (ctx->tag) + { + // Try to parse ndef message + auto ndefMsg = ndef::Message::FromBytes(ctx->tag->GetNDEFData()); + if (ndefMsg) + { + // Look for the unknown TNF which contains the data we care about + for (const auto& rec : *ndefMsg) + { + if (rec.GetTNF() == ndef::Record::NDEF_TNF_UNKNOWN) { + dataSize = rec.GetPayload().size(); + cemu_assert(dataSize < 0x200); + memcpy(data.GetPointer(), rec.GetPayload().data(), dataSize); + break; + } + } + + if (dataSize) + { + // Get locked data + lockedDataSize = ctx->tag->GetLockedArea().size(); + memcpy(lockedData.GetPointer(), ctx->tag->GetLockedArea().data(), lockedDataSize); + + // Fill in uid + memcpy(uid.GetPointer(), ctx->tag->GetUIDBlock().data(), sizeof(NFCUid)); + + result = 0; + } + else + { + result = -0xBFE; + } + } + else + { + result = -0xBFE; + } + + // Clear tag status after read + // TODO this is not really nice here + ctx->nfcStatus &= ~NFC_STATUS_HAS_TAG; + ctx->tag = {}; + } + else + { + result = -0x1DD; + } + + PPCCoreCallback(ctx->readCallback, chan, result, uid.GetPointer(), readOnly, dataSize, data.GetPointer(), lockedDataSize, lockedData.GetPointer(), ctx->readContext); + } + + void __NFCHandleWrite(uint32 chan) + { + NFCContext* ctx = &gNFCContexts[chan]; + + ctx->state = NFC_STATE_IDLE; + + // TODO write to file + + PPCCoreCallback(ctx->writeCallback, chan, 0, ctx->writeContext); + } + + void __NFCHandleAbort(uint32 chan) + { + NFCContext* ctx = &gNFCContexts[chan]; + + ctx->state = NFC_STATE_IDLE; + + PPCCoreCallback(ctx->abortCallback, chan, 0, ctx->abortContext); + } + + void __NFCHandleRaw(uint32 chan) + { + NFCContext* ctx = &gNFCContexts[chan]; + + ctx->state = NFC_STATE_IDLE; + + sint32 result; + if (ctx->nfcStatus & NFC_STATUS_HAS_TAG) + { + result = 0; + } + else + { + result = -0x9DD; + } + + // We don't actually send any commands/responses + uint32 responseSize = 0; + void* responseData = nullptr; + + PPCCoreCallback(ctx->rawCallback, chan, result, responseSize, responseData, ctx->rawContext); + } + + void NFCProc(uint32 chan) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + if (!ctx->isInitialized) + { + return; + } + + // Check if the detect callback should be called + if (ctx->nfcStatus & NFC_STATUS_HAS_TAG) + { + if (!ctx->hasTag && ctx->state > NFC_STATE_IDLE && ctx->state != NFC_STATE_ABORT) + { + if (ctx->tagDetectCallback) + { + PPCCoreCallback(ctx->tagDetectCallback, chan, true, ctx->tagDetectContext); + } + + ctx->hasTag = true; + } + } + else + { + if (ctx->hasTag && ctx->state == NFC_STATE_IDLE) + { + if (ctx->tagDetectCallback) + { + PPCCoreCallback(ctx->tagDetectCallback, chan, false, ctx->tagDetectContext); + } + + ctx->hasTag = false; + } + } + + switch (ctx->state) + { + case NFC_STATE_INITIALIZED: + ctx->state = NFC_STATE_IDLE; + break; + case NFC_STATE_IDLE: + break; + case NFC_STATE_READ: + // Do we have a tag or did the timeout expire? + if ((ctx->nfcStatus & NFC_STATUS_HAS_TAG) || ctx->discoveryTimeout < std::chrono::system_clock::now()) + { + __NFCHandleRead(chan); + } + break; + case NFC_STATE_WRITE: + __NFCHandleWrite(chan); + break; + case NFC_STATE_ABORT: + __NFCHandleAbort(chan); + break; + case NFC_STATE_RAW: + // Do we have a tag or did the timeout expire? + if ((ctx->nfcStatus & NFC_STATUS_HAS_TAG) || ctx->discoveryTimeout < std::chrono::system_clock::now()) + { + __NFCHandleRaw(chan); + } + break; + } + } + + sint32 NFCGetMode(uint32 chan) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + if (!NFCIsInit(chan) || ctx->state == NFC_STATE_UNINITIALIZED) + { + return NFC_MODE_INVALID; + } + + return ctx->mode; + } + + sint32 NFCSetMode(uint32 chan, sint32 mode) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + if (!NFCIsInit(chan)) + { + return -0xAE0; + } + + if (ctx->state == NFC_STATE_UNINITIALIZED) + { + return -0xADF; + } + + ctx->mode = mode; + + return 0; + } + + void NFCSetTagDetectCallback(uint32 chan, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + ctx->tagDetectCallback = callback; + ctx->tagDetectContext = context; + } + + sint32 NFCAbort(uint32 chan, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + if (!NFCIsInit(chan)) + { + return -0x6E0; + } + + if (ctx->state <= NFC_STATE_IDLE) + { + return -0x6DF; + } + + ctx->state = NFC_STATE_ABORT; + ctx->abortCallback = callback; + ctx->abortContext = context; + + return 0; + } + + void __NFCGetTagInfoCallback(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(chan, 0); + ppcDefineParamS32(error, 1); + ppcDefineParamU32(responseSize, 2); + ppcDefineParamPtr(responseData, void, 3); + ppcDefineParamPtr(context, void, 4); + + NFCContext* ctx = &gNFCContexts[chan]; + + // TODO convert error + error = error; + if (error == 0 && ctx->tag) + { + // this is usually parsed from response data + ctx->tagInfo->uidSize = sizeof(NFCUid); + memcpy(ctx->tagInfo->uid, ctx->tag->GetUIDBlock().data(), ctx->tagInfo->uidSize); + ctx->tagInfo->technology = NFC_TECHNOLOGY_A; + ctx->tagInfo->protocol = NFC_PROTOCOL_T1T; + } + + PPCCoreCallback(ctx->getTagInfoCallback, chan, error, ctx->tagInfo.GetPtr(), context); + osLib_returnFromFunction(hCPU, 0); + } + + sint32 NFCGetTagInfo(uint32 chan, uint32 discoveryTimeout, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + // Forward this request to nn_nfp, if the title initialized it + // TODO integrate nn_nfp/ntag/nfc + if (nnNfp_isInitialized()) + { + return nn::nfp::NFCGetTagInfo(chan, discoveryTimeout, callback, context); + } + + NFCContext* ctx = &gNFCContexts[chan]; + + ctx->getTagInfoCallback = callback; + + sint32 result = NFCSendRawData(chan, true, discoveryTimeout, 1000U, 0, 0, nullptr, RPLLoader_MakePPCCallable(__NFCGetTagInfoCallback), context); + return result; // TODO convert result + } + + sint32 NFCSendRawData(uint32 chan, bool startDiscovery, uint32 discoveryTimeout, uint32 commandTimeout, uint32 commandSize, uint32 responseSize, void* commandData, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + if (!NFCIsInit(chan)) + { + return -0x9E0; + } + + // Only allow discovery + if (!startDiscovery) + { + return -0x9DC; + } + + if (NFCGetMode(chan) == NFC_MODE_ACTIVE && NFCSetMode(chan, NFC_MODE_IDLE) < 0) + { + return -0x9DC; + } + + if (ctx->state != NFC_STATE_IDLE) + { + return -0x9DF; + } + + ctx->state = NFC_STATE_RAW; + ctx->rawCallback = callback; + ctx->rawContext = context; + + // If the discoveryTimeout is 0, no timeout + if (discoveryTimeout == 0) + { + ctx->discoveryTimeout = std::chrono::time_point<std::chrono::system_clock>::max(); + } + else + { + ctx->discoveryTimeout = std::chrono::system_clock::now() + std::chrono::milliseconds(discoveryTimeout); + } + + return 0; + } + + sint32 NFCRead(uint32 chan, uint32 discoveryTimeout, NFCUid* uid, NFCUid* uidMask, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + if (!NFCIsInit(chan)) + { + return -0x1E0; + } + + if (NFCGetMode(chan) == NFC_MODE_ACTIVE && NFCSetMode(chan, NFC_MODE_IDLE) < 0) + { + return -0x1DC; + } + + if (ctx->state != NFC_STATE_IDLE) + { + return -0x1DF; + } + + cemuLog_log(LogType::NFC, "starting read"); + + ctx->state = NFC_STATE_READ; + ctx->readCallback = callback; + ctx->readContext = context; + + // If the discoveryTimeout is 0, no timeout + if (discoveryTimeout == 0) + { + ctx->discoveryTimeout = std::chrono::time_point<std::chrono::system_clock>::max(); + } + else + { + ctx->discoveryTimeout = std::chrono::system_clock::now() + std::chrono::milliseconds(discoveryTimeout); + } + + // TODO uid filter? + + return 0; + } + + sint32 NFCWrite(uint32 chan, uint32 discoveryTimeout, NFCUid* uid, NFCUid* uidMask, uint32 size, void* data, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + NFCContext* ctx = &gNFCContexts[chan]; + + if (!NFCIsInit(chan)) + { + return -0x2e0; + } + + if (NFCGetMode(chan) == NFC_MODE_ACTIVE && NFCSetMode(chan, NFC_MODE_IDLE) < 0) + { + return -0x2dc; + } + + if (ctx->state != NFC_STATE_IDLE) + { + return -0x1df; + } + + // Create unknown record which contains the rw area + ndef::Record rec; + rec.SetTNF(ndef::Record::NDEF_TNF_UNKNOWN); + rec.SetPayload(std::span(reinterpret_cast<std::byte*>(data), size)); + + // Create ndef message which contains the record + ndef::Message msg; + msg.append(rec); + ctx->writeMessage = msg; + + ctx->state = NFC_STATE_WRITE; + ctx->writeCallback = callback; + ctx->writeContext = context; + + // If the discoveryTimeout is 0, no timeout + if (discoveryTimeout == 0) + { + ctx->discoveryTimeout = std::chrono::time_point<std::chrono::system_clock>::max(); + } + else + { + ctx->discoveryTimeout = std::chrono::system_clock::now() + std::chrono::milliseconds(discoveryTimeout); + } + + // TODO uid filter? + + return 0; + } + + void Initialize() + { + cafeExportRegister("nfc", NFCInit, LogType::NFC); + cafeExportRegister("nfc", NFCInitEx, LogType::NFC); + cafeExportRegister("nfc", NFCShutdown, LogType::NFC); + cafeExportRegister("nfc", NFCIsInit, LogType::NFC); + cafeExportRegister("nfc", NFCProc, LogType::NFC); + cafeExportRegister("nfc", NFCGetMode, LogType::NFC); + cafeExportRegister("nfc", NFCSetMode, LogType::NFC); + cafeExportRegister("nfc", NFCSetTagDetectCallback, LogType::NFC); + cafeExportRegister("nfc", NFCGetTagInfo, LogType::NFC); + cafeExportRegister("nfc", NFCSendRawData, LogType::NFC); + cafeExportRegister("nfc", NFCAbort, LogType::NFC); + cafeExportRegister("nfc", NFCRead, LogType::NFC); + cafeExportRegister("nfc", NFCWrite, LogType::NFC); + } + + bool TouchTagFromFile(const fs::path& filePath, uint32* nfcError) + { + // Forward this request to nn_nfp, if the title initialized it + // TODO integrate nn_nfp/ntag/nfc + if (nnNfp_isInitialized()) + { + return nnNfp_touchNfcTagFromFile(filePath, nfcError); + } + + NFCContext* ctx = &gNFCContexts[0]; + + auto nfcData = FileStream::LoadIntoMemory(filePath); + if (!nfcData) + { + *nfcError = NFC_ERROR_NO_ACCESS; + return false; + } + + ctx->tag = TagV0::FromBytes(std::as_bytes(std::span(nfcData->data(), nfcData->size()))); + if (!ctx->tag) + { + *nfcError = NFC_ERROR_INVALID_FILE_FORMAT; + return false; + } + + ctx->nfcStatus |= NFC_STATUS_HAS_TAG; + ctx->tagPath = filePath; + + *nfcError = NFC_ERROR_NONE; + return true; + } +} diff --git a/src/Cafe/OS/libs/nfc/nfc.h b/src/Cafe/OS/libs/nfc/nfc.h new file mode 100644 index 00000000..2ebdd2a4 --- /dev/null +++ b/src/Cafe/OS/libs/nfc/nfc.h @@ -0,0 +1,62 @@ +#pragma once + +// CEMU NFC error codes +#define NFC_ERROR_NONE (0) +#define NFC_ERROR_NO_ACCESS (1) +#define NFC_ERROR_INVALID_FILE_FORMAT (2) + +#define NFC_PROTOCOL_T1T 0x1 +#define NFC_PROTOCOL_T2T 0x2 + +#define NFC_TECHNOLOGY_A 0x0 +#define NFC_TECHNOLOGY_B 0x1 +#define NFC_TECHNOLOGY_F 0x2 + +namespace nfc +{ + struct NFCUid + { + /* +0x00 */ uint8 uid[7]; + }; + static_assert(sizeof(NFCUid) == 0x7); + + struct NFCTagInfo + { + /* +0x00 */ uint8 uidSize; + /* +0x01 */ uint8 uid[10]; + /* +0x0B */ uint8 technology; + /* +0x0C */ uint8 protocol; + /* +0x0D */ uint8 reserved[0x20]; + }; + static_assert(sizeof(NFCTagInfo) == 0x2D); + + sint32 NFCInit(uint32 chan); + + sint32 NFCInitEx(uint32 chan, uint32 powerMode); + + sint32 NFCShutdown(uint32 chan); + + bool NFCIsInit(uint32 chan); + + void NFCProc(uint32 chan); + + sint32 NFCGetMode(uint32 chan); + + sint32 NFCSetMode(uint32 chan, sint32 mode); + + void NFCSetTagDetectCallback(uint32 chan, MPTR callback, void* context); + + sint32 NFCGetTagInfo(uint32 chan, uint32 discoveryTimeout, MPTR callback, void* context); + + sint32 NFCSendRawData(uint32 chan, bool startDiscovery, uint32 discoveryTimeout, uint32 commandTimeout, uint32 commandSize, uint32 responseSize, void* commandData, MPTR callback, void* context); + + sint32 NFCAbort(uint32 chan, MPTR callback, void* context); + + sint32 NFCRead(uint32 chan, uint32 discoveryTimeout, NFCUid* uid, NFCUid* uidMask, MPTR callback, void* context); + + sint32 NFCWrite(uint32 chan, uint32 discoveryTimeout, NFCUid* uid, NFCUid* uidMask, uint32 size, void* data, MPTR callback, void* context); + + void Initialize(); + + bool TouchTagFromFile(const fs::path& filePath, uint32* nfcError); +} diff --git a/src/Cafe/OS/libs/nfc/stream.cpp b/src/Cafe/OS/libs/nfc/stream.cpp new file mode 100644 index 00000000..73c2880f --- /dev/null +++ b/src/Cafe/OS/libs/nfc/stream.cpp @@ -0,0 +1,201 @@ +#include "stream.h" + +#include <algorithm> + +Stream::Stream(std::endian endianness) + : mError(ERROR_OK), mEndianness(endianness) +{ +} + +Stream::~Stream() +{ +} + +Stream::Error Stream::GetError() const +{ + return mError; +} + +void Stream::SetEndianness(std::endian endianness) +{ + mEndianness = endianness; +} + +std::endian Stream::GetEndianness() const +{ + return mEndianness; +} + +Stream& Stream::operator>>(bool& val) +{ + std::uint8_t i; + *this >> i; + val = !!i; + + return *this; +} + +Stream& Stream::operator>>(float& val) +{ + std::uint32_t i; + *this >> i; + val = std::bit_cast<float>(i); + + return *this; +} + +Stream& Stream::operator>>(double& val) +{ + std::uint64_t i; + *this >> i; + val = std::bit_cast<double>(i); + + return *this; +} + +Stream& Stream::operator<<(bool val) +{ + std::uint8_t i = val; + *this >> i; + + return *this; +} + +Stream& Stream::operator<<(float val) +{ + std::uint32_t i = std::bit_cast<std::uint32_t>(val); + *this >> i; + + return *this; +} + +Stream& Stream::operator<<(double val) +{ + std::uint64_t i = std::bit_cast<std::uint64_t>(val); + *this >> i; + + return *this; +} + +void Stream::SetError(Error error) +{ + mError = error; +} + +bool Stream::NeedsSwap() +{ + return mEndianness != std::endian::native; +} + +VectorStream::VectorStream(std::vector<std::byte>& vector, std::endian endianness) + : Stream(endianness), mVector(vector), mPosition(0) +{ +} + +VectorStream::~VectorStream() +{ +} + +std::size_t VectorStream::Read(const std::span<std::byte>& data) +{ + if (data.size() > GetRemaining()) + { + SetError(ERROR_READ_FAILED); + std::fill(data.begin(), data.end(), std::byte(0)); + return 0; + } + + std::copy_n(mVector.get().begin() + mPosition, data.size(), data.begin()); + mPosition += data.size(); + return data.size(); +} + +std::size_t VectorStream::Write(const std::span<const std::byte>& data) +{ + // Resize vector if not enough bytes remain + if (mPosition + data.size() > mVector.get().size()) + { + mVector.get().resize(mPosition + data.size()); + } + + std::copy(data.begin(), data.end(), mVector.get().begin() + mPosition); + mPosition += data.size(); + return data.size(); +} + +bool VectorStream::SetPosition(std::size_t position) +{ + if (position >= mVector.get().size()) + { + return false; + } + + mPosition = position; + return true; +} + +std::size_t VectorStream::GetPosition() const +{ + return mPosition; +} + +std::size_t VectorStream::GetRemaining() const +{ + return mVector.get().size() - mPosition; +} + +SpanStream::SpanStream(std::span<const std::byte> span, std::endian endianness) + : Stream(endianness), mSpan(std::move(span)), mPosition(0) +{ +} + +SpanStream::~SpanStream() +{ +} + +std::size_t SpanStream::Read(const std::span<std::byte>& data) +{ + if (data.size() > GetRemaining()) + { + SetError(ERROR_READ_FAILED); + std::fill(data.begin(), data.end(), std::byte(0)); + return 0; + } + + std::copy_n(mSpan.begin() + mPosition, data.size(), data.begin()); + mPosition += data.size(); + return data.size(); +} + +std::size_t SpanStream::Write(const std::span<const std::byte>& data) +{ + // Cannot write to const span + SetError(ERROR_WRITE_FAILED); + return 0; +} + +bool SpanStream::SetPosition(std::size_t position) +{ + if (position >= mSpan.size()) + { + return false; + } + + mPosition = position; + return true; +} + +std::size_t SpanStream::GetPosition() const +{ + return mPosition; +} + +std::size_t SpanStream::GetRemaining() const +{ + if (mPosition > mSpan.size()) + { + return 0; + } + + return mSpan.size() - mPosition; +} diff --git a/src/Cafe/OS/libs/nfc/stream.h b/src/Cafe/OS/libs/nfc/stream.h new file mode 100644 index 00000000..e666b480 --- /dev/null +++ b/src/Cafe/OS/libs/nfc/stream.h @@ -0,0 +1,139 @@ +#pragma once + +#include <cstdint> +#include <vector> +#include <span> +#include <bit> + +#include "Common/precompiled.h" + +class Stream +{ +public: + enum Error + { + ERROR_OK, + ERROR_READ_FAILED, + ERROR_WRITE_FAILED, + }; + +public: + Stream(std::endian endianness = std::endian::native); + virtual ~Stream(); + + Error GetError() const; + + void SetEndianness(std::endian endianness); + std::endian GetEndianness() const; + + virtual std::size_t Read(const std::span<std::byte>& data) = 0; + virtual std::size_t Write(const std::span<const std::byte>& data) = 0; + + virtual bool SetPosition(std::size_t position) = 0; + virtual std::size_t GetPosition() const = 0; + + virtual std::size_t GetRemaining() const = 0; + + // Stream read operators + template<std::integral T> + Stream& operator>>(T& val) + { + val = 0; + if (Read(std::as_writable_bytes(std::span(std::addressof(val), 1))) == sizeof(val)) + { + if (NeedsSwap()) + { + if (sizeof(T) == 2) + { + val = _swapEndianU16(val); + } + else if (sizeof(T) == 4) + { + val = _swapEndianU32(val); + } + else if (sizeof(T) == 8) + { + val = _swapEndianU64(val); + } + } + } + + return *this; + } + Stream& operator>>(bool& val); + Stream& operator>>(float& val); + Stream& operator>>(double& val); + + // Stream write operators + template<std::integral T> + Stream& operator<<(T val) + { + if (NeedsSwap()) + { + if (sizeof(T) == 2) + { + val = _swapEndianU16(val); + } + else if (sizeof(T) == 4) + { + val = _swapEndianU32(val); + } + else if (sizeof(T) == 8) + { + val = _swapEndianU64(val); + } + } + + Write(std::as_bytes(std::span(std::addressof(val), 1))); + return *this; + } + Stream& operator<<(bool val); + Stream& operator<<(float val); + Stream& operator<<(double val); + +protected: + void SetError(Error error); + + bool NeedsSwap(); + + Error mError; + std::endian mEndianness; +}; + +class VectorStream : public Stream +{ +public: + VectorStream(std::vector<std::byte>& vector, std::endian endianness = std::endian::native); + virtual ~VectorStream(); + + virtual std::size_t Read(const std::span<std::byte>& data) override; + virtual std::size_t Write(const std::span<const std::byte>& data) override; + + virtual bool SetPosition(std::size_t position) override; + virtual std::size_t GetPosition() const override; + + virtual std::size_t GetRemaining() const override; + +private: + std::reference_wrapper<std::vector<std::byte>> mVector; + std::size_t mPosition; +}; + +class SpanStream : public Stream +{ +public: + SpanStream(std::span<const std::byte> span, std::endian endianness = std::endian::native); + virtual ~SpanStream(); + + virtual std::size_t Read(const std::span<std::byte>& data) override; + virtual std::size_t Write(const std::span<const std::byte>& data) override; + + virtual bool SetPosition(std::size_t position) override; + virtual std::size_t GetPosition() const override; + + virtual std::size_t GetRemaining() const override; + +private: + std::span<const std::byte> mSpan; + std::size_t mPosition; +}; diff --git a/src/Cafe/OS/libs/nn_nfp/nn_nfp.cpp b/src/Cafe/OS/libs/nn_nfp/nn_nfp.cpp index ad2ea203..10d9e7cb 100644 --- a/src/Cafe/OS/libs/nn_nfp/nn_nfp.cpp +++ b/src/Cafe/OS/libs/nn_nfp/nn_nfp.cpp @@ -293,41 +293,6 @@ void nnNfpExport_GetTagInfo(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } -typedef struct -{ - /* +0x00 */ uint8 uidLength; - /* +0x01 */ uint8 uid[0xA]; - /* +0x0B */ uint8 ukn0B; - /* +0x0C */ uint8 ukn0C; - /* +0x0D */ uint8 ukn0D; - // more? -}NFCTagInfoCallbackParam_t; - -uint32 NFCGetTagInfo(uint32 index, uint32 timeout, MPTR functionPtr, void* userParam) -{ - cemuLog_log(LogType::NN_NFP, "NFCGetTagInfo({},{},0x{:08x},0x{:08x})", index, timeout, functionPtr, userParam ? memory_getVirtualOffsetFromPointer(userParam) : 0); - - - cemu_assert(index == 0); - - nnNfpLock(); - - StackAllocator<NFCTagInfoCallbackParam_t> _callbackParam; - NFCTagInfoCallbackParam_t* callbackParam = _callbackParam.GetPointer(); - - memset(callbackParam, 0x00, sizeof(NFCTagInfoCallbackParam_t)); - - memcpy(callbackParam->uid, nfp_data.amiiboProcessedData.uid, nfp_data.amiiboProcessedData.uidLength); - callbackParam->uidLength = (uint8)nfp_data.amiiboProcessedData.uidLength; - - PPCCoreCallback(functionPtr, index, 0, _callbackParam.GetPointer(), userParam); - - nnNfpUnlock(); - - - return 0; // 0 -> success -} - void nnNfpExport_Mount(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::NN_NFP, "Mount()"); @@ -769,6 +734,16 @@ void nnNfp_unloadAmiibo() nnNfpUnlock(); } +bool nnNfp_isInitialized() +{ + return nfp_data.nfpIsInitialized; +} + +// CEMU NFC error codes +#define NFC_ERROR_NONE (0) +#define NFC_ERROR_NO_ACCESS (1) +#define NFC_ERROR_INVALID_FILE_FORMAT (2) + bool nnNfp_touchNfcTagFromFile(const fs::path& filePath, uint32* nfcError) { AmiiboRawNFCData rawData = { 0 }; @@ -960,6 +935,41 @@ void nnNfpExport_GetNfpState(PPCInterpreter_t* hCPU) namespace nn::nfp { + typedef struct + { + /* +0x00 */ uint8 uidLength; + /* +0x01 */ uint8 uid[0xA]; + /* +0x0B */ uint8 ukn0B; + /* +0x0C */ uint8 ukn0C; + /* +0x0D */ uint8 ukn0D; + // more? + }NFCTagInfoCallbackParam_t; + + uint32 NFCGetTagInfo(uint32 index, uint32 timeout, MPTR functionPtr, void* userParam) + { + cemuLog_log(LogType::NN_NFP, "NFCGetTagInfo({},{},0x{:08x},0x{:08x})", index, timeout, functionPtr, userParam ? memory_getVirtualOffsetFromPointer(userParam) : 0); + + + cemu_assert(index == 0); + + nnNfpLock(); + + StackAllocator<NFCTagInfoCallbackParam_t> _callbackParam; + NFCTagInfoCallbackParam_t* callbackParam = _callbackParam.GetPointer(); + + memset(callbackParam, 0x00, sizeof(NFCTagInfoCallbackParam_t)); + + memcpy(callbackParam->uid, nfp_data.amiiboProcessedData.uid, nfp_data.amiiboProcessedData.uidLength); + callbackParam->uidLength = (uint8)nfp_data.amiiboProcessedData.uidLength; + + PPCCoreCallback(functionPtr, index, 0, _callbackParam.GetPointer(), userParam); + + nnNfpUnlock(); + + + return 0; // 0 -> success + } + uint32 GetErrorCode(uint32 result) { uint32 level = (result >> 0x1b) & 3; @@ -1019,9 +1029,6 @@ namespace nn::nfp nnNfp_load(); // legacy interface, update these to use cafeExportRegister / cafeExportRegisterFunc cafeExportRegisterFunc(nn::nfp::GetErrorCode, "nn_nfp", "GetErrorCode__Q2_2nn3nfpFRCQ2_2nn6Result", LogType::Placeholder); - - // NFC API - cafeExportRegister("nn_nfp", NFCGetTagInfo, LogType::Placeholder); } } diff --git a/src/Cafe/OS/libs/nn_nfp/nn_nfp.h b/src/Cafe/OS/libs/nn_nfp/nn_nfp.h index e8a1c55f..25b36cc9 100644 --- a/src/Cafe/OS/libs/nn_nfp/nn_nfp.h +++ b/src/Cafe/OS/libs/nn_nfp/nn_nfp.h @@ -2,12 +2,15 @@ namespace nn::nfp { + uint32 NFCGetTagInfo(uint32 index, uint32 timeout, MPTR functionPtr, void* userParam); + void load(); } void nnNfp_load(); void nnNfp_update(); +bool nnNfp_isInitialized(); bool nnNfp_touchNfcTagFromFile(const fs::path& filePath, uint32* nfcError); #define NFP_STATE_NONE (0) @@ -18,8 +21,3 @@ bool nnNfp_touchNfcTagFromFile(const fs::path& filePath, uint32* nfcError); #define NFP_STATE_RW_MOUNT (5) #define NFP_STATE_UNEXPECTED (6) #define NFP_STATE_RW_MOUNT_ROM (7) - -// CEMU NFC error codes -#define NFC_ERROR_NONE (0) -#define NFC_ERROR_NO_ACCESS (1) -#define NFC_ERROR_INVALID_FILE_FORMAT (2) diff --git a/src/Cafe/OS/libs/ntag/ntag.cpp b/src/Cafe/OS/libs/ntag/ntag.cpp new file mode 100644 index 00000000..8bdbb66f --- /dev/null +++ b/src/Cafe/OS/libs/ntag/ntag.cpp @@ -0,0 +1,438 @@ +#include "Cafe/OS/common/OSCommon.h" +#include "Cafe/OS/RPL/rpl.h" +#include "Cafe/OS/libs/ntag/ntag.h" +#include "Cafe/OS/libs/nfc/nfc.h" +#include "Cafe/OS/libs/coreinit/coreinit_IPC.h" +#include "Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.h" + +namespace ntag +{ + struct NTAGWriteData + { + + }; + NTAGWriteData gWriteData[2]; + + bool ccrNfcOpened = false; + IOSDevHandle gCcrNfcHandle; + + NTAGFormatSettings gFormatSettings; + + MPTR gDetectCallbacks[2]; + MPTR gAbortCallbacks[2]; + MPTR gReadCallbacks[2]; + MPTR gWriteCallbacks[2]; + + sint32 __NTAGConvertNFCError(sint32 error) + { + // TODO + return error; + } + + sint32 NTAGInit(uint32 chan) + { + return NTAGInitEx(chan); + } + + sint32 NTAGInitEx(uint32 chan) + { + sint32 result = nfc::NFCInitEx(chan, 1); + return __NTAGConvertNFCError(result); + } + + sint32 NTAGShutdown(uint32 chan) + { + sint32 result = nfc::NFCShutdown(chan); + + if (ccrNfcOpened) + { + coreinit::IOS_Close(gCcrNfcHandle); + ccrNfcOpened = false; + } + + gDetectCallbacks[chan] = MPTR_NULL; + gAbortCallbacks[chan] = MPTR_NULL; + gReadCallbacks[chan] = MPTR_NULL; + gWriteCallbacks[chan] = MPTR_NULL; + + return __NTAGConvertNFCError(result); + } + + bool NTAGIsInit(uint32 chan) + { + return nfc::NFCIsInit(chan); + } + + void NTAGProc(uint32 chan) + { + nfc::NFCProc(chan); + } + + void NTAGSetFormatSettings(NTAGFormatSettings* formatSettings) + { + gFormatSettings.version = formatSettings->version; + gFormatSettings.makerCode = _swapEndianU32(formatSettings->makerCode); + gFormatSettings.indentifyCode = _swapEndianU32(formatSettings->indentifyCode); + } + + void __NTAGDetectCallback(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(chan, 0); + ppcDefineParamU32(hasTag, 1); + ppcDefineParamPtr(context, void, 2); + + cemuLog_log(LogType::NTAG, "__NTAGDetectCallback: {} {} {}", chan, hasTag, context); + + PPCCoreCallback(gDetectCallbacks[chan], chan, hasTag, context); + + osLib_returnFromFunction(hCPU, 0); + } + + void NTAGSetTagDetectCallback(uint32 chan, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + gDetectCallbacks[chan] = callback; + nfc::NFCSetTagDetectCallback(chan, RPLLoader_MakePPCCallable(__NTAGDetectCallback), context); + } + + void __NTAGAbortCallback(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(chan, 0); + ppcDefineParamS32(error, 1); + ppcDefineParamPtr(context, void, 2); + + PPCCoreCallback(gAbortCallbacks[chan], chan, __NTAGConvertNFCError(error), context); + + osLib_returnFromFunction(hCPU, 0); + } + + sint32 NTAGAbort(uint32 chan, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + // TODO is it normal that Rumble U calls this? + + gAbortCallbacks[chan] = callback; + sint32 result = nfc::NFCAbort(chan, RPLLoader_MakePPCCallable(__NTAGAbortCallback), context); + return __NTAGConvertNFCError(result); + } + + bool __NTAGRawDataToNfcData(iosu::ccr_nfc::CCRNFCCryptData* raw, iosu::ccr_nfc::CCRNFCCryptData* nfc) + { + memcpy(nfc, raw, sizeof(iosu::ccr_nfc::CCRNFCCryptData)); + + if (raw->version == 0) + { + nfc->version = 0; + nfc->dataSize = 0x1C8; + nfc->seedOffset = 0x25; + nfc->keyGenSaltOffset = 0x1A8; + nfc->uuidOffset = 0x198; + nfc->unfixedInfosOffset = 0x28; + nfc->unfixedInfosSize = 0x120; + nfc->lockedSecretOffset = 0x168; + nfc->lockedSecretSize = 0x30; + nfc->unfixedInfosHmacOffset = 0; + nfc->lockedSecretHmacOffset = 0x148; + } + else if (raw->version == 2) + { + nfc->version = 2; + nfc->dataSize = 0x208; + nfc->seedOffset = 0x29; + nfc->keyGenSaltOffset = 0x1E8; + nfc->uuidOffset = 0x1D4; + nfc->unfixedInfosOffset = 0x2C; + nfc->unfixedInfosSize = 0x188; + nfc->lockedSecretOffset = 0x1DC; + nfc->lockedSecretSize = 0; + nfc->unfixedInfosHmacOffset = 0x8; + nfc->lockedSecretHmacOffset = 0x1B4; + + memcpy(nfc->data + 0x1d4, raw->data, 0x8); + memcpy(nfc->data, raw->data + 0x8, 0x8); + memcpy(nfc->data + 0x28, raw->data + 0x10, 0x4); + memcpy(nfc->data + nfc->unfixedInfosOffset, raw->data + 0x14, 0x20); + memcpy(nfc->data + nfc->lockedSecretHmacOffset, raw->data + 0x34, 0x20); + memcpy(nfc->data + nfc->lockedSecretOffset, raw->data + 0x54, 0xC); + memcpy(nfc->data + nfc->keyGenSaltOffset, raw->data + 0x60, 0x20); + memcpy(nfc->data + nfc->unfixedInfosHmacOffset, raw->data + 0x80, 0x20); + memcpy(nfc->data + nfc->unfixedInfosOffset + 0x20, raw->data + 0xa0, 0x168); + memcpy(nfc->data + 0x208, raw->data + 0x208, 0x14); + } + else + { + return false; + } + + return true; + } + + bool __NTAGNfcDataToRawData(iosu::ccr_nfc::CCRNFCCryptData* nfc, iosu::ccr_nfc::CCRNFCCryptData* raw) + { + memcpy(raw, nfc, sizeof(iosu::ccr_nfc::CCRNFCCryptData)); + + if (nfc->version == 0) + { + raw->version = 0; + raw->dataSize = 0x1C8; + raw->seedOffset = 0x25; + raw->keyGenSaltOffset = 0x1A8; + raw->uuidOffset = 0x198; + raw->unfixedInfosOffset = 0x28; + raw->unfixedInfosSize = 0x120; + raw->lockedSecretOffset = 0x168; + raw->lockedSecretSize = 0x30; + raw->unfixedInfosHmacOffset = 0; + raw->lockedSecretHmacOffset = 0x148; + } + else if (nfc->version == 2) + { + raw->version = 2; + raw->dataSize = 0x208; + raw->seedOffset = 0x11; + raw->keyGenSaltOffset = 0x60; + raw->uuidOffset = 0; + raw->unfixedInfosOffset = 0x14; + raw->unfixedInfosSize = 0x188; + raw->lockedSecretOffset = 0x54; + raw->lockedSecretSize = 0xC; + raw->unfixedInfosHmacOffset = 0x80; + raw->lockedSecretHmacOffset = 0x34; + + memcpy(raw->data + 0x8, nfc->data, 0x8); + memcpy(raw->data + raw->unfixedInfosHmacOffset, nfc->data + 0x8, 0x20); + memcpy(raw->data + 0x10, nfc->data + 0x28, 0x4); + memcpy(raw->data + raw->unfixedInfosOffset, nfc->data + 0x2C, 0x20); + memcpy(raw->data + 0xa0, nfc->data + 0x4C, 0x168); + memcpy(raw->data + raw->lockedSecretHmacOffset, nfc->data + 0x1B4, 0x20); + memcpy(raw->data + raw->uuidOffset, nfc->data + 0x1D4, 0x8); + memcpy(raw->data + raw->lockedSecretOffset, nfc->data + 0x1DC, 0xC); + memcpy(raw->data + raw->keyGenSaltOffset, nfc->data + 0x1E8, 0x20); + memcpy(raw->data + 0x208, nfc->data + 0x208, 0x14); + } + else + { + return false; + } + + return true; + } + + sint32 __NTAGDecryptData(void* decryptedData, void* rawData) + { + StackAllocator<iosu::ccr_nfc::CCRNFCCryptData> nfcRawData, nfcInData, nfcOutData; + + if (!ccrNfcOpened) + { + gCcrNfcHandle = coreinit::IOS_Open("/dev/ccr_nfc", 0); + } + + // Prepare nfc buffer + nfcRawData->version = 0; + memcpy(nfcRawData->data, rawData, 0x1C8); + __NTAGRawDataToNfcData(nfcRawData.GetPointer(), nfcInData.GetPointer()); + + // Decrypt + sint32 result = coreinit::IOS_Ioctl(gCcrNfcHandle, 2, nfcInData.GetPointer(), sizeof(iosu::ccr_nfc::CCRNFCCryptData), nfcOutData.GetPointer(), sizeof(iosu::ccr_nfc::CCRNFCCryptData)); + + // Unpack nfc buffer + __NTAGNfcDataToRawData(nfcOutData.GetPointer(), nfcRawData.GetPointer()); + memcpy(decryptedData, nfcRawData->data, 0x1C8); + + // Convert result + if (result == CCR_NFC_INVALID_UNFIXED_INFOS) + { + return -0x2708; + } + else if (result == CCR_NFC_INVALID_LOCKED_SECRET) + { + return -0x2707; + } + + return result; + } + + sint32 __NTAGValidateHeaders(NTAGNoftHeader* noftHeader, NTAGInfoHeader* infoHeader, NTAGAreaHeader* rwHeader, NTAGAreaHeader* roHeader) + { + // TODO + return 0; + } + + sint32 __NTAGParseHeaders(const uint8* data, NTAGNoftHeader* noftHeader, NTAGInfoHeader* infoHeader, NTAGAreaHeader* rwHeader, NTAGAreaHeader* roHeader) + { + memcpy(noftHeader, data + 0x20, sizeof(NTAGNoftHeader)); + memcpy(infoHeader, data + 0x198, sizeof(NTAGInfoHeader)); + memcpy(rwHeader, data + _swapEndianU16(infoHeader->rwHeaderOffset), sizeof(NTAGAreaHeader)); + memcpy(roHeader, data + _swapEndianU16(infoHeader->roHeaderOffset), sizeof(NTAGAreaHeader)); + return __NTAGValidateHeaders(noftHeader, infoHeader, rwHeader, roHeader); + } + + sint32 __NTAGParseData(void* rawData, void* rwData, void* roData, nfc::NFCUid* uid, uint32 lockedDataSize, NTAGNoftHeader* noftHeader, NTAGInfoHeader* infoHeader, NTAGAreaHeader* rwHeader, NTAGAreaHeader* roHeader) + { + uint8 decryptedData[0x1C8]; + sint32 result = __NTAGDecryptData(decryptedData, rawData); + if (result < 0) + { + return result; + } + + result = __NTAGParseHeaders(decryptedData, noftHeader, infoHeader, rwHeader, roHeader); + if (result < 0) + { + return result; + } + + if (_swapEndianU16(roHeader->size) + 0x70 != lockedDataSize) + { + cemuLog_log(LogType::Force, "Invalid locked area size"); + return -0x270C; + } + + if (memcmp(infoHeader->uid.uid, uid->uid, sizeof(nfc::NFCUid)) != 0) + { + cemuLog_log(LogType::Force, "UID mismatch"); + return -0x270B; + } + + cemu_assert(_swapEndianU16(rwHeader->offset) + _swapEndianU16(rwHeader->size) < 0x200); + cemu_assert(_swapEndianU16(roHeader->offset) + _swapEndianU16(roHeader->size) < 0x200); + + memcpy(rwData, decryptedData + _swapEndianU16(rwHeader->offset), _swapEndianU16(rwHeader->size)); + memcpy(roData, decryptedData + _swapEndianU16(roHeader->offset), _swapEndianU16(roHeader->size)); + + return 0; + } + + void __NTAGReadCallback(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(chan, 0); + ppcDefineParamS32(error, 1); + ppcDefineParamPtr(uid, nfc::NFCUid, 2); + ppcDefineParamU32(readOnly, 3); + ppcDefineParamU32(dataSize, 4); + ppcDefineParamPtr(data, void, 5); + ppcDefineParamU32(lockedDataSize, 6); + ppcDefineParamPtr(lockedData, void, 7); + ppcDefineParamPtr(context, void, 8); + + uint8 rawData[0x1C8]; + StackAllocator<NTAGData> readResult; + StackAllocator<uint8, 0x1C8> rwData; + StackAllocator<uint8, 0x1C8> roData; + NTAGNoftHeader noftHeader; + NTAGInfoHeader infoHeader; + NTAGAreaHeader rwHeader; + NTAGAreaHeader roHeader; + + readResult->readOnly = readOnly; + + error = __NTAGConvertNFCError(error); + if (error == 0) + { + // Copy raw and locked data into a contigous buffer + memcpy(rawData, data, dataSize); + memcpy(rawData + dataSize, lockedData, lockedDataSize); + + error = __NTAGParseData(rawData, rwData.GetPointer(), roData.GetPointer(), uid, lockedDataSize, &noftHeader, &infoHeader, &rwHeader, &roHeader); + if (error == 0) + { + memcpy(readResult->uid.uid, uid->uid, sizeof(uid->uid)); + readResult->rwInfo.data = _swapEndianU32(rwData.GetMPTR()); + readResult->roInfo.data = _swapEndianU32(roData.GetMPTR()); + readResult->rwInfo.makerCode = rwHeader.makerCode; + readResult->rwInfo.size = rwHeader.size; + readResult->roInfo.makerCode = roHeader.makerCode; + readResult->rwInfo.identifyCode = rwHeader.identifyCode; + readResult->roInfo.identifyCode = roHeader.identifyCode; + readResult->formatVersion = infoHeader.formatVersion; + readResult->roInfo.size = roHeader.size; + + cemuLog_log(LogType::NTAG, "__NTAGReadCallback: {} {} {}", chan, error, context); + + PPCCoreCallback(gReadCallbacks[chan], chan, 0, readResult.GetPointer(), context); + osLib_returnFromFunction(hCPU, 0); + return; + } + } + + if (uid) + { + memcpy(readResult->uid.uid, uid->uid, sizeof(uid->uid)); + } + readResult->roInfo.size = 0; + readResult->rwInfo.size = 0; + readResult->roInfo.data = MPTR_NULL; + readResult->formatVersion = 0; + readResult->rwInfo.data = MPTR_NULL; + cemuLog_log(LogType::NTAG, "__NTAGReadCallback: {} {} {}", chan, error, context); + PPCCoreCallback(gReadCallbacks[chan], chan, error, readResult.GetPointer(), context); + osLib_returnFromFunction(hCPU, 0); + } + + sint32 NTAGRead(uint32 chan, uint32 timeout, nfc::NFCUid* uid, nfc::NFCUid* uidMask, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + gReadCallbacks[chan] = callback; + + nfc::NFCUid _uid{}, _uidMask{}; + if (uid && uidMask) + { + memcpy(&_uid, uid, sizeof(*uid)); + memcpy(&_uidMask, uidMask, sizeof(*uidMask)); + } + + sint32 result = nfc::NFCRead(chan, timeout, &_uid, &_uidMask, RPLLoader_MakePPCCallable(__NTAGReadCallback), context); + return __NTAGConvertNFCError(result); + } + + void __NTAGReadBeforeWriteCallback(PPCInterpreter_t* hCPU) + { + osLib_returnFromFunction(hCPU, 0); + } + + sint32 NTAGWrite(uint32 chan, uint32 timeout, nfc::NFCUid* uid, uint32 rwSize, void* rwData, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + gWriteCallbacks[chan] = callback; + + nfc::NFCUid _uid{}, _uidMask{}; + if (uid) + { + memcpy(&_uid, uid, sizeof(*uid)); + } + memset(_uidMask.uid, 0xff, sizeof(_uidMask.uid)); + + // TODO save write data + + // TODO we probably don't need to read first here + sint32 result = nfc::NFCRead(chan, timeout, &_uid, &_uidMask, RPLLoader_MakePPCCallable(__NTAGReadBeforeWriteCallback), context); + return __NTAGConvertNFCError(result); + } + + sint32 NTAGFormat(uint32 chan, uint32 timeout, nfc::NFCUid* uid, uint32 rwSize, void* rwData, MPTR callback, void* context) + { + cemu_assert(chan < 2); + + // TODO + return 0; + } + + void Initialize() + { + cafeExportRegister("ntag", NTAGInit, LogType::NTAG); + cafeExportRegister("ntag", NTAGInitEx, LogType::NTAG); + cafeExportRegister("ntag", NTAGShutdown, LogType::NTAG); + cafeExportRegister("ntag", NTAGIsInit, LogType::Placeholder); // disabled logging, since this gets spammed + cafeExportRegister("ntag", NTAGProc, LogType::Placeholder); // disabled logging, since this gets spammed + cafeExportRegister("ntag", NTAGSetFormatSettings, LogType::NTAG); + cafeExportRegister("ntag", NTAGSetTagDetectCallback, LogType::NTAG); + cafeExportRegister("ntag", NTAGAbort, LogType::NTAG); + cafeExportRegister("ntag", NTAGRead, LogType::NTAG); + cafeExportRegister("ntag", NTAGWrite, LogType::NTAG); + cafeExportRegister("ntag", NTAGFormat, LogType::NTAG); + } +} diff --git a/src/Cafe/OS/libs/ntag/ntag.h b/src/Cafe/OS/libs/ntag/ntag.h new file mode 100644 index 00000000..1174e6bc --- /dev/null +++ b/src/Cafe/OS/libs/ntag/ntag.h @@ -0,0 +1,94 @@ +#pragma once +#include "Cafe/OS/libs/nfc/nfc.h" + +namespace ntag +{ + struct NTAGFormatSettings + { + /* +0x00 */ uint8 version; + /* +0x04 */ uint32 makerCode; + /* +0x08 */ uint32 indentifyCode; + /* +0x0C */ uint8 reserved[0x1C]; + }; + static_assert(sizeof(NTAGFormatSettings) == 0x28); + +#pragma pack(1) + struct NTAGNoftHeader + { + /* +0x00 */ uint32 magic; + /* +0x04 */ uint8 version; + /* +0x05 */ uint16 writeCount; + /* +0x07 */ uint8 unknown; + }; + static_assert(sizeof(NTAGNoftHeader) == 0x8); +#pragma pack() + + struct NTAGInfoHeader + { + /* +0x00 */ uint16 rwHeaderOffset; + /* +0x02 */ uint16 rwSize; + /* +0x04 */ uint16 roHeaderOffset; + /* +0x06 */ uint16 roSize; + /* +0x08 */ nfc::NFCUid uid; + /* +0x0F */ uint8 formatVersion; + }; + static_assert(sizeof(NTAGInfoHeader) == 0x10); + + struct NTAGAreaHeader + { + /* +0x00 */ uint16 magic; + /* +0x02 */ uint16 offset; + /* +0x04 */ uint16 size; + /* +0x06 */ uint16 padding; + /* +0x08 */ uint32 makerCode; + /* +0x0C */ uint32 identifyCode; + }; + static_assert(sizeof(NTAGAreaHeader) == 0x10); + + struct NTAGAreaInfo + { + /* +0x00 */ MPTR data; + /* +0x04 */ uint16 size; + /* +0x06 */ uint16 padding; + /* +0x08 */ uint32 makerCode; + /* +0x0C */ uint32 identifyCode; + /* +0x10 */ uint8 reserved[0x20]; + }; + static_assert(sizeof(NTAGAreaInfo) == 0x30); + + struct NTAGData + { + /* +0x00 */ nfc::NFCUid uid; + /* +0x07 */ uint8 readOnly; + /* +0x08 */ uint8 formatVersion; + /* +0x09 */ uint8 padding[3]; + /* +0x0C */ NTAGAreaInfo rwInfo; + /* +0x3C */ NTAGAreaInfo roInfo; + /* +0x6C */ uint8 reserved[0x20]; + }; + static_assert(sizeof(NTAGData) == 0x8C); + + sint32 NTAGInit(uint32 chan); + + sint32 NTAGInitEx(uint32 chan); + + sint32 NTAGShutdown(uint32 chan); + + bool NTAGIsInit(uint32 chan); + + void NTAGProc(uint32 chan); + + void NTAGSetFormatSettings(NTAGFormatSettings* formatSettings); + + void NTAGSetTagDetectCallback(uint32 chan, MPTR callback, void* context); + + sint32 NTAGAbort(uint32 chan, MPTR callback, void* context); + + sint32 NTAGRead(uint32 chan, uint32 timeout, nfc::NFCUid* uid, nfc::NFCUid* uidMask, MPTR callback, void* context); + + sint32 NTAGWrite(uint32 chan, uint32 timeout, nfc::NFCUid* uid, uint32 rwSize, void* rwData, MPTR callback, void* context); + + sint32 NTAGFormat(uint32 chan, uint32 timeout, nfc::NFCUid* uid, uint32 rwSize, void* rwData, MPTR callback, void* context); + + void Initialize(); +} diff --git a/src/Cemu/Logging/CemuLogging.cpp b/src/Cemu/Logging/CemuLogging.cpp index 058ab07a..e49ece94 100644 --- a/src/Cemu/Logging/CemuLogging.cpp +++ b/src/Cemu/Logging/CemuLogging.cpp @@ -51,6 +51,8 @@ const std::map<LogType, std::string> g_logging_window_mapping {LogType::Socket, "Socket"}, {LogType::Save, "Save"}, {LogType::H264, "H264"}, + {LogType::NFC, "NFC"}, + {LogType::NTAG, "NTAG"}, {LogType::Patches, "Graphic pack patches"}, {LogType::TextureCache, "Texture cache"}, {LogType::TextureReadback, "Texture readback"}, diff --git a/src/Cemu/Logging/CemuLogging.h b/src/Cemu/Logging/CemuLogging.h index 8fbb318c..5fd652b3 100644 --- a/src/Cemu/Logging/CemuLogging.h +++ b/src/Cemu/Logging/CemuLogging.h @@ -44,6 +44,9 @@ enum class LogType : sint32 nlibcurl = 41, PRUDP = 40, + + NFC = 41, + NTAG = 42, }; template <> diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 097d506e..cb2e988d 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -12,7 +12,7 @@ #include "audio/audioDebuggerWindow.h" #include "gui/canvas/OpenGLCanvas.h" #include "gui/canvas/VulkanCanvas.h" -#include "Cafe/OS/libs/nn_nfp/nn_nfp.h" +#include "Cafe/OS/libs/nfc/nfc.h" #include "Cafe/OS/libs/swkbd/swkbd.h" #include "gui/debugger/DebuggerWindow2.h" #include "util/helpers/helpers.h" @@ -261,7 +261,7 @@ public: return false; uint32 nfcError; std::string path = filenames[0].utf8_string(); - if (nnNfp_touchNfcTagFromFile(_utf8ToPath(path), &nfcError)) + if (nfc::TouchTagFromFile(_utf8ToPath(path), &nfcError)) { GetConfig().AddRecentNfcFile(path); m_window->UpdateNFCMenu(); @@ -749,7 +749,7 @@ void MainWindow::OnNFCMenu(wxCommandEvent& event) return; wxString wxStrFilePath = openFileDialog.GetPath(); uint32 nfcError; - if (nnNfp_touchNfcTagFromFile(_utf8ToPath(wxStrFilePath.utf8_string()), &nfcError) == false) + if (nfc::TouchTagFromFile(_utf8ToPath(wxStrFilePath.utf8_string()), &nfcError) == false) { if (nfcError == NFC_ERROR_NO_ACCESS) wxMessageBox(_("Cannot open file")); @@ -772,7 +772,7 @@ void MainWindow::OnNFCMenu(wxCommandEvent& event) if (!path.empty()) { uint32 nfcError = 0; - if (nnNfp_touchNfcTagFromFile(_utf8ToPath(path), &nfcError) == false) + if (nfc::TouchTagFromFile(_utf8ToPath(path), &nfcError) == false) { if (nfcError == NFC_ERROR_NO_ACCESS) wxMessageBox(_("Cannot open file")); @@ -2210,6 +2210,8 @@ void MainWindow::RecreateMenu() debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::Socket), _("&Socket API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::Socket)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::Save), _("&Save API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::Save)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::H264), _("&H264 API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::H264)); + debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NFC), _("&NFC API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NFC)); + debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NTAG), _("&NTAG API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NTAG)); debugLoggingMenu->AppendSeparator(); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::Patches), _("&Graphic pack patches"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::Patches)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::TextureCache), _("&Texture cache warnings"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::TextureCache)); From 8e8431113a4128330351674ca59771cf203bf8d9 Mon Sep 17 00:00:00 2001 From: GaryOderNichts <12049776+GaryOderNichts@users.noreply.github.com> Date: Fri, 10 May 2024 00:33:31 +0200 Subject: [PATCH 114/299] ntag: Implement NTAGWrite --- src/Cafe/OS/libs/nfc/ndef.cpp | 1 + src/Cafe/OS/libs/nfc/nfc.cpp | 119 +++++++++++++----- src/Cafe/OS/libs/ntag/ntag.cpp | 214 +++++++++++++++++++++++++++++++-- src/Cafe/OS/libs/ntag/ntag.h | 2 +- 4 files changed, 293 insertions(+), 43 deletions(-) diff --git a/src/Cafe/OS/libs/nfc/ndef.cpp b/src/Cafe/OS/libs/nfc/ndef.cpp index f8d87fb8..32097cfd 100644 --- a/src/Cafe/OS/libs/nfc/ndef.cpp +++ b/src/Cafe/OS/libs/nfc/ndef.cpp @@ -6,6 +6,7 @@ namespace ndef { Record::Record() + : mFlags(0), mTNF(NDEF_TNF_EMPTY) { } diff --git a/src/Cafe/OS/libs/nfc/nfc.cpp b/src/Cafe/OS/libs/nfc/nfc.cpp index 21e9e91b..f8f67ebd 100644 --- a/src/Cafe/OS/libs/nfc/nfc.cpp +++ b/src/Cafe/OS/libs/nfc/nfc.cpp @@ -39,6 +39,7 @@ namespace nfc bool hasTag; uint32 nfcStatus; + std::chrono::time_point<std::chrono::system_clock> touchTime; std::chrono::time_point<std::chrono::system_clock> discoveryTimeout; MPTR tagDetectCallback; @@ -146,7 +147,8 @@ namespace nfc // Look for the unknown TNF which contains the data we care about for (const auto& rec : *ndefMsg) { - if (rec.GetTNF() == ndef::Record::NDEF_TNF_UNKNOWN) { + if (rec.GetTNF() == ndef::Record::NDEF_TNF_UNKNOWN) + { dataSize = rec.GetPayload().size(); cemu_assert(dataSize < 0x200); memcpy(data.GetPointer(), rec.GetPayload().data(), dataSize); @@ -174,11 +176,6 @@ namespace nfc { result = -0xBFE; } - - // Clear tag status after read - // TODO this is not really nice here - ctx->nfcStatus &= ~NFC_STATUS_HAS_TAG; - ctx->tag = {}; } else { @@ -194,9 +191,42 @@ namespace nfc ctx->state = NFC_STATE_IDLE; - // TODO write to file + sint32 result; - PPCCoreCallback(ctx->writeCallback, chan, 0, ctx->writeContext); + if (ctx->tag) + { + // Update tag NDEF data + ctx->tag->SetNDEFData(ctx->writeMessage.ToBytes()); + + // TODO remove this once writing is confirmed working + fs::path newPath = ctx->tagPath; + if (newPath.extension() != ".bak") + { + newPath += ".bak"; + } + cemuLog_log(LogType::Force, "Saving tag as {}...", newPath.string()); + + // open file for writing + FileStream* fs = FileStream::createFile2(newPath); + if (!fs) + { + result = -0x2DE; + } + else + { + auto tagBytes = ctx->tag->ToBytes(); + fs->writeData(tagBytes.data(), tagBytes.size()); + delete fs; + + result = 0; + } + } + else + { + result = -0x2DD; + } + + PPCCoreCallback(ctx->writeCallback, chan, result, ctx->writeContext); } void __NFCHandleAbort(uint32 chan) @@ -231,6 +261,29 @@ namespace nfc PPCCoreCallback(ctx->rawCallback, chan, result, responseSize, responseData, ctx->rawContext); } + bool __NFCShouldHandleState(NFCContext* ctx) + { + // Always handle abort + if (ctx->state == NFC_STATE_ABORT) + { + return true; + } + + // Do we have a tag? + if (ctx->nfcStatus & NFC_STATUS_HAS_TAG) + { + return true; + } + + // Did the timeout expire? + if (ctx->discoveryTimeout < std::chrono::system_clock::now()) + { + return true; + } + + return false; + } + void NFCProc(uint32 chan) { cemu_assert(chan < 2); @@ -242,6 +295,11 @@ namespace nfc return; } + if (ctx->state == NFC_STATE_INITIALIZED) + { + ctx->state = NFC_STATE_IDLE; + } + // Check if the detect callback should be called if (ctx->nfcStatus & NFC_STATUS_HAS_TAG) { @@ -254,6 +312,14 @@ namespace nfc ctx->hasTag = true; } + + // Check if the tag should be removed again + if (ctx->touchTime + std::chrono::seconds(2) < std::chrono::system_clock::now()) + { + ctx->nfcStatus &= ~NFC_STATUS_HAS_TAG; + ctx->tag = {}; + ctx->tagPath = ""; + } } else { @@ -268,33 +334,25 @@ namespace nfc } } - switch (ctx->state) + if (__NFCShouldHandleState(ctx)) { - case NFC_STATE_INITIALIZED: - ctx->state = NFC_STATE_IDLE; - break; - case NFC_STATE_IDLE: - break; - case NFC_STATE_READ: - // Do we have a tag or did the timeout expire? - if ((ctx->nfcStatus & NFC_STATUS_HAS_TAG) || ctx->discoveryTimeout < std::chrono::system_clock::now()) + switch (ctx->state) { + case NFC_STATE_READ: __NFCHandleRead(chan); - } - break; - case NFC_STATE_WRITE: - __NFCHandleWrite(chan); - break; - case NFC_STATE_ABORT: - __NFCHandleAbort(chan); - break; - case NFC_STATE_RAW: - // Do we have a tag or did the timeout expire? - if ((ctx->nfcStatus & NFC_STATUS_HAS_TAG) || ctx->discoveryTimeout < std::chrono::system_clock::now()) - { + break; + case NFC_STATE_WRITE: + __NFCHandleWrite(chan); + break; + case NFC_STATE_ABORT: + __NFCHandleAbort(chan); + break; + case NFC_STATE_RAW: __NFCHandleRaw(chan); + break; + default: + break; } - break; } } @@ -589,6 +647,7 @@ namespace nfc ctx->nfcStatus |= NFC_STATUS_HAS_TAG; ctx->tagPath = filePath; + ctx->touchTime = std::chrono::system_clock::now(); *nfcError = NFC_ERROR_NONE; return true; diff --git a/src/Cafe/OS/libs/ntag/ntag.cpp b/src/Cafe/OS/libs/ntag/ntag.cpp index 8bdbb66f..18ed798a 100644 --- a/src/Cafe/OS/libs/ntag/ntag.cpp +++ b/src/Cafe/OS/libs/ntag/ntag.cpp @@ -9,7 +9,10 @@ namespace ntag { struct NTAGWriteData { - + uint16 size; + uint8 data[0x1C8]; + nfc::NFCUid uid; + nfc::NFCUid uidMask; }; NTAGWriteData gWriteData[2]; @@ -72,7 +75,7 @@ namespace ntag { gFormatSettings.version = formatSettings->version; gFormatSettings.makerCode = _swapEndianU32(formatSettings->makerCode); - gFormatSettings.indentifyCode = _swapEndianU32(formatSettings->indentifyCode); + gFormatSettings.identifyCode = _swapEndianU32(formatSettings->identifyCode); } void __NTAGDetectCallback(PPCInterpreter_t* hCPU) @@ -220,7 +223,7 @@ namespace ntag return true; } - sint32 __NTAGDecryptData(void* decryptedData, void* rawData) + sint32 __NTAGDecryptData(void* decryptedData, const void* rawData) { StackAllocator<iosu::ccr_nfc::CCRNFCCryptData> nfcRawData, nfcInData, nfcOutData; @@ -256,7 +259,41 @@ namespace ntag sint32 __NTAGValidateHeaders(NTAGNoftHeader* noftHeader, NTAGInfoHeader* infoHeader, NTAGAreaHeader* rwHeader, NTAGAreaHeader* roHeader) { - // TODO + if (infoHeader->formatVersion != gFormatSettings.version || noftHeader->version != 0x1) + { + cemuLog_log(LogType::Force, "Invalid format version"); + return -0x2710; + } + + if (_swapEndianU32(noftHeader->magic) != 0x4E4F4654 /* 'NOFT' */ || + _swapEndianU16(rwHeader->magic) != 0x5257 /* 'RW' */ || + _swapEndianU16(roHeader->magic) != 0x524F /* 'RO' */) + { + cemuLog_log(LogType::Force, "Invalid header magic"); + return -0x270F; + } + + if (_swapEndianU32(rwHeader->makerCode) != gFormatSettings.makerCode || + _swapEndianU32(roHeader->makerCode) != gFormatSettings.makerCode) + { + cemuLog_log(LogType::Force, "Invalid maker code"); + return -0x270E; + } + + if (infoHeader->formatVersion != 0 && + (_swapEndianU32(rwHeader->identifyCode) != gFormatSettings.identifyCode || + _swapEndianU32(roHeader->identifyCode) != gFormatSettings.identifyCode)) + { + cemuLog_log(LogType::Force, "Invalid identify code"); + return -0x2709; + } + + if (_swapEndianU16(rwHeader->size) + _swapEndianU16(roHeader->size) != 0x130) + { + cemuLog_log(LogType::Force, "Invalid data size"); + return -0x270D; + } + return 0; } @@ -264,8 +301,13 @@ namespace ntag { memcpy(noftHeader, data + 0x20, sizeof(NTAGNoftHeader)); memcpy(infoHeader, data + 0x198, sizeof(NTAGInfoHeader)); + + cemu_assert(_swapEndianU16(infoHeader->rwHeaderOffset) + sizeof(NTAGAreaHeader) < 0x200); + cemu_assert(_swapEndianU16(infoHeader->roHeaderOffset) + sizeof(NTAGAreaHeader) < 0x200); + memcpy(rwHeader, data + _swapEndianU16(infoHeader->rwHeaderOffset), sizeof(NTAGAreaHeader)); memcpy(roHeader, data + _swapEndianU16(infoHeader->roHeaderOffset), sizeof(NTAGAreaHeader)); + return __NTAGValidateHeaders(noftHeader, infoHeader, rwHeader, roHeader); } @@ -317,7 +359,7 @@ namespace ntag ppcDefineParamPtr(lockedData, void, 7); ppcDefineParamPtr(context, void, 8); - uint8 rawData[0x1C8]; + uint8 rawData[0x1C8]{}; StackAllocator<NTAGData> readResult; StackAllocator<uint8, 0x1C8> rwData; StackAllocator<uint8, 0x1C8> roData; @@ -331,6 +373,9 @@ namespace ntag error = __NTAGConvertNFCError(error); if (error == 0) { + memset(rwData.GetPointer(), 0, 0x1C8); + memset(roData.GetPointer(), 0, 0x1C8); + // Copy raw and locked data into a contigous buffer memcpy(rawData, data, dataSize); memcpy(rawData + dataSize, lockedData, lockedDataSize); @@ -388,28 +433,173 @@ namespace ntag return __NTAGConvertNFCError(result); } + sint32 __NTAGEncryptData(void* encryptedData, const void* rawData) + { + StackAllocator<iosu::ccr_nfc::CCRNFCCryptData> nfcRawData, nfcInData, nfcOutData; + + if (!ccrNfcOpened) + { + gCcrNfcHandle = coreinit::IOS_Open("/dev/ccr_nfc", 0); + } + + // Prepare nfc buffer + nfcRawData->version = 0; + memcpy(nfcRawData->data, rawData, 0x1C8); + __NTAGRawDataToNfcData(nfcRawData.GetPointer(), nfcInData.GetPointer()); + + // Encrypt + sint32 result = coreinit::IOS_Ioctl(gCcrNfcHandle, 1, nfcInData.GetPointer(), sizeof(iosu::ccr_nfc::CCRNFCCryptData), nfcOutData.GetPointer(), sizeof(iosu::ccr_nfc::CCRNFCCryptData)); + + // Unpack nfc buffer + __NTAGNfcDataToRawData(nfcOutData.GetPointer(), nfcRawData.GetPointer()); + memcpy(encryptedData, nfcRawData->data, 0x1C8); + + return result; + } + + sint32 __NTAGPrepareWriteData(void* outBuffer, uint32 dataSize, const void* data, const void* tagData, NTAGNoftHeader* noftHeader, NTAGAreaHeader* rwHeader) + { + uint8 decryptedBuffer[0x1C8]; + uint8 encryptedBuffer[0x1C8]; + + memcpy(decryptedBuffer, tagData, 0x1C8); + + // Fill the rest of the rw area with random data + if (dataSize < _swapEndianU16(rwHeader->size)) + { + uint8 randomBuffer[0x1C8]; + for (int i = 0; i < sizeof(randomBuffer); i++) + { + randomBuffer[i] = rand() & 0xFF; + } + + memcpy(decryptedBuffer + _swapEndianU16(rwHeader->offset) + dataSize, randomBuffer, _swapEndianU16(rwHeader->size) - dataSize); + } + + // Make sure the data fits into the rw area + if (_swapEndianU16(rwHeader->size) < dataSize) + { + return -0x270D; + } + + // Update write count (check for overflow) + if ((_swapEndianU16(noftHeader->writeCount) & 0x7fff) == 0x7fff) + { + noftHeader->writeCount = _swapEndianU16(_swapEndianU16(noftHeader->writeCount) & 0x8000); + } + else + { + noftHeader->writeCount = _swapEndianU16(_swapEndianU16(noftHeader->writeCount) + 1); + } + + memcpy(decryptedBuffer + 0x20, noftHeader, sizeof(noftHeader)); + memcpy(decryptedBuffer + _swapEndianU16(rwHeader->offset), data, dataSize); + + // Encrypt + sint32 result = __NTAGEncryptData(encryptedBuffer, decryptedBuffer); + if (result < 0) + { + return result; + } + + memcpy(outBuffer, encryptedBuffer, _swapEndianU16(rwHeader->size) + 0x28); + return 0; + } + + void __NTAGWriteCallback(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(chan, 0); + ppcDefineParamS32(error, 1); + ppcDefineParamPtr(context, void, 2); + + PPCCoreCallback(gWriteCallbacks[chan], chan, __NTAGConvertNFCError(error), context); + + osLib_returnFromFunction(hCPU, 0); + } + void __NTAGReadBeforeWriteCallback(PPCInterpreter_t* hCPU) { + ppcDefineParamU32(chan, 0); + ppcDefineParamS32(error, 1); + ppcDefineParamPtr(uid, nfc::NFCUid, 2); + ppcDefineParamU32(readOnly, 3); + ppcDefineParamU32(dataSize, 4); + ppcDefineParamPtr(data, void, 5); + ppcDefineParamU32(lockedDataSize, 6); + ppcDefineParamPtr(lockedData, void, 7); + ppcDefineParamPtr(context, void, 8); + + uint8 rawData[0x1C8]{}; + uint8 rwData[0x1C8]{}; + uint8 roData[0x1C8]{}; + NTAGNoftHeader noftHeader; + NTAGInfoHeader infoHeader; + NTAGAreaHeader rwHeader; + NTAGAreaHeader roHeader; + uint8 writeBuffer[0x1C8]{}; + + error = __NTAGConvertNFCError(error); + if (error == 0) + { + // Copy raw and locked data into a contigous buffer + memcpy(rawData, data, dataSize); + memcpy(rawData + dataSize, lockedData, lockedDataSize); + + error = __NTAGParseData(rawData, rwData, roData, uid, lockedDataSize, &noftHeader, &infoHeader, &rwHeader, &roHeader); + if (error < 0) + { + cemuLog_log(LogType::Force, "Failed to parse data before write"); + PPCCoreCallback(gWriteCallbacks[chan], chan, -0x3E3, context); + osLib_returnFromFunction(hCPU, 0); + return; + } + + // Prepare data + memcpy(rawData + _swapEndianU16(infoHeader.rwHeaderOffset), &rwHeader, sizeof(rwHeader)); + memcpy(rawData + _swapEndianU16(infoHeader.roHeaderOffset), &roHeader, sizeof(roHeader)); + memcpy(rawData + _swapEndianU16(roHeader.offset), roData, _swapEndianU16(roHeader.size)); + error = __NTAGPrepareWriteData(writeBuffer, gWriteData[chan].size, gWriteData[chan].data, rawData, &noftHeader, &rwHeader); + if (error < 0) + { + cemuLog_log(LogType::Force, "Failed to prepare write data"); + PPCCoreCallback(gWriteCallbacks[chan], chan, -0x3E3, context); + osLib_returnFromFunction(hCPU, 0); + return; + } + + // Write data to tag + error = nfc::NFCWrite(chan, 200, &gWriteData[chan].uid, &gWriteData[chan].uidMask, + _swapEndianU16(rwHeader.size) + 0x28, writeBuffer, RPLLoader_MakePPCCallable(__NTAGWriteCallback), context); + if (error >= 0) + { + osLib_returnFromFunction(hCPU, 0); + return; + } + + error = __NTAGConvertNFCError(error); + } + + PPCCoreCallback(gWriteCallbacks[chan], chan, error, context); osLib_returnFromFunction(hCPU, 0); } sint32 NTAGWrite(uint32 chan, uint32 timeout, nfc::NFCUid* uid, uint32 rwSize, void* rwData, MPTR callback, void* context) { cemu_assert(chan < 2); + cemu_assert(rwSize < 0x1C8); gWriteCallbacks[chan] = callback; - nfc::NFCUid _uid{}, _uidMask{}; if (uid) { - memcpy(&_uid, uid, sizeof(*uid)); + memcpy(&gWriteData[chan].uid, uid, sizeof(nfc::NFCUid)); } - memset(_uidMask.uid, 0xff, sizeof(_uidMask.uid)); + memset(&gWriteData[chan].uidMask, 0xff, sizeof(nfc::NFCUid)); - // TODO save write data + gWriteData[chan].size = rwSize; + memcpy(gWriteData[chan].data, rwData, rwSize); - // TODO we probably don't need to read first here - sint32 result = nfc::NFCRead(chan, timeout, &_uid, &_uidMask, RPLLoader_MakePPCCallable(__NTAGReadBeforeWriteCallback), context); + sint32 result = nfc::NFCRead(chan, timeout, &gWriteData[chan].uid, &gWriteData[chan].uidMask, RPLLoader_MakePPCCallable(__NTAGReadBeforeWriteCallback), context); return __NTAGConvertNFCError(result); } @@ -418,7 +608,7 @@ namespace ntag cemu_assert(chan < 2); // TODO - return 0; + return -1; } void Initialize() diff --git a/src/Cafe/OS/libs/ntag/ntag.h b/src/Cafe/OS/libs/ntag/ntag.h index 1174e6bc..697c065e 100644 --- a/src/Cafe/OS/libs/ntag/ntag.h +++ b/src/Cafe/OS/libs/ntag/ntag.h @@ -7,7 +7,7 @@ namespace ntag { /* +0x00 */ uint8 version; /* +0x04 */ uint32 makerCode; - /* +0x08 */ uint32 indentifyCode; + /* +0x08 */ uint32 identifyCode; /* +0x0C */ uint8 reserved[0x1C]; }; static_assert(sizeof(NTAGFormatSettings) == 0x28); From 41fe598e333920196aa8fd6033aaa78172e21655 Mon Sep 17 00:00:00 2001 From: GaryOderNichts <12049776+GaryOderNichts@users.noreply.github.com> Date: Fri, 17 May 2024 14:19:51 +0200 Subject: [PATCH 115/299] nfc: Implement UID filter --- src/Cafe/OS/libs/nfc/nfc.cpp | 118 ++++++++++++++++++++++------------- 1 file changed, 76 insertions(+), 42 deletions(-) diff --git a/src/Cafe/OS/libs/nfc/nfc.cpp b/src/Cafe/OS/libs/nfc/nfc.cpp index f8f67ebd..4505f3b1 100644 --- a/src/Cafe/OS/libs/nfc/nfc.cpp +++ b/src/Cafe/OS/libs/nfc/nfc.cpp @@ -41,6 +41,10 @@ namespace nfc uint32 nfcStatus; std::chrono::time_point<std::chrono::system_clock> touchTime; std::chrono::time_point<std::chrono::system_clock> discoveryTimeout; + struct { + NFCUid uid; + NFCUid mask; + } filter; MPTR tagDetectCallback; void* tagDetectContext; @@ -124,6 +128,19 @@ namespace nfc return gNFCContexts[chan].isInitialized; } + bool __NFCCompareUid(NFCUid* uid, NFCUid* filterUid, NFCUid* filterMask) + { + for (int i = 0; i < sizeof(uid->uid); i++) + { + if ((uid->uid[i] & filterMask->uid[i]) != filterUid->uid[i]) + { + return false; + } + } + + return true; + } + void __NFCHandleRead(uint32 chan) { NFCContext* ctx = &gNFCContexts[chan]; @@ -140,32 +157,38 @@ namespace nfc if (ctx->tag) { - // Try to parse ndef message - auto ndefMsg = ndef::Message::FromBytes(ctx->tag->GetNDEFData()); - if (ndefMsg) + // Compare UID + memcpy(uid.GetPointer(), ctx->tag->GetUIDBlock().data(), sizeof(NFCUid)); + if (__NFCCompareUid(uid.GetPointer(), &ctx->filter.uid, &ctx->filter.mask)) { - // Look for the unknown TNF which contains the data we care about - for (const auto& rec : *ndefMsg) + // Try to parse ndef message + auto ndefMsg = ndef::Message::FromBytes(ctx->tag->GetNDEFData()); + if (ndefMsg) { - if (rec.GetTNF() == ndef::Record::NDEF_TNF_UNKNOWN) + // Look for the unknown TNF which contains the data we care about + for (const auto& rec : *ndefMsg) { - dataSize = rec.GetPayload().size(); - cemu_assert(dataSize < 0x200); - memcpy(data.GetPointer(), rec.GetPayload().data(), dataSize); - break; + if (rec.GetTNF() == ndef::Record::NDEF_TNF_UNKNOWN) + { + dataSize = rec.GetPayload().size(); + cemu_assert(dataSize < 0x200); + memcpy(data.GetPointer(), rec.GetPayload().data(), dataSize); + break; + } } - } - if (dataSize) - { - // Get locked data - lockedDataSize = ctx->tag->GetLockedArea().size(); - memcpy(lockedData.GetPointer(), ctx->tag->GetLockedArea().data(), lockedDataSize); + if (dataSize) + { + // Get locked data + lockedDataSize = ctx->tag->GetLockedArea().size(); + memcpy(lockedData.GetPointer(), ctx->tag->GetLockedArea().data(), lockedDataSize); - // Fill in uid - memcpy(uid.GetPointer(), ctx->tag->GetUIDBlock().data(), sizeof(NFCUid)); - - result = 0; + result = 0; + } + else + { + result = -0xBFE; + } } else { @@ -174,7 +197,7 @@ namespace nfc } else { - result = -0xBFE; + result = -0x1F6; } } else @@ -195,30 +218,39 @@ namespace nfc if (ctx->tag) { - // Update tag NDEF data - ctx->tag->SetNDEFData(ctx->writeMessage.ToBytes()); - - // TODO remove this once writing is confirmed working - fs::path newPath = ctx->tagPath; - if (newPath.extension() != ".bak") + NFCUid uid; + memcpy(&uid, ctx->tag->GetUIDBlock().data(), sizeof(NFCUid)); + if (__NFCCompareUid(&uid, &ctx->filter.uid, &ctx->filter.mask)) { - newPath += ".bak"; - } - cemuLog_log(LogType::Force, "Saving tag as {}...", newPath.string()); + // Update tag NDEF data + ctx->tag->SetNDEFData(ctx->writeMessage.ToBytes()); - // open file for writing - FileStream* fs = FileStream::createFile2(newPath); - if (!fs) - { - result = -0x2DE; + // TODO remove this once writing is confirmed working + fs::path newPath = ctx->tagPath; + if (newPath.extension() != ".bak") + { + newPath += ".bak"; + } + cemuLog_log(LogType::Force, "Saving tag as {}...", newPath.string()); + + // open file for writing + FileStream* fs = FileStream::createFile2(newPath); + if (!fs) + { + result = -0x2DE; + } + else + { + auto tagBytes = ctx->tag->ToBytes(); + fs->writeData(tagBytes.data(), tagBytes.size()); + delete fs; + + result = 0; + } } else { - auto tagBytes = ctx->tag->ToBytes(); - fs->writeData(tagBytes.data(), tagBytes.size()); - delete fs; - - result = 0; + result = -0x2F6; } } else @@ -548,7 +580,8 @@ namespace nfc ctx->discoveryTimeout = std::chrono::system_clock::now() + std::chrono::milliseconds(discoveryTimeout); } - // TODO uid filter? + memcpy(&ctx->filter.uid, uid, sizeof(*uid)); + memcpy(&ctx->filter.mask, uidMask, sizeof(*uidMask)); return 0; } @@ -598,7 +631,8 @@ namespace nfc ctx->discoveryTimeout = std::chrono::system_clock::now() + std::chrono::milliseconds(discoveryTimeout); } - // TODO uid filter? + memcpy(&ctx->filter.uid, uid, sizeof(*uid)); + memcpy(&ctx->filter.mask, uidMask, sizeof(*uidMask)); return 0; } From 8fe69cd0fb6be8d916a963290e7c5525c0848bb5 Mon Sep 17 00:00:00 2001 From: GaryOderNichts <12049776+GaryOderNichts@users.noreply.github.com> Date: Sat, 18 May 2024 16:38:52 +0200 Subject: [PATCH 116/299] Properly implement NFC result codes --- src/Cafe/OS/libs/nfc/nfc.cpp | 133 ++++++++++++++++++--------------- src/Cafe/OS/libs/nfc/nfc.h | 36 ++++++++- src/Cafe/OS/libs/ntag/ntag.cpp | 47 ++++++++---- src/Cafe/OS/libs/ntag/ntag.h | 7 ++ src/gui/MainWindow.cpp | 18 ++--- 5 files changed, 153 insertions(+), 88 deletions(-) diff --git a/src/Cafe/OS/libs/nfc/nfc.cpp b/src/Cafe/OS/libs/nfc/nfc.cpp index 4505f3b1..818c7339 100644 --- a/src/Cafe/OS/libs/nfc/nfc.cpp +++ b/src/Cafe/OS/libs/nfc/nfc.cpp @@ -7,27 +7,25 @@ #include "TagV0.h" #include "ndef.h" -// TODO move errors to header and allow ntag to convert them +#define NFC_MODE_INVALID -1 +#define NFC_MODE_IDLE 0 +#define NFC_MODE_ACTIVE 1 -#define NFC_MODE_INVALID -1 -#define NFC_MODE_IDLE 0 -#define NFC_MODE_ACTIVE 1 +#define NFC_STATE_UNINITIALIZED 0x0 +#define NFC_STATE_INITIALIZED 0x1 +#define NFC_STATE_IDLE 0x2 +#define NFC_STATE_READ 0x3 +#define NFC_STATE_WRITE 0x4 +#define NFC_STATE_ABORT 0x5 +#define NFC_STATE_FORMAT 0x6 +#define NFC_STATE_SET_READ_ONLY 0x7 +#define NFC_STATE_TAG_PRESENT 0x8 +#define NFC_STATE_DETECT 0x9 +#define NFC_STATE_SEND_RAW_DATA 0xA -#define NFC_STATE_UNINITIALIZED 0x0 -#define NFC_STATE_INITIALIZED 0x1 -#define NFC_STATE_IDLE 0x2 -#define NFC_STATE_READ 0x3 -#define NFC_STATE_WRITE 0x4 -#define NFC_STATE_ABORT 0x5 -#define NFC_STATE_FORMAT 0x6 -#define NFC_STATE_SET_READ_ONLY 0x7 -#define NFC_STATE_TAG_PRESENT 0x8 -#define NFC_STATE_DETECT 0x9 -#define NFC_STATE_RAW 0xA - -#define NFC_STATUS_COMMAND_COMPLETE 0x1 -#define NFC_STATUS_READY 0x2 -#define NFC_STATUS_HAS_TAG 0x4 +#define NFC_STATUS_COMMAND_COMPLETE 0x1 +#define NFC_STATUS_READY 0x2 +#define NFC_STATUS_HAS_TAG 0x4 namespace nfc { @@ -107,7 +105,7 @@ namespace nfc ctx->isInitialized = true; ctx->state = NFC_STATE_INITIALIZED; - return 0; + return NFC_RESULT_SUCCESS; } sint32 NFCShutdown(uint32 chan) @@ -118,7 +116,7 @@ namespace nfc __NFCClearContext(ctx); - return 0; + return NFC_RESULT_SUCCESS; } bool NFCIsInit(uint32 chan) @@ -183,26 +181,26 @@ namespace nfc lockedDataSize = ctx->tag->GetLockedArea().size(); memcpy(lockedData.GetPointer(), ctx->tag->GetLockedArea().data(), lockedDataSize); - result = 0; + result = NFC_RESULT_SUCCESS; } else { - result = -0xBFE; + result = NFC_MAKE_RESULT(NFC_RESULT_BASE_TAG_PARSE, NFC_RESULT_INVALID_TAG); } } else { - result = -0xBFE; + result = NFC_MAKE_RESULT(NFC_RESULT_BASE_TAG_PARSE, NFC_RESULT_INVALID_TAG); } } else { - result = -0x1F6; + result = NFC_MAKE_RESULT(NFC_RESULT_BASE_READ, NFC_RESULT_UID_MISMATCH); } } else { - result = -0x1DD; + result = NFC_MAKE_RESULT(NFC_RESULT_BASE_READ, NFC_RESULT_NO_TAG); } PPCCoreCallback(ctx->readCallback, chan, result, uid.GetPointer(), readOnly, dataSize, data.GetPointer(), lockedDataSize, lockedData.GetPointer(), ctx->readContext); @@ -231,13 +229,13 @@ namespace nfc { newPath += ".bak"; } - cemuLog_log(LogType::Force, "Saving tag as {}...", newPath.string()); + cemuLog_log(LogType::NFC, "Saving tag as {}...", newPath.string()); // open file for writing FileStream* fs = FileStream::createFile2(newPath); if (!fs) { - result = -0x2DE; + result = NFC_MAKE_RESULT(NFC_RESULT_BASE_WRITE, 0x22); } else { @@ -245,17 +243,17 @@ namespace nfc fs->writeData(tagBytes.data(), tagBytes.size()); delete fs; - result = 0; + result = NFC_RESULT_SUCCESS; } } else { - result = -0x2F6; + result = NFC_MAKE_RESULT(NFC_RESULT_BASE_WRITE, NFC_RESULT_UID_MISMATCH); } } else { - result = -0x2DD; + result = NFC_MAKE_RESULT(NFC_RESULT_BASE_WRITE, NFC_RESULT_NO_TAG); } PPCCoreCallback(ctx->writeCallback, chan, result, ctx->writeContext); @@ -279,11 +277,11 @@ namespace nfc sint32 result; if (ctx->nfcStatus & NFC_STATUS_HAS_TAG) { - result = 0; + result = NFC_RESULT_SUCCESS; } else { - result = -0x9DD; + result = NFC_MAKE_RESULT(NFC_RESULT_BASE_SEND_RAW_DATA, NFC_RESULT_NO_TAG); } // We don't actually send any commands/responses @@ -379,12 +377,15 @@ namespace nfc case NFC_STATE_ABORT: __NFCHandleAbort(chan); break; - case NFC_STATE_RAW: + case NFC_STATE_SEND_RAW_DATA: __NFCHandleRaw(chan); break; default: break; } + + // Return back to idle mode + ctx->mode = NFC_MODE_IDLE; } } @@ -410,17 +411,17 @@ namespace nfc if (!NFCIsInit(chan)) { - return -0xAE0; + return NFC_MAKE_RESULT(NFC_RESULT_BASE_SET_MODE, NFC_RESULT_UNINITIALIZED); } if (ctx->state == NFC_STATE_UNINITIALIZED) { - return -0xADF; + return NFC_MAKE_RESULT(NFC_RESULT_BASE_SET_MODE, NFC_RESULT_INVALID_STATE); } ctx->mode = mode; - return 0; + return NFC_RESULT_SUCCESS; } void NFCSetTagDetectCallback(uint32 chan, MPTR callback, void* context) @@ -440,19 +441,30 @@ namespace nfc if (!NFCIsInit(chan)) { - return -0x6E0; + return NFC_MAKE_RESULT(NFC_RESULT_BASE_ABORT, NFC_RESULT_UNINITIALIZED); } if (ctx->state <= NFC_STATE_IDLE) { - return -0x6DF; + return NFC_MAKE_RESULT(NFC_RESULT_BASE_ABORT, NFC_RESULT_INVALID_STATE); } ctx->state = NFC_STATE_ABORT; ctx->abortCallback = callback; ctx->abortContext = context; - return 0; + return NFC_RESULT_SUCCESS; + } + + sint32 __NFCConvertGetTagInfoResult(sint32 result) + { + if (result == NFC_MAKE_RESULT(NFC_RESULT_BASE_SEND_RAW_DATA, NFC_RESULT_NO_TAG)) + { + return NFC_MAKE_RESULT(NFC_RESULT_BASE_GET_TAG_INFO, NFC_RESULT_TAG_INFO_TIMEOUT); + } + + // TODO convert the rest of the results + return result; } void __NFCGetTagInfoCallback(PPCInterpreter_t* hCPU) @@ -465,8 +477,7 @@ namespace nfc NFCContext* ctx = &gNFCContexts[chan]; - // TODO convert error - error = error; + error = __NFCConvertGetTagInfoResult(error); if (error == 0 && ctx->tag) { // this is usually parsed from response data @@ -496,7 +507,7 @@ namespace nfc ctx->getTagInfoCallback = callback; sint32 result = NFCSendRawData(chan, true, discoveryTimeout, 1000U, 0, 0, nullptr, RPLLoader_MakePPCCallable(__NFCGetTagInfoCallback), context); - return result; // TODO convert result + return __NFCConvertGetTagInfoResult(result); } sint32 NFCSendRawData(uint32 chan, bool startDiscovery, uint32 discoveryTimeout, uint32 commandTimeout, uint32 commandSize, uint32 responseSize, void* commandData, MPTR callback, void* context) @@ -507,26 +518,26 @@ namespace nfc if (!NFCIsInit(chan)) { - return -0x9E0; + return NFC_MAKE_RESULT(NFC_RESULT_BASE_SEND_RAW_DATA, NFC_RESULT_UNINITIALIZED); } // Only allow discovery if (!startDiscovery) { - return -0x9DC; + return NFC_MAKE_RESULT(NFC_RESULT_BASE_SEND_RAW_DATA, NFC_RESULT_INVALID_MODE); } if (NFCGetMode(chan) == NFC_MODE_ACTIVE && NFCSetMode(chan, NFC_MODE_IDLE) < 0) { - return -0x9DC; + return NFC_MAKE_RESULT(NFC_RESULT_BASE_SEND_RAW_DATA, NFC_RESULT_INVALID_MODE); } if (ctx->state != NFC_STATE_IDLE) { - return -0x9DF; + return NFC_MAKE_RESULT(NFC_RESULT_BASE_SEND_RAW_DATA, NFC_RESULT_INVALID_STATE); } - ctx->state = NFC_STATE_RAW; + ctx->state = NFC_STATE_SEND_RAW_DATA; ctx->rawCallback = callback; ctx->rawContext = context; @@ -540,7 +551,7 @@ namespace nfc ctx->discoveryTimeout = std::chrono::system_clock::now() + std::chrono::milliseconds(discoveryTimeout); } - return 0; + return NFC_RESULT_SUCCESS; } sint32 NFCRead(uint32 chan, uint32 discoveryTimeout, NFCUid* uid, NFCUid* uidMask, MPTR callback, void* context) @@ -551,21 +562,19 @@ namespace nfc if (!NFCIsInit(chan)) { - return -0x1E0; + return NFC_MAKE_RESULT(NFC_RESULT_BASE_READ, NFC_RESULT_UNINITIALIZED); } if (NFCGetMode(chan) == NFC_MODE_ACTIVE && NFCSetMode(chan, NFC_MODE_IDLE) < 0) { - return -0x1DC; + return NFC_MAKE_RESULT(NFC_RESULT_BASE_READ, NFC_RESULT_INVALID_MODE); } if (ctx->state != NFC_STATE_IDLE) { - return -0x1DF; + return NFC_MAKE_RESULT(NFC_RESULT_BASE_READ, NFC_RESULT_INVALID_STATE); } - cemuLog_log(LogType::NFC, "starting read"); - ctx->state = NFC_STATE_READ; ctx->readCallback = callback; ctx->readContext = context; @@ -583,7 +592,7 @@ namespace nfc memcpy(&ctx->filter.uid, uid, sizeof(*uid)); memcpy(&ctx->filter.mask, uidMask, sizeof(*uidMask)); - return 0; + return NFC_RESULT_SUCCESS; } sint32 NFCWrite(uint32 chan, uint32 discoveryTimeout, NFCUid* uid, NFCUid* uidMask, uint32 size, void* data, MPTR callback, void* context) @@ -594,17 +603,17 @@ namespace nfc if (!NFCIsInit(chan)) { - return -0x2e0; + return NFC_MAKE_RESULT(NFC_RESULT_BASE_WRITE, NFC_RESULT_UNINITIALIZED); } if (NFCGetMode(chan) == NFC_MODE_ACTIVE && NFCSetMode(chan, NFC_MODE_IDLE) < 0) { - return -0x2dc; + return NFC_MAKE_RESULT(NFC_RESULT_BASE_WRITE, NFC_RESULT_INVALID_MODE); } if (ctx->state != NFC_STATE_IDLE) { - return -0x1df; + return NFC_MAKE_RESULT(NFC_RESULT_BASE_WRITE, NFC_RESULT_INVALID_STATE); } // Create unknown record which contains the rw area @@ -634,7 +643,7 @@ namespace nfc memcpy(&ctx->filter.uid, uid, sizeof(*uid)); memcpy(&ctx->filter.mask, uidMask, sizeof(*uidMask)); - return 0; + return NFC_RESULT_SUCCESS; } void Initialize() @@ -668,14 +677,14 @@ namespace nfc auto nfcData = FileStream::LoadIntoMemory(filePath); if (!nfcData) { - *nfcError = NFC_ERROR_NO_ACCESS; + *nfcError = NFC_TOUCH_TAG_ERROR_NO_ACCESS; return false; } ctx->tag = TagV0::FromBytes(std::as_bytes(std::span(nfcData->data(), nfcData->size()))); if (!ctx->tag) { - *nfcError = NFC_ERROR_INVALID_FILE_FORMAT; + *nfcError = NFC_TOUCH_TAG_ERROR_INVALID_FILE_FORMAT; return false; } @@ -683,7 +692,7 @@ namespace nfc ctx->tagPath = filePath; ctx->touchTime = std::chrono::system_clock::now(); - *nfcError = NFC_ERROR_NONE; + *nfcError = NFC_TOUCH_TAG_ERROR_NONE; return true; } } diff --git a/src/Cafe/OS/libs/nfc/nfc.h b/src/Cafe/OS/libs/nfc/nfc.h index 2ebdd2a4..ea959cd1 100644 --- a/src/Cafe/OS/libs/nfc/nfc.h +++ b/src/Cafe/OS/libs/nfc/nfc.h @@ -1,9 +1,39 @@ #pragma once // CEMU NFC error codes -#define NFC_ERROR_NONE (0) -#define NFC_ERROR_NO_ACCESS (1) -#define NFC_ERROR_INVALID_FILE_FORMAT (2) +#define NFC_TOUCH_TAG_ERROR_NONE (0) +#define NFC_TOUCH_TAG_ERROR_NO_ACCESS (1) +#define NFC_TOUCH_TAG_ERROR_INVALID_FILE_FORMAT (2) + +// NFC result base +#define NFC_RESULT_BASE_INIT (-0x100) +#define NFC_RESULT_BASE_READ (-0x200) +#define NFC_RESULT_BASE_WRITE (-0x300) +#define NFC_RESULT_BASE_FORMAT (-0x400) +#define NFC_RESULT_BASE_SET_READ_ONLY (-0x500) +#define NFC_RESULT_BASE_IS_TAG_PRESENT (-0x600) +#define NFC_RESULT_BASE_ABORT (-0x700) +#define NFC_RESULT_BASE_SHUTDOWN (-0x800) +#define NFC_RESULT_BASE_DETECT (-0x900) +#define NFC_RESULT_BASE_SEND_RAW_DATA (-0xA00) +#define NFC_RESULT_BASE_SET_MODE (-0xB00) +#define NFC_RESULT_BASE_TAG_PARSE (-0xC00) +#define NFC_RESULT_BASE_GET_TAG_INFO (-0x1400) + +// NFC result status +#define NFC_RESULT_NO_TAG (0x01) +#define NFC_RESULT_INVALID_TAG (0x02) +#define NFC_RESULT_UID_MISMATCH (0x0A) +#define NFC_RESULT_UNINITIALIZED (0x20) +#define NFC_RESULT_INVALID_STATE (0x21) +#define NFC_RESULT_INVALID_MODE (0x24) +#define NFC_RESULT_TAG_INFO_TIMEOUT (0x7A) + +// Result macros +#define NFC_RESULT_SUCCESS (0) +#define NFC_RESULT_BASE_MASK (0xFFFFFF00) +#define NFC_RESULT_MASK (0x000000FF) +#define NFC_MAKE_RESULT(base, result) ((base) | (result)) #define NFC_PROTOCOL_T1T 0x1 #define NFC_PROTOCOL_T2T 0x2 diff --git a/src/Cafe/OS/libs/ntag/ntag.cpp b/src/Cafe/OS/libs/ntag/ntag.cpp index 18ed798a..24617791 100644 --- a/src/Cafe/OS/libs/ntag/ntag.cpp +++ b/src/Cafe/OS/libs/ntag/ntag.cpp @@ -26,10 +26,27 @@ namespace ntag MPTR gReadCallbacks[2]; MPTR gWriteCallbacks[2]; - sint32 __NTAGConvertNFCError(sint32 error) + sint32 __NTAGConvertNFCResult(sint32 result) { - // TODO - return error; + if (result == NFC_RESULT_SUCCESS) + { + return NTAG_RESULT_SUCCESS; + } + + switch (result & NFC_RESULT_MASK) + { + case NFC_RESULT_UNINITIALIZED: + return NTAG_RESULT_UNINITIALIZED; + case NFC_RESULT_INVALID_STATE: + return NTAG_RESULT_INVALID_STATE; + case NFC_RESULT_NO_TAG: + return NTAG_RESULT_NO_TAG; + case NFC_RESULT_UID_MISMATCH: + return NTAG_RESULT_UID_MISMATCH; + } + + // TODO convert more errors + return NTAG_RESULT_INVALID; } sint32 NTAGInit(uint32 chan) @@ -40,7 +57,7 @@ namespace ntag sint32 NTAGInitEx(uint32 chan) { sint32 result = nfc::NFCInitEx(chan, 1); - return __NTAGConvertNFCError(result); + return __NTAGConvertNFCResult(result); } sint32 NTAGShutdown(uint32 chan) @@ -58,7 +75,7 @@ namespace ntag gReadCallbacks[chan] = MPTR_NULL; gWriteCallbacks[chan] = MPTR_NULL; - return __NTAGConvertNFCError(result); + return __NTAGConvertNFCResult(result); } bool NTAGIsInit(uint32 chan) @@ -105,7 +122,7 @@ namespace ntag ppcDefineParamS32(error, 1); ppcDefineParamPtr(context, void, 2); - PPCCoreCallback(gAbortCallbacks[chan], chan, __NTAGConvertNFCError(error), context); + PPCCoreCallback(gAbortCallbacks[chan], chan, __NTAGConvertNFCResult(error), context); osLib_returnFromFunction(hCPU, 0); } @@ -118,7 +135,7 @@ namespace ntag gAbortCallbacks[chan] = callback; sint32 result = nfc::NFCAbort(chan, RPLLoader_MakePPCCallable(__NTAGAbortCallback), context); - return __NTAGConvertNFCError(result); + return __NTAGConvertNFCResult(result); } bool __NTAGRawDataToNfcData(iosu::ccr_nfc::CCRNFCCryptData* raw, iosu::ccr_nfc::CCRNFCCryptData* nfc) @@ -370,7 +387,7 @@ namespace ntag readResult->readOnly = readOnly; - error = __NTAGConvertNFCError(error); + error = __NTAGConvertNFCResult(error); if (error == 0) { memset(rwData.GetPointer(), 0, 0x1C8); @@ -430,7 +447,7 @@ namespace ntag } sint32 result = nfc::NFCRead(chan, timeout, &_uid, &_uidMask, RPLLoader_MakePPCCallable(__NTAGReadCallback), context); - return __NTAGConvertNFCError(result); + return __NTAGConvertNFCResult(result); } sint32 __NTAGEncryptData(void* encryptedData, const void* rawData) @@ -512,7 +529,7 @@ namespace ntag ppcDefineParamS32(error, 1); ppcDefineParamPtr(context, void, 2); - PPCCoreCallback(gWriteCallbacks[chan], chan, __NTAGConvertNFCError(error), context); + PPCCoreCallback(gWriteCallbacks[chan], chan, __NTAGConvertNFCResult(error), context); osLib_returnFromFunction(hCPU, 0); } @@ -538,7 +555,7 @@ namespace ntag NTAGAreaHeader roHeader; uint8 writeBuffer[0x1C8]{}; - error = __NTAGConvertNFCError(error); + error = __NTAGConvertNFCResult(error); if (error == 0) { // Copy raw and locked data into a contigous buffer @@ -576,7 +593,7 @@ namespace ntag return; } - error = __NTAGConvertNFCError(error); + error = __NTAGConvertNFCResult(error); } PPCCoreCallback(gWriteCallbacks[chan], chan, error, context); @@ -600,7 +617,7 @@ namespace ntag memcpy(gWriteData[chan].data, rwData, rwSize); sint32 result = nfc::NFCRead(chan, timeout, &gWriteData[chan].uid, &gWriteData[chan].uidMask, RPLLoader_MakePPCCallable(__NTAGReadBeforeWriteCallback), context); - return __NTAGConvertNFCError(result); + return __NTAGConvertNFCResult(result); } sint32 NTAGFormat(uint32 chan, uint32 timeout, nfc::NFCUid* uid, uint32 rwSize, void* rwData, MPTR callback, void* context) @@ -608,7 +625,9 @@ namespace ntag cemu_assert(chan < 2); // TODO - return -1; + cemu_assert_debug(false); + + return NTAG_RESULT_INVALID; } void Initialize() diff --git a/src/Cafe/OS/libs/ntag/ntag.h b/src/Cafe/OS/libs/ntag/ntag.h index 697c065e..68f1801b 100644 --- a/src/Cafe/OS/libs/ntag/ntag.h +++ b/src/Cafe/OS/libs/ntag/ntag.h @@ -1,6 +1,13 @@ #pragma once #include "Cafe/OS/libs/nfc/nfc.h" +#define NTAG_RESULT_SUCCESS (0) +#define NTAG_RESULT_UNINITIALIZED (-0x3E7) +#define NTAG_RESULT_INVALID_STATE (-0x3E6) +#define NTAG_RESULT_NO_TAG (-0x3E5) +#define NTAG_RESULT_INVALID (-0x3E1) +#define NTAG_RESULT_UID_MISMATCH (-0x3DB) + namespace ntag { struct NTAGFormatSettings diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index cb2e988d..33e2cdc1 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -269,10 +269,10 @@ public: } else { - if (nfcError == NFC_ERROR_NO_ACCESS) + if (nfcError == NFC_TOUCH_TAG_ERROR_NO_ACCESS) wxMessageBox(_("Cannot open file"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR); - else if (nfcError == NFC_ERROR_INVALID_FILE_FORMAT) - wxMessageBox(_("Not a valid NFC NTAG215 file"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR); + else if (nfcError == NFC_TOUCH_TAG_ERROR_INVALID_FILE_FORMAT) + wxMessageBox(_("Not a valid NFC file"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR); return false; } } @@ -751,10 +751,10 @@ void MainWindow::OnNFCMenu(wxCommandEvent& event) uint32 nfcError; if (nfc::TouchTagFromFile(_utf8ToPath(wxStrFilePath.utf8_string()), &nfcError) == false) { - if (nfcError == NFC_ERROR_NO_ACCESS) + if (nfcError == NFC_TOUCH_TAG_ERROR_NO_ACCESS) wxMessageBox(_("Cannot open file")); - else if (nfcError == NFC_ERROR_INVALID_FILE_FORMAT) - wxMessageBox(_("Not a valid NFC NTAG215 file")); + else if (nfcError == NFC_TOUCH_TAG_ERROR_INVALID_FILE_FORMAT) + wxMessageBox(_("Not a valid NFC file")); } else { @@ -774,10 +774,10 @@ void MainWindow::OnNFCMenu(wxCommandEvent& event) uint32 nfcError = 0; if (nfc::TouchTagFromFile(_utf8ToPath(path), &nfcError) == false) { - if (nfcError == NFC_ERROR_NO_ACCESS) + if (nfcError == NFC_TOUCH_TAG_ERROR_NO_ACCESS) wxMessageBox(_("Cannot open file")); - else if (nfcError == NFC_ERROR_INVALID_FILE_FORMAT) - wxMessageBox(_("Not a valid NFC NTAG215 file")); + else if (nfcError == NFC_TOUCH_TAG_ERROR_INVALID_FILE_FORMAT) + wxMessageBox(_("Not a valid NFC file")); } else { From eb1983daa6e46dfa09ad76eba86c5b636fe0b826 Mon Sep 17 00:00:00 2001 From: GaryOderNichts <12049776+GaryOderNichts@users.noreply.github.com> Date: Sat, 18 May 2024 17:27:49 +0200 Subject: [PATCH 117/299] nfc: Remove backup path --- src/Cafe/OS/libs/nfc/nfc.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/Cafe/OS/libs/nfc/nfc.cpp b/src/Cafe/OS/libs/nfc/nfc.cpp index 818c7339..c6809362 100644 --- a/src/Cafe/OS/libs/nfc/nfc.cpp +++ b/src/Cafe/OS/libs/nfc/nfc.cpp @@ -223,16 +223,8 @@ namespace nfc // Update tag NDEF data ctx->tag->SetNDEFData(ctx->writeMessage.ToBytes()); - // TODO remove this once writing is confirmed working - fs::path newPath = ctx->tagPath; - if (newPath.extension() != ".bak") - { - newPath += ".bak"; - } - cemuLog_log(LogType::NFC, "Saving tag as {}...", newPath.string()); - // open file for writing - FileStream* fs = FileStream::createFile2(newPath); + FileStream* fs = FileStream::openFile2(ctx->tagPath, true); if (!fs) { result = NFC_MAKE_RESULT(NFC_RESULT_BASE_WRITE, 0x22); From a115921b43d39c24fafd387d9c87190168422583 Mon Sep 17 00:00:00 2001 From: GaryOderNichts <12049776+GaryOderNichts@users.noreply.github.com> Date: Sat, 18 May 2024 19:56:56 +0200 Subject: [PATCH 118/299] Fix inconsistency with int types --- src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp | 22 +++++++------- src/Cafe/OS/libs/nfc/TLV.cpp | 12 ++++---- src/Cafe/OS/libs/nfc/TagV0.cpp | 42 +++++++++++++------------- src/Cafe/OS/libs/nfc/TagV0.h | 8 ++--- src/Cafe/OS/libs/nfc/ndef.cpp | 26 ++++++++-------- src/Cafe/OS/libs/nfc/ndef.h | 4 +-- src/Cafe/OS/libs/nfc/nfc.cpp | 4 +-- src/Cafe/OS/libs/nfc/stream.cpp | 12 ++++---- 8 files changed, 65 insertions(+), 65 deletions(-) diff --git a/src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp b/src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp index ff8ba2b1..1ceb16dc 100644 --- a/src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp +++ b/src/Cafe/IOSU/ccr_nfc/iosu_ccr_nfc.cpp @@ -71,9 +71,9 @@ namespace iosu return CCR_NFC_ERROR; } - sint32 CCRNFCAESCTRCrypt(const uint8* key, const void* ivNonce, const void* inData, uint32_t inSize, void* outData, uint32_t outSize) + sint32 CCRNFCAESCTRCrypt(const uint8* key, const void* ivNonce, const void* inData, uint32 inSize, void* outData, uint32 outSize) { - uint8_t tmpIv[0x10]; + uint8 tmpIv[0x10]; memcpy(tmpIv, ivNonce, sizeof(tmpIv)); memcpy(outData, inData, inSize); @@ -81,7 +81,7 @@ namespace iosu return 0; } - sint32 __CCRNFCGenerateKey(const uint8* hmacKey, uint32 hmacKeySize, const uint8* name, uint32_t nameSize, const uint8* inData, uint32_t inSize, uint8* outData, uint32_t outSize) + sint32 __CCRNFCGenerateKey(const uint8* hmacKey, uint32 hmacKeySize, const uint8* name, uint32 nameSize, const uint8* inData, uint32 inSize, uint8* outData, uint32 outSize) { if (nameSize != 0xe || outSize != 0x40) { @@ -89,13 +89,13 @@ namespace iosu } // Create a buffer containing 2 counter bytes, the key name, and the key data - uint8_t buffer[0x50]; + uint8 buffer[0x50]; buffer[0] = 0; buffer[1] = 0; memcpy(buffer + 2, name, nameSize); memcpy(buffer + nameSize + 2, inData, inSize); - uint16_t counter = 0; + uint16 counter = 0; while (outSize > 0) { // Set counter bytes and increment counter @@ -118,9 +118,9 @@ namespace iosu sint32 __CCRNFCGenerateInternalKeys(const CCRNFCCryptData* in, const uint8* keyGenSalt) { - uint8_t lockedSecretBuffer[0x40] = { 0 }; - uint8_t unfixedInfosBuffer[0x40] = { 0 }; - uint8_t outBuffer[0x40] = { 0 }; + uint8 lockedSecretBuffer[0x40] = { 0 }; + uint8 unfixedInfosBuffer[0x40] = { 0 }; + uint8 outBuffer[0x40] = { 0 }; // Fill the locked secret buffer memcpy(lockedSecretBuffer, sLockedSecretMagicBytes, sizeof(sLockedSecretMagicBytes)); @@ -193,7 +193,7 @@ namespace iosu sint32 __CCRNFCCryptData(const CCRNFCCryptData* in, CCRNFCCryptData* out, bool decrypt) { // Decrypt key generation salt - uint8_t keyGenSalt[0x20]; + uint8 keyGenSalt[0x20]; sint32 res = CCRNFCAESCTRCrypt(sNfcKey, sNfcKeyIV, in->data + in->keyGenSaltOffset, 0x20, keyGenSalt, sizeof(keyGenSalt)); if (res != 0) { @@ -227,7 +227,7 @@ namespace iosu } // Verify HMACs - uint8_t hmacBuffer[0x20]; + uint8 hmacBuffer[0x20]; uint32 hmacLen = sizeof(hmacBuffer); if (!HMAC(EVP_sha256(), sLockedSecretInternalHmacKey, sizeof(sLockedSecretInternalHmacKey), out->data + in->lockedSecretHmacOffset + 0x20, (in->dataSize - in->lockedSecretHmacOffset) - 0x20, hmacBuffer, &hmacLen)) @@ -258,7 +258,7 @@ namespace iosu } else { - uint8_t hmacBuffer[0x20]; + uint8 hmacBuffer[0x20]; uint32 hmacLen = sizeof(hmacBuffer); if (!HMAC(EVP_sha256(), sLockedSecretInternalHmacKey, sizeof(sLockedSecretInternalHmacKey), out->data + in->lockedSecretHmacOffset + 0x20, (in->dataSize - in->lockedSecretHmacOffset) - 0x20, hmacBuffer, &hmacLen)) diff --git a/src/Cafe/OS/libs/nfc/TLV.cpp b/src/Cafe/OS/libs/nfc/TLV.cpp index 99536428..2650858d 100644 --- a/src/Cafe/OS/libs/nfc/TLV.cpp +++ b/src/Cafe/OS/libs/nfc/TLV.cpp @@ -25,7 +25,7 @@ std::vector<TLV> TLV::FromBytes(const std::span<std::byte>& data) while (stream.GetRemaining() > 0 && !hasTerminator) { // Read the tag - uint8_t byte; + uint8 byte; stream >> byte; Tag tag = static_cast<Tag>(byte); @@ -43,7 +43,7 @@ std::vector<TLV> TLV::FromBytes(const std::span<std::byte>& data) default: { // Read the length - uint16_t length; + uint16 length; stream >> byte; length = byte; @@ -85,7 +85,7 @@ std::vector<std::byte> TLV::ToBytes() const VectorStream stream(bytes, std::endian::big); // Write tag - stream << std::uint8_t(mTag); + stream << uint8(mTag); switch (mTag) { @@ -99,12 +99,12 @@ std::vector<std::byte> TLV::ToBytes() const // Write length (decide if as a 8-bit or 16-bit value) if (mValue.size() >= 0xff) { - stream << std::uint8_t(0xff); - stream << std::uint16_t(mValue.size()); + stream << uint8(0xff); + stream << uint16(mValue.size()); } else { - stream << std::uint8_t(mValue.size()); + stream << uint8(mValue.size()); } // Write value diff --git a/src/Cafe/OS/libs/nfc/TagV0.cpp b/src/Cafe/OS/libs/nfc/TagV0.cpp index 8b5a8143..41b5c7a0 100644 --- a/src/Cafe/OS/libs/nfc/TagV0.cpp +++ b/src/Cafe/OS/libs/nfc/TagV0.cpp @@ -9,17 +9,17 @@ namespace constexpr std::size_t kTagSize = 512u; constexpr std::size_t kMaxBlockCount = kTagSize / sizeof(TagV0::Block); -constexpr std::uint8_t kLockbyteBlock0 = 0xe; -constexpr std::uint8_t kLockbytesStart0 = 0x0; -constexpr std::uint8_t kLockbytesEnd0 = 0x2; -constexpr std::uint8_t kLockbyteBlock1 = 0xf; -constexpr std::uint8_t kLockbytesStart1 = 0x2; -constexpr std::uint8_t kLockbytesEnd1 = 0x8; +constexpr uint8 kLockbyteBlock0 = 0xe; +constexpr uint8 kLockbytesStart0 = 0x0; +constexpr uint8 kLockbytesEnd0 = 0x2; +constexpr uint8 kLockbyteBlock1 = 0xf; +constexpr uint8 kLockbytesStart1 = 0x2; +constexpr uint8 kLockbytesEnd1 = 0x8; -constexpr std::uint8_t kNDEFMagicNumber = 0xe1; +constexpr uint8 kNDEFMagicNumber = 0xe1; // These blocks are not part of the locked area -constexpr bool IsBlockLockedOrReserved(std::uint8_t blockIdx) +constexpr bool IsBlockLockedOrReserved(uint8 blockIdx) { // Block 0 is the UID if (blockIdx == 0x0) @@ -153,7 +153,7 @@ std::vector<std::byte> TagV0::ToBytes() const // The rest will be the data area auto dataIterator = dataArea.begin(); - for (std::uint8_t currentBlock = 0; currentBlock < kMaxBlockCount; currentBlock++) + for (uint8 currentBlock = 0; currentBlock < kMaxBlockCount; currentBlock++) { // All blocks which aren't locked make up the dataArea if (!IsBlockLocked(currentBlock)) @@ -189,15 +189,15 @@ void TagV0::SetNDEFData(const std::span<const std::byte>& data) bool TagV0::ParseLockedArea(const std::span<const std::byte>& data) { - std::uint8_t currentBlock = 0; + uint8 currentBlock = 0; // Start by parsing the first set of lock bytes - for (std::uint8_t i = kLockbytesStart0; i < kLockbytesEnd0; i++) + for (uint8 i = kLockbytesStart0; i < kLockbytesEnd0; i++) { - std::uint8_t lockByte = std::uint8_t(data[kLockbyteBlock0 * sizeof(Block) + i]); + uint8 lockByte = uint8(data[kLockbyteBlock0 * sizeof(Block) + i]); // Iterate over the individual bits in the lock byte - for (std::uint8_t j = 0; j < 8; j++) + for (uint8 j = 0; j < 8; j++) { // Is block locked? if (lockByte & (1u << j)) @@ -221,11 +221,11 @@ bool TagV0::ParseLockedArea(const std::span<const std::byte>& data) } // Parse the second set of lock bytes - for (std::uint8_t i = kLockbytesStart1; i < kLockbytesEnd1; i++) { - std::uint8_t lockByte = std::uint8_t(data[kLockbyteBlock1 * sizeof(Block) + i]); + for (uint8 i = kLockbytesStart1; i < kLockbytesEnd1; i++) { + uint8 lockByte = uint8(data[kLockbyteBlock1 * sizeof(Block) + i]); // Iterate over the individual bits in the lock byte - for (std::uint8_t j = 0; j < 8; j++) + for (uint8 j = 0; j < 8; j++) { // Is block locked? if (lockByte & (1u << j)) @@ -251,14 +251,14 @@ bool TagV0::ParseLockedArea(const std::span<const std::byte>& data) return true; } -bool TagV0::IsBlockLocked(std::uint8_t blockIdx) const +bool TagV0::IsBlockLocked(uint8 blockIdx) const { return mLockedBlocks.contains(blockIdx) || IsBlockLockedOrReserved(blockIdx); } bool TagV0::ParseDataArea(const std::span<const std::byte>& data, std::vector<std::byte>& dataArea) { - for (std::uint8_t currentBlock = 0; currentBlock < kMaxBlockCount; currentBlock++) + for (uint8 currentBlock = 0; currentBlock < kMaxBlockCount; currentBlock++) { // All blocks which aren't locked make up the dataArea if (!IsBlockLocked(currentBlock)) @@ -274,7 +274,7 @@ bool TagV0::ParseDataArea(const std::span<const std::byte>& data, std::vector<st bool TagV0::ValidateCapabilityContainer() { // NDEF Magic Number - std::uint8_t nmn = mCapabilityContainer[0]; + uint8 nmn = mCapabilityContainer[0]; if (nmn != kNDEFMagicNumber) { cemuLog_log(LogType::Force, "Error: CC: Invalid NDEF Magic Number"); @@ -282,7 +282,7 @@ bool TagV0::ValidateCapabilityContainer() } // Version Number - std::uint8_t vno = mCapabilityContainer[1]; + uint8 vno = mCapabilityContainer[1]; if (vno >> 4 != 1) { cemuLog_log(LogType::Force, "Error: CC: Invalid Version Number"); @@ -290,7 +290,7 @@ bool TagV0::ValidateCapabilityContainer() } // Tag memory size - std::uint8_t tms = mCapabilityContainer[2]; + uint8 tms = mCapabilityContainer[2]; if (8u * (tms + 1) < kTagSize) { cemuLog_log(LogType::Force, "Error: CC: Incomplete tag memory size"); diff --git a/src/Cafe/OS/libs/nfc/TagV0.h b/src/Cafe/OS/libs/nfc/TagV0.h index 1d0e88d7..72c321b6 100644 --- a/src/Cafe/OS/libs/nfc/TagV0.h +++ b/src/Cafe/OS/libs/nfc/TagV0.h @@ -26,13 +26,13 @@ public: private: bool ParseLockedArea(const std::span<const std::byte>& data); - bool IsBlockLocked(std::uint8_t blockIdx) const; + bool IsBlockLocked(uint8 blockIdx) const; bool ParseDataArea(const std::span<const std::byte>& data, std::vector<std::byte>& dataArea); bool ValidateCapabilityContainer(); - std::map<std::uint8_t, Block> mLockedOrReservedBlocks; - std::map<std::uint8_t, Block> mLockedBlocks; - std::array<std::uint8_t, 0x4> mCapabilityContainer; + std::map<uint8, Block> mLockedOrReservedBlocks; + std::map<uint8, Block> mLockedBlocks; + std::array<uint8, 0x4> mCapabilityContainer; std::vector<TLV> mTLVs; std::size_t mNdefTlvIdx; std::vector<std::byte> mLockedArea; diff --git a/src/Cafe/OS/libs/nfc/ndef.cpp b/src/Cafe/OS/libs/nfc/ndef.cpp index 32097cfd..60be5811 100644 --- a/src/Cafe/OS/libs/nfc/ndef.cpp +++ b/src/Cafe/OS/libs/nfc/ndef.cpp @@ -19,20 +19,20 @@ namespace ndef Record rec; // Read record header - uint8_t recHdr; + uint8 recHdr; stream >> recHdr; rec.mFlags = recHdr & ~NDEF_TNF_MASK; rec.mTNF = static_cast<TypeNameFormat>(recHdr & NDEF_TNF_MASK); // Type length - uint8_t typeLen; + uint8 typeLen; stream >> typeLen; // Payload length; - uint32_t payloadLen; + uint32 payloadLen; if (recHdr & NDEF_SR) { - uint8_t len; + uint8 len; stream >> len; payloadLen = len; } @@ -48,7 +48,7 @@ namespace ndef } // ID length - uint8_t idLen = 0; + uint8 idLen = 0; if (recHdr & NDEF_IL) { stream >> idLen; @@ -81,35 +81,35 @@ namespace ndef return rec; } - std::vector<std::byte> Record::ToBytes(uint8_t flags) const + std::vector<std::byte> Record::ToBytes(uint8 flags) const { std::vector<std::byte> bytes; VectorStream stream(bytes, std::endian::big); // Combine flags (clear message begin and end flags) - std::uint8_t finalFlags = mFlags & ~(NDEF_MB | NDEF_ME); + uint8 finalFlags = mFlags & ~(NDEF_MB | NDEF_ME); finalFlags |= flags; // Write flags + tnf - stream << std::uint8_t(finalFlags | std::uint8_t(mTNF)); + stream << uint8(finalFlags | uint8(mTNF)); // Type length - stream << std::uint8_t(mType.size()); + stream << uint8(mType.size()); // Payload length if (IsShort()) { - stream << std::uint8_t(mPayload.size()); + stream << uint8(mPayload.size()); } else { - stream << std::uint32_t(mPayload.size()); + stream << uint32(mPayload.size()); } // ID length if (mFlags & NDEF_IL) { - stream << std::uint8_t(mID.size()); + stream << uint8(mID.size()); } // Type @@ -249,7 +249,7 @@ namespace ndef for (std::size_t i = 0; i < mRecords.size(); i++) { - std::uint8_t flags = 0; + uint8 flags = 0; // Add message begin flag to first record if (i == 0) diff --git a/src/Cafe/OS/libs/nfc/ndef.h b/src/Cafe/OS/libs/nfc/ndef.h index b5f38b17..398feb54 100644 --- a/src/Cafe/OS/libs/nfc/ndef.h +++ b/src/Cafe/OS/libs/nfc/ndef.h @@ -39,7 +39,7 @@ namespace ndef virtual ~Record(); static std::optional<Record> FromStream(Stream& stream); - std::vector<std::byte> ToBytes(uint8_t flags = 0) const; + std::vector<std::byte> ToBytes(uint8 flags = 0) const; TypeNameFormat GetTNF() const; const std::vector<std::byte>& GetID() const; @@ -55,7 +55,7 @@ namespace ndef bool IsShort() const; private: - uint8_t mFlags; + uint8 mFlags; TypeNameFormat mTNF; std::vector<std::byte> mID; std::vector<std::byte> mType; diff --git a/src/Cafe/OS/libs/nfc/nfc.cpp b/src/Cafe/OS/libs/nfc/nfc.cpp index c6809362..fcb1d8d0 100644 --- a/src/Cafe/OS/libs/nfc/nfc.cpp +++ b/src/Cafe/OS/libs/nfc/nfc.cpp @@ -149,9 +149,9 @@ namespace nfc StackAllocator<NFCUid> uid; bool readOnly = false; uint32 dataSize = 0; - StackAllocator<uint8_t, 0x200> data; + StackAllocator<uint8, 0x200> data; uint32 lockedDataSize = 0; - StackAllocator<uint8_t, 0x200> lockedData; + StackAllocator<uint8, 0x200> lockedData; if (ctx->tag) { diff --git a/src/Cafe/OS/libs/nfc/stream.cpp b/src/Cafe/OS/libs/nfc/stream.cpp index 73c2880f..dd6de7ad 100644 --- a/src/Cafe/OS/libs/nfc/stream.cpp +++ b/src/Cafe/OS/libs/nfc/stream.cpp @@ -28,7 +28,7 @@ std::endian Stream::GetEndianness() const Stream& Stream::operator>>(bool& val) { - std::uint8_t i; + uint8 i; *this >> i; val = !!i; @@ -37,7 +37,7 @@ Stream& Stream::operator>>(bool& val) Stream& Stream::operator>>(float& val) { - std::uint32_t i; + uint32 i; *this >> i; val = std::bit_cast<float>(i); @@ -46,7 +46,7 @@ Stream& Stream::operator>>(float& val) Stream& Stream::operator>>(double& val) { - std::uint64_t i; + uint64 i; *this >> i; val = std::bit_cast<double>(i); @@ -55,7 +55,7 @@ Stream& Stream::operator>>(double& val) Stream& Stream::operator<<(bool val) { - std::uint8_t i = val; + uint8 i = val; *this >> i; return *this; @@ -63,7 +63,7 @@ Stream& Stream::operator<<(bool val) Stream& Stream::operator<<(float val) { - std::uint32_t i = std::bit_cast<std::uint32_t>(val); + uint32 i = std::bit_cast<uint32>(val); *this >> i; return *this; @@ -71,7 +71,7 @@ Stream& Stream::operator<<(float val) Stream& Stream::operator<<(double val) { - std::uint64_t i = std::bit_cast<std::uint64_t>(val); + uint64 i = std::bit_cast<uint64>(val); *this >> i; return *this; From 964d2acb44c64015637d1a8713cc2e96bf53bb48 Mon Sep 17 00:00:00 2001 From: GaryOderNichts <12049776+GaryOderNichts@users.noreply.github.com> Date: Sat, 18 May 2024 20:47:09 +0200 Subject: [PATCH 119/299] Filestream_unix: Include cstdarg --- src/Common/unix/FileStream_unix.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Common/unix/FileStream_unix.cpp b/src/Common/unix/FileStream_unix.cpp index 2dba17b7..4bc9b526 100644 --- a/src/Common/unix/FileStream_unix.cpp +++ b/src/Common/unix/FileStream_unix.cpp @@ -1,4 +1,5 @@ #include "Common/unix/FileStream_unix.h" +#include <cstdarg> fs::path findPathCI(const fs::path& path) { From c913a59c7a7140eb7b148611362eee519217dc27 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Wed, 22 May 2024 04:11:02 +0200 Subject: [PATCH 120/299] TitleList: Add homebrew title type (#1203) --- src/Cafe/TitleList/TitleId.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Cafe/TitleList/TitleId.h b/src/Cafe/TitleList/TitleId.h index b7f63b13..073472d9 100644 --- a/src/Cafe/TitleList/TitleId.h +++ b/src/Cafe/TitleList/TitleId.h @@ -13,6 +13,7 @@ public: /* 00 */ BASE_TITLE = 0x00, // eShop and disc titles /* 02 */ BASE_TITLE_DEMO = 0x02, /* 0E */ BASE_TITLE_UPDATE = 0x0E, // update for BASE_TITLE (and maybe BASE_TITLE_DEMO?) + /* 0F */ HOMEBREW = 0x0F, /* 0C */ AOC = 0x0C, // DLC /* 10 */ SYSTEM_TITLE = 0x10, // eShop etc /* 1B */ SYSTEM_DATA = 0x1B, @@ -43,6 +44,8 @@ public: return TITLE_TYPE::BASE_TITLE_DEMO; case 0x0E: return TITLE_TYPE::BASE_TITLE_UPDATE; + case 0x0F: + return TITLE_TYPE::HOMEBREW; case 0x0C: return TITLE_TYPE::AOC; case 0x10: From 523a1652df4e8e7a18466e1d2668573dc06909af Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Wed, 22 May 2024 04:23:33 +0200 Subject: [PATCH 121/299] OpenGL: Restore ProgramBinary cache for GL shaders (#1209) --- src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp index 3d46f206..cae53140 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.cpp @@ -23,16 +23,13 @@ bool RendererShaderGL::loadBinary() cemu_assert_debug(m_baseHash != 0); uint64 h1, h2; GenerateShaderPrecompiledCacheFilename(m_type, m_baseHash, m_auxHash, h1, h2); - sint32 fileSize = 0; std::vector<uint8> cacheFileData; if (!s_programBinaryCache->GetFile({h1, h2 }, cacheFileData)) return false; - if (fileSize < sizeof(uint32)) - { + if (cacheFileData.size() <= sizeof(uint32)) return false; - } - uint32 shaderBinFormat = *(uint32*)(cacheFileData.data() + 0); + uint32 shaderBinFormat = *(uint32*)(cacheFileData.data()); m_program = glCreateProgram(); glProgramBinary(m_program, shaderBinFormat, cacheFileData.data()+4, cacheFileData.size()-4); From b048a1fd9effb59a212c8e1c8ad5069a953f3f3b Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 22 May 2024 05:08:03 +0200 Subject: [PATCH 122/299] Use CURLOPT_USERAGENT instead of manually setting User-Agent --- src/Cemu/napi/napi_helper.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Cemu/napi/napi_helper.cpp b/src/Cemu/napi/napi_helper.cpp index e498d07f..164de7e5 100644 --- a/src/Cemu/napi/napi_helper.cpp +++ b/src/Cemu/napi/napi_helper.cpp @@ -388,9 +388,8 @@ bool CurlSOAPHelper::submitRequest() headers = curl_slist_append(headers, "Accept-Charset: UTF-8"); headers = curl_slist_append(headers, fmt::format("SOAPAction: urn:{}.wsapi.broadon.com/{}", m_serviceType, m_requestMethod).c_str()); headers = curl_slist_append(headers, "Accept: */*"); - headers = curl_slist_append(headers, "User-Agent: EVL NUP 040800 Sep 18 2012 20:20:02"); - curl_easy_setopt(m_curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(m_curl, CURLOPT_USERAGENT, "EVL NUP 040800 Sep 18 2012 20:20:02"); // send request auto res = curl_easy_perform(m_curl); From 917ea2ef234f583032dab84a6f0f371709823ce1 Mon Sep 17 00:00:00 2001 From: Cemu-Language CI <github-actions@github.com> Date: Thu, 23 May 2024 17:48:04 +0000 Subject: [PATCH 123/299] Update translation files --- bin/resources/de/cemu.mo | Bin 65048 -> 65571 bytes bin/resources/es/cemu.mo | Bin 62413 -> 65733 bytes bin/resources/fr/cemu.mo | Bin 63716 -> 72178 bytes bin/resources/hu/cemu.mo | Bin 62167 -> 71267 bytes bin/resources/ko/cemu.mo | Bin 69116 -> 69784 bytes bin/resources/ru/cemu.mo | Bin 46500 -> 89284 bytes bin/resources/zh/cemu.mo | Bin 56868 -> 58070 bytes 7 files changed, 0 insertions(+), 0 deletions(-) diff --git a/bin/resources/de/cemu.mo b/bin/resources/de/cemu.mo index 918fcd3546c99e9c385fe726bbd91c9e95b19270..8dc4e8cc9ec032d5dcd38af5359b3246398a7e0a 100644 GIT binary patch delta 15405 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 delta 14957 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 T2MmbJT>bS>-^?0UevSS=9Aq~l diff --git a/bin/resources/es/cemu.mo b/bin/resources/es/cemu.mo index 4f78fdcd0d63921934cbce6338ed108d179fbc24..856049de037de70de90a2868e6da991fe25d1235 100644 GIT binary patch delta 20815 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 delta 18004 zcmb8$2Y6J){{Qi_385tP9_rFNQbg$xLN6h-fFR%|S(1fhH*7XkaRrqoDtIU=ARwS3 zAY}moX@b}QD~bgvqKLhq*iioO&z`|w?*0Fs=l}fAbBEW=oH;XdzB6YQz4soUUF6i4 zvEg&&i!8Ia-i)!V8n~#kWxX0}S=XDZ*0QR0wyaW^h$XQRCg81D0=pag8;2Xmq1t6* zbqrw{T#Ti0Eyi0`*xF`p>_-jo1{T3HSRT(}ar^<x;qMrS@m(yd5|+h=*bvL&AgqYv zjQLof@_f|5Td^n}z<9>Dj*%%w#ha)%`T#Z11=K)4oAU3d2jjas6Dx-mDAz~c#A=V4 zXfo=#fvEn5n)02fanezdnTW+1-<m>3p$Ve~egxHU9qNrXp(3*%^~SHF-sDr%0N<hp zyoQ>{bz`w^PQT@`0rhoJ6YPt6ZU}}o&=@jW;|$YaGFGKL!<22*1Xf{mLZ~I$k9z(v z>IGg!h5n53ThxHA?oOoQQEy%gwX{vU6MxOP3l*9`UsOobP#sT1g*=GrXgX@<b5H{= z!Ya55*|*j%RJ#+XiJV3a_!X-DS5$xhm~vcF*a=zbB&VZ9)BvqfZ`=tL>b|H4N1_HA zhgzxvtc~+fZ}g0*-;bKWNmL{+qT2n5df{q4oEK~mCZn}$fi<x$s=*M{z;~h|G6@xW zwI70uk+0PH3>AsXsOPSsBIinWo-2X1DAz<i-x)jNAe@Ea`D7Bw6z%B@=teEYEf|Yk zOgRaMQyz?Z(+#MBUch;H7}ar~Ue1K(pxQl(%*on_G_kHAL9kl%W-^R#1;{8=Td)qk zgmHKYwG>}r5xk1(@ORYGB=m7YS{2(-ZjWl`M?II13jHk93oXZDxB;~nwnSz9cau?w z4;kM@J#ZPdd453+X!UhMTnhCWRz=-!iAAv^Dq=~fiS|NGWE9rGbW;wamSh1YGQRaJ z8LiDx)aH5*wX45IbsW>r>97Q<T@BP;X@R%l08|9$U>2^x6uf~GF{Qs{b-;Z%0<U5T z>^Fe;KTc*Cnd11J@fs>3H&9DaVW1PbL}MLPeG}s?s58EuxjzKe-yNvUnT{nfh{Z6B z>VMuq*1sE>mFC7L7*F|<Y4E)<W{|Tam2n04>ticCfHbkbLsrS^$ZKoO=b)BgB`Pw{ zV^cha8u)wUkhEe36aQ2)NrRn`K8pITx1(ly6g8nYP)qSX*2c5w#_OnoYSPJ8Y=x!p z9CpHQQT;XMn^nX*qV`C4)N?(<WHiu7RLI7o8fK&Rg5UT6s@*EoL^fb!+>F}wr!fIP zGWWkg_4h57#b2>C79HwLtUXSk91fAmA@d2ULEmA{?w)~qaItY6x+(9(GWZ@g!b_-u z6F3~IVHMPFZ-e?u2BS8s7t7;)rhYlnf7seWhW}Ux`J*D1XNxKVjW7dSU~gQ4Y-sC4 zEQfW|oOZWj1<D;!1Ek<=^q>zfp_XLONGF10PzPQHR%U!_78wn^6t#Iap*lK>Rq%b( zguX-Vg{#;M|G_rcbd*ybi`rxhur2PwmUsnk!zy?1#b65Fiqo(S<6B$F+>RGe9akOg ztZ`4{0Mr@}HRX|5iSjs9BtobG=c1n9k6Mz;s2BMe^@4F@oJdr|l9U@`Si7+und;a9 z>){BjjMI@b+FFd7`6=v(zoFi=&7ICf#~L$Hdt(wR<TFkE0@TsI*pzo-C(8ToB>vjX zznUA@Q8SMn>%4IptVX#aYK?C}y-_l1Dbi4p@tE=iEJ1m)DbGZ`>0H#;V`C<+G-YcX z@z*XdKF;||Vj?O6U9kbCqb4{X8{#9VH{4@<1vS72s0m&|4fLC-zk!NG$?^O^VKqE} z!%&}R+3;OX2Z^YT8)8+w4b{<LjKPt(6-T2cRNUjNeHpAyxeh84T~HD1ftpwfmc}ut ziA+RAXc}s~@EkG<<)f(0v>FxiZKyYT1GRb1p&q=78sKl7izU)6Ybq{AeXhm%V;JUO zlFPD=pk5@#>zo(ysQ%huoWB1)WE6@)s0pN@B9M)7I2-lG^HA*`MonM^s^j&j_M1@? zd>IvqH;wP1zLpPB5&0e!k-xBwzW?|MPKRAk1N1|6Fccf$NOOM{Dr5^#6MY=jelx25 zE-Z@sP!l|Yn&3&){nMyOe}tOQ=QxJ(tv^kJv@B;cjzK+;gIfE^sHF&DBF;yJ@F`SB zFQD3=!W#H4cEul1Yu+;3+1#B`We;lN0Ss$^X=Gw?CaN4pHJpPQXff)|H=-u88x_LW zP5DDqM7~8W#XnddtN5G<bVg0Irzwv{Ek(MI_$yQaQ!x{ZQl5`>@DY3)cc9*I&O~R8 z=V5WmOOUh3dcu^ip(c0(i(!RHPP^);e(PfiY=&{zZW8gYMWz!KI`KxM9-NGNU^?o7 zdr@z&5NqOERHzT3+I@mL(LO`HS*;w$W~lz!V_WQwJu!sIxG!uP{EZ5ET&~k$1$0xc zhVj@5^~Sx;{lTW3h8kczDnePPiRGY9$_1E!t58e$ESAE(s0f5#A)_}qiF)uotb#vc z0T%Z=d*B{ahc;?$pT<&n0_);Or~$5{B3UxPPdC;>ZQ3DN4<})CX|b8U{~cu1@Ke;U zU_ze0OTvRmI0dytdr=cRgH7=V)In8kvU9MsLrr8XYJ&Hg@<vqTUc)l@4ywP4SVG^w z6?AUIV>yb|P$6%QCGmFDoAyNQfuX23%*N6f#<KV@>bW)84xdA<^#yE<4f35ANI^Y6 z0gGt-S!A>Xb5Lu!05#JksIz^Yxqr~qzid2h>OV)V@i(Xm{f6qNXvmp(Y1E6<zzWy| zb-xRS6`EeABE^`7io`h7gL$Y2XQ9rAC8!B6MSUGBO?e|~jklm8@ir=QpP<(KD|{Lg z*jU<YI}3=vo6K8O)ONAyumk0?{GxQm{x}rpp&LIo{)K8+V;Y@e8m3|Q=}x;9IEeCI zY(&3BXIRmH&S*K)>DP}*+}}Kt_&-eMJ1Uyv-FG{ia1*wsd=YQM3bUL|*&jDjo`sFD z-aWKufB~pYcOU)Lz-O@&9!D*S>t1IQmPJJ*5o=?kFqsBq`k*#ZE~>#js7<m670UIf zrFtF}@)uB>>LpbD*QkhGMXha%*-n4$un6Ve7>fh25)Q^581|Bh&KNsWaUAc%m^o}n zoXa12pahj#^V+D1Hbm`_+fjR@A2!4^jP4cGb4yS!@C>HlPE+r?KYBlGRUxAgwZLK6 z8TG(or-8K+Yf|1}>fgdR${!mqpkClID&(;bI1%iIir^qr#4@oy`cV<Gv8cZPm1JV6 zc*^*+Y49AbrhX^tO;YDNYc>HD`njlIyKSh5yoQR%N2vb3!R7da$}zN`#|EXmaDj7B zoxoCzZ+%auAzsIhSZ|?oWRAlwl&9e_+=E4M#)Hn9hp{5%`51?*u?Mb4P583$Iu@l| zevvb=L{vXDF{}scnu^A#4qKRV4`Uxx$Od9O4o5vV9(4qJQ4zTpb#QG$E$KegW_}IT z{sYv&XEAyNFCzZUsjwb$POuiJ1|3iXc10p*^+vrxKh!2l#rJUyD&z$ZJ4-MRHG#*l z6h3R*V|>~8E~?#yhlzi6GCxvL8cROnY?|t*H*JGj+b&ohQ&1E1px%4}YGSidA)jx` zPonzWh>h?iR6k##_ROEC%~>J5*!f4RrdWoG;aCzgQ4^Sqip1Tfd_QW59zlKg8&DJ5 zYVIFFH{}zk34V)uv0_V{_LWf+u8k!y+|b-;Why$LI!re8X{a|Fk5#b1)IWskcsXjq z8&PYz6ZQSSgBsuq)PO&r+FdioJ{p~1*eXlrR&F#yJ#d$CBI*r8sDWppBC-tiW@}OH zUqlUf92J?5P)qbZDsoq`9R7uRu9WSZCyg=s``??4W}b=(7(g|gi5hS@R={<rNbE)p zd;-<}ebny0h<d{>QD^%vSRX4cb@oCB)TSJPI?ysOLEryuGV1Ul)Elor4ZPO48Jkev zgKBpHwYk1VO|<ATr{g-PlkgVQ00XfNPQ>!K8ntBGQJe5EhMSW4kW4*{<+#!e8=)rB z4)tbT(TzP(6Z2vP%tKA!eyoIRa4hadoq!cqIJ>+GYT%Zrz0d_U(Vi>#{%;}Up+ax8 z1UuqZT!2@wB?cdJCbkaCQr?DI+ap*RU&9PMj}<Zbac82Va3kdm?2RR!aNax>wdB4h zh<_h4^Qq{HZ(|acS?QFAU?a+lQERyu6_HO-OK}Oc*}g-C_-Cw!ajTraPq?us<w2;3 zJc`jhg;go93zJc3_M<v{9TocbP@C&4s>3VB*wxNCQ495^El~p}qdHDQwfA5Zyc^Z; zQq%;V#6)}wwbbDQWa^Q512ypX*cgAq7U+J`+07}aNcgcKE=RRLh&AvP<0aIRT5Fu8 zY=wH$NtloK<8b^PCo{e^Xsz?#XdK2d+$gus*<87}jq+^NTDN}6iAV?3oAp91#aLqo zYC=9#`ygtw%|N}-Y*d7vL$%wD<@Ni2h>Sw<j=6Cj71}E}9{)ylICi}=pckWWggRL6 zM=jxUJdGPsYd&KG|Lz|TV+vMfC+fK@RR8y46~?!gk<l*Siu#PsVn6&D)3Ec?&cD~Q zQ4b!*I6RGN|FJ3mh^;AK!?xJ?8RzS`6W^kok7Y3VS?4bz!!WEFEhVE4w_+?tP;d4E z>djwAZN_(upJH{&Um4>zIT5IXMX7Iv?eKOig1M-ZF^K9XY<zGN@z)!zprRvg!LIlv zK7b9Mb2iNu)TVN8c0!nnwJ7^g2hn_Nf~!#H!z*|jp1}kxy~X(|s$m_<?NHB++(P`_ zWF}Fe&u9_W#5JhT<RBK{8Fb^It<LA<M|Q6@4YToU9E8K3cP6+FHNbu>g>RzzJBxH> zT|@P|KFk)^Zcf~8b~!eq+yU#O7i;4}tc%Z~zVB1m0I#5yuJjJ)%`2iJ(EzmsgHe%t z09)g3Y=d8#`f#<K&ObQx$41<^-;}qYLU<XwW6g-u(Kyr^KWtovn!qY7j!$7@+>Cm$ z(>N5r#SYkQm$Mh9BS&}GnoC9p$ZFL0xgX2oanxq~0JUZxV?(@d%Jp_TUq>(08?M5> zcoG$vN_(7%bwW)z3Ds|3ERA<yF@68pWEAqeXayZ%3gr!`H@a%Pj%6svz2MYWLWQ~( z*2BK27s)}b`2tjAR-gu4ZQO$D=S3{1@BaiDeP*9x9)4lUX?vaTcN{8o1$Y~7!!CFk z^<4dZ&YCvIl9b1z2FSrmcsFVfJ&Jmvov28>h~e^NPLdgb=kX#o+wbi1QZG6kR>6wY z*GD&YKn*+^V=x_g#PXsBa364XdlS^2N=BUnlTZ`PLq%xT0phO-JV=FR^dy$RZCDQX zq4vZHQ~m(87p|b*EdHPqayM$=E~rrV#-%s{AHc6M8}kl1|BU$&-s<89?lAG!gXu?{ z&tno+r@R`qY4)M+A3-<1j`}RWMy+j`mz+pcLbb1nI&kWvo@<3}?0|aX(WuB}qn^(T zlhH{w1uNh(<0ed^d=T~E4OEAvjyeNX#s-vYq9T}#dOi*7;6zm97NJ7F0yXd|RDT;# z{e^d$1}~x>ID(0I0<-W7)SC@D=Invts3r1Y4CbREHU&H3qo|3!iS_Uo)WB6=c3#wt z{V8`t_CnZNNTwkbD^P292sN`)s0TkVo<j|I8MRlwLJj;UYOhp2?)1|bb$>AGq`V9D zLNl>BF2kmH6pQQo|Bg%zDz2d>P~jCPM0HVb+6oogj;M*HpgI_d7jX_2#mrZo2u?yh zHwEY7y{PAkz2@wNvZ(g0G5Wv%X-}pY6-lTi=!bDQ92LURs0rR<?yp3(e;PH=^Qb+v z7hB*NY=?2LI|FyZzLa~S_R2%XwHPizMTAUOJcvW_Iu69aZ#WZLg$n62SRD7FPRy53 zyZt*<KYyU!EcS#G!FVh}xhiVHHBp<h0an32Cy2jJs_|54Ed!|U`ax7j>rfp(i(12- zSQSs=bNB_S!^J0^`kkl<oIv&S2i}5ZPB|xOH&i6kum|R!V*TA@_E6Cf&)}{24|c=0 zZ#qY7KK7&hG$!GXI1<~u<!q{j*qHJQ*at6QQ*8XU^H;lJ*o5*NY=c{{HJ%TXDNUx_ zJI-!(V{^)#uq0-q-nanW_%N2iZCDl$V>x^q^=6-;_RI~`TGxBmiNtNFaRy?09D{=~ zyp&9TGM}S1(Jk*e1E!$XEDN<10o2-0K`m7n>*Gpu{|IVguVQ69gKGahYNEfP*1Y&> zr(ZYHK5R86qnWiwt#KdJfF6v;Y}6jeH_k#g<#|{EpGCd-0o3{M5o+RRQO|vj(TQL) z%D<r|TJL>Di1lwvMr+d>cVZr@!+IY$q3n*MTzua+i28$PoS)x>51kP9L#_SeSR0Sx z?f4n~fYm<Ymk;AVcE*{4S(Mk}z%U&b|HS#+Mx5mw=f+c*jhFE#4*t~H6Xnl2|6tJ# z+fjc%cE|nL8vnxW*y6nN6`aFa^k4FV^X3aLIzPvIa18CvVOXK>_?fe&{ZLEb#Wonm zmbeou;ze`+8fxv!UUEX&4l7aag<6s^*cJm=3fE&-+=1HEKcXV__a)-51ES{V&YIOl zg}y1KVIQoAPvT%aikq?g7yQEt9>V&VbJ^KLOHePc7S+#w)TTRwT9O}712_88x!>o@ zu=AN@QZbGjQ?V|7fDQ2v)MjyCiT>9_Rx7Mbc|A73{piLEn1HUYoUf-8>IiR$&9FHR z!BMCPJ%{>=4u#3+!H=;MUdG~B_iHC2%`ld7TjTAh_MLDG_CbaI7gVI;zj5|TOVpki zgPQO|s7P!;^>+Z*VfYA{+!+1_^sTc-Z-4JRSca_|PlF_Ef&;NPPC-TJpebL#3zQrE z=$!S%e{v3_Qm7@Ujb*Sij>3Vaexqa9I!dM}H_oGGb{Q4AuZ=&N``1t*`x{GO;?K@= z^{^P_rl<+GK|S9EYhf~KGp3^sqUESf{|uJV_rHgXI(WtSCPw!FYSa9Ida(6XXW$-~ zKzSf);-gR<jz{ewKe{l7KX#z{8b^c`ji0G|j>IZkGjO`T|0>*ENyYCZT}LU`qdo<1 zUI$EGe`~Eux_ON!pHATl{z1Bv{7h11)Ba8J50FN1uRKZDo0v%ILz{4_Q)m5wm#8dG z<(=4`q-!2>P(`oZ{5h0zmbo{EwhxmZi;t81<javbVXYS_-@FKj^(y6>xS!OGHj^lq zjZyr$zNX^l^@7Rp3v4|>DnmnUP+eY9KKVS_*TnnrG3tC+(e&|xu@d)R*F#+6sB4dd z&Apa5hx-W{{~m5^qM|4jhp;8?B7H)A3TXlP@2LNq{21(p`W<+L6vMry)NMyy-;q3& z=bG|+<j;^oq+_H3Bwh8n-<@<(@1I0^h=L!t(O@F^uJ|<Nr^#0$--YxfWnF7YuTZ{u zrPHQtp|a}vs;!dL564}q;NlCl+R*M%>bsEEkivIR`3#wZq>ZFHrlUOabI4D_hNkWp zTtoVtG+sS&Z6*Ib={osMq>|(dUu`Jl6|Nw^nm%>y(fWVP13H;>&F98E(o*V<n~wTW z|7Y|r-?yoIpYlo5iEjNv{cO_0LUs31zQeRv8=VCaQZ}iXsqgI~{)1IX#U<>650P|~ zP9oXl8)M;X0E6gfwWF!LmwXZGTA0CSQr4v(Fb+qnC*`}ir?cc)Qe*N@;b^tf`Y)lP z7wUS;JlvnUH1c(v>gc~LdX@5I>Wg3oE<s%ZybJYTM31Nb26iO1Ca>!@^6N=D<a9#5 zrAn?GQg{h}WN>pN=`#7d@Eej&G+ntQH|74(lJmzk$^|4{)r{wzqP3Yphf()6<?l%y z$d{vTCF-<1Yuel7=Q;0B&Rid2T^dXy-EC%n^PY?&J;J>&OebGqrpfERkF=Qdn+ja> zNqe||U$o@>vW_%)2jZW_KwnV06Bn7ADLj;FCeRFvQoaq>;(n5@mZTP>DWu`leMq{4 zHj}WqX%jXcr;UERR+F^kx0=2es=-kzx>3PjZuGxJu|}Ai&B^OpX37(tqBWOi_M5r` z<cpI^k#v3FU|pa*hkQisOk3T$mwYYKEv9{VRH2({Fo<#{{$v`cPFDlUwMd7ZqSf0x zw}&!6kkP9ybwS$bFC=40FOqLW($xy%xj&iwF7lno_i#mn!S1J^{{xb)?j&Vz=a1*{ zDH@-}n^ywm5)?XeKbiavHRhU2YC^sZ=_>WQZY4FKJjj$cQjRnE>eQFh_+c_{P}v^q z<7=cR$alsK>V>NvX$$3tu_CD%$)a49cCV0DP#i`1I_X);H?NbFn~-!>CdEc`9ObkL zD6h@Xmckq6p&c}iCAFfyn5ip9`4iG?>Y9>tJxKbSI$hOBL!vo8GoI;dp1TKMqQ0jo ztI1!aQl|VV&ujeXRhq&wa(A0fb4;TNlrNG}P5n?){|@C@v@cKj7E(LPT`0dsDofI( zznYgJHRt}X+@DW!ndfz%D{SqfGD3Qk(h#gn3Xy-FhU-ZDz0`V=x^3hak@it8d`+V; zg!;{<@-f;@H+lVaYy$VvNQJLB%B^X)A%^!qWA1pVc$}o`JdUQ(Q1V4cLGq0-nRN4- zPF*hTVo00Fzie*2MBYO>LH#O{uBnu3lW#%lLB0od50OSF{)?y_jdi#&oOJUlL4C3* zA0e;nkSV`J`55Ig_#%Fb6YvSs?icb$X!{`P4f47cb8jhS7j8D~!uNCIAZeRgaJ|fp z&u|6}Ym)zx)SL21$`6=_-ZkDw-9hqh?k~WP@D$#SO-MJd)zm#eTU}3KUrcn$Ve2(A zuTb%h?sCmE4J(je#)FM0zf1mi^17-~9%$MO#ve_6Y4Tf1t!a~u>q&dLx7^%cfl1W; z=SBRdaH9gLAL*pIyOI3P|7;m8rBmOPP7dQ<tWBy&+D`d?yo_JcvIO~j^ZW_yPyGQ? z-iig>*R@!OkFIm1*U8r)9i{Oo%44xHX+8P3@dwh)t2+7TN&9GjnDnroEp%V)Jfz*! zcc;EH`JLo-jUjc7<_dlPvE2NX%HL?*6Hid~lAfkKmDGW{&+z6on|l{11W?x&T*v(t zrmVVaq+68Xs_kG+q)o`QX{x{fRiKb)ZtgWE(P)^-Zy<k+Hh*D)dGIv(k4PD`=||mE ze30_Z>ur;How~=k-<h<Ww9eFTx!F+XpUn+jZAphn4M{JXx=!RzkzZ@d9q<`CT}ZvI z-Gv4%Nx2{C3TY?xrEmaA*9Vl#qmSojnZ8tiT<gDv%qqNry=Yj4G?4T@N!O#8g`G@! z3T>Yyzks@i<ln=-l<SdSPJSs#SH5Ylyq|kj@o~~4<R^2l9rFJFbKOh!1uCB-oih(_ zr|ve=Kcr_!Ztm%7K>C!_igG4tpeniA^4vY<**}e{e^q~Q{p1k+*SEuB_<1-&#VS)d z!-|aSR>oyd?!F+BkW@Ry?wq{HK9@YJe0E@}dqU8Y>viV^0~35X-e6=@&#|#3eEz(G zkUJ^0Uu1iq4zbnTDS?pN=g$v$a&o+x{GTB5z3wcY>gbNd4(RGCGBH2kw|for*~bQs zALa|?yEA=3Z$>B(oZd1%HOH&wxq&HOw=d)l1>7EPPnh5hdi^1HP9Vb*@&)|vK+qj5 z@Vk?}xdm=dzT1<T>+}2aLqQL{)Uitq8etDl*%*l%T-IgZnp(}yO3kr<NllFm9TFEC zd1&|mmwkRjwTk~W&z=L5JG$*sX=!#rTDu}sd>P)z$+XniaB{#O3I=lW-Jxu++mn$| z5cFhBcLyf8C*%Z}jX%qs7w|E;+=85tFE7Vurs(s#^Rqpf-e7*q`1qmTj6iO#*PrRl z?C1^!3%pjZQK`L#4tEbu9o}znO8hV{?@IT%o@u_^f?V=JuP4);Pj5B7dexARs5hq1 zDZXH+z?0+7n4Xd2&2M6t8ok)wIQmLLrkhygM57Z~IOeNZd)@dN_LcFS>=}1;voG8= z-u8IrMdH$Xx+19=m0Xc&ncZEH<KC%pvA&Sk?l`Gd@vJPpiqCIPoHX1%Iw>X5o6Cy0 z)0xPm=nGndd_K3Ib+m`)Y^aD<iq{kL=7-!VUSC#rdLS5a=T>$_+WJel><NKCY&&nN zJ#}(@`^Cv`L|zPbDiUcib&t#bW7_1%^ywR2c8i&#BAaHOim^+EPe+R1`*)E@g9mEH zmU7;Fa)HMmD#*3F&(ACA3*{&>eoqz~?)mxQn2P>M{=ihf+ZznB5dvBgZ>HVs!5LNl zYe`4sWqLx|cLyGvR6Hgp!)~y&^%!<at|!OJ&B<uh+MVYKWm|sl6mQT9gz{RZ6XA?N zK~ARI&mK<qx+eq*{F(pW^5t_k7%a#Oc{8;Kv)Ow-{}fM-kG1m6@Y-vZ_8RcNJfH9N z^2OvvyZ?XnVdd*H%g+dUy?(p-vMhVwvJUY*y&1k9-W*?GnEl7HX^Ep+^zr(=(cRwB zof`0F@~O0K*`{Ti$nND`TxBvcy!rZYJi&}?-;{uTW5x2w=Et_i*nyQZiVgKnV9_${ zpH{ZDyRUk_`k;Wmo1i<-8|0IAXX>3pfh<0AkNwT6v#zAbiPe*1tNMcaz;&AsG3d$k zSV_lru)XbbH>yTPtlJj-?9Es2$qZQi3%pqYcRC+4;jgvv4fEN5`}Asi?K8u|(J$8@ zaOV|x)g`a%OJ@i#A5Fo(=YcQKGXChgR02K0GbIpg&L?(k6VcYY1(<}pAf2x*!*?vg ziQx85^kx)<yw0J(=SHi4kA+N*1?%X#e@C_dd8WVJWz)-%s?QB_*|Rn$*jqM-?Ezb+ z+3#$r+>Ecw`Q~|%pp`VSAV1{gl=9|i7xHfAM9|$x(XGG5#AJKznC*8*-ratW%O1D0 zQ3bYPp-?yn%VRrzcCAQ1dvc_ry(W^KP-uuq$z9p5a-N)APllHd%twTI9e*T$cZryr z$B)OI<Kft4ALK{hm#9(kKWB~;1>(n>dvr8~_<*!n9Gdz6eG+jHF?n+m4Ngt&cg#+4 zC#5B&j85vA6yJ-JJ3Ysn;blE?`ny9uZ*E?|oqKE@XHy`$0~BdzKxZ03Z#ti6CP%f` zo$X_N8fzlH3=M4#ES*&oU)T_-vbUbAN^hT^Z)OI&j(3QjThZPk9ri_v*tHKO+Iffi z*_98Ev{xLSZnr%Ws9h*nj1lq%Iq$X0dIy5JocWQnM{-<o`i;r62OUk0Ja)9e6>0qP za#!TF<3GmLh~DsITD>OnqcUT1fzKP{*@3<(K9Ao%d7@FdetusD`OJVj-y03DUG`*F zgP{c>k9*L-WUFr=mt)ZCr33cg*Ulrp{J_~8PPTWY+Kaa*me19R>GN1A(dKTBOS{pj zz4oP3H6mr+oaJ))BMaZkaz*OAQ^{rLzPsL@{9c>LOYfC;MJm0Y7gMs(D|#af&s>kO ztAA1>^4-ThV(bB*?zJnOyHR+{*oo&yB@k_X1oHy~+%7o3!|r-vvi;VDHHF(n@-Mb^ z{YS;-&vIS%^PiuM?E2yxmn%22_Dffh$m`$iiY?t&E0<AlY#qNg83Ft5AGXyl`~hik zL&vs;3RqPuDThVy+n(*!?EXKV{$KWP&7VfbG#tiu(5?++vR#NSvvtnwOuqPlKcGr0 z$*%EB4SVa=1bfxh)h@T)@s}F>Ir5iXwF)?se0+fe*+!gA{0i$UbbAW;B@G4aOM5y+ zyuY3(-l$OMI7*z&tKsrEIihRt{B6Y^@lSqiCcgr&{*zQJ_t;i`2Lg7n8#Vjn6+~BF z$4|5??W~)>2K0AqojcQ`!!E!n!`I-ChCRdqd2C039}4%BW9#?@2-w~m-T1#D6+Y~v mC*1$tdEqAi9|KsqH_vKk?|CeKxA>C1!GL8Vx~zd~#{U7g9r)4! diff --git a/bin/resources/fr/cemu.mo b/bin/resources/fr/cemu.mo index d31685fca1c4072f58d2fe0e0b467e562a64a205..8328991f01eb915b5ea722452ccefde985d6416c 100644 GIT binary patch delta 25093 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 Cn8+#s delta 17309 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 diff --git a/bin/resources/hu/cemu.mo b/bin/resources/hu/cemu.mo index d7f761fe66fa9a72d7e0ec3fed2c6a21687de3da..dd3d2fbc74157e457fac7fefe3356bdbfbbe9e11 100644 GIT binary patch delta 26825 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 delta 18303 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 Br<(u( diff --git a/bin/resources/ko/cemu.mo b/bin/resources/ko/cemu.mo index 9f20110f9de6a6fa23ec74fce6806faf1d7c695b..63d82220c256cda72cbc9cf550ff5f36d8da6bb4 100644 GIT binary patch delta 17509 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 delta 16937 zcmYk@1#}h19>?(w3GoC-2pS-0C>kud7k4R614WAzw-#93io3K>BrRGbcySHITCBK3 z(c-lD`~L0>@37|#pZU+s&W`Q9NnYRVg+6cR`M8(Ed}cUY$-EsW5~rteoU}fU6I?*0 zj?=xo<Ah)j3`Q4&a6AU!Z0jQHO6x{cyIq(bk6<#qgQ55z2GX75SHV<-p&pP0dA^e$ z!>};=V--w+bulS^g=w)P=D=Z?9G78A+-g0J*@$o2`hbdN0O`<|=R2Q~VI)or`e8}b z%qpTDSPS*QI2*S^U*dkK0S?A+oPz2n9yQ=KsQb2{`rB#aL#XE*#~_~X{6$7L+(0eS z1Jr}xq8f&NZf2GWwL;MtfrU{^SsnF&rl<$DK@H?<Yj0G)E@sC`sON1!S2yk;qX#9T z_WBRh11_T)K1Ys)^UlVpDw%;~LM?e7^uf}oek!0QP#yK)7N{-hVjYU=cV;ElUrV=u z0v(nF)ROK*&EN!TK$lPhx{qq_RoOf!0<{$xP#tDNO(-ww!9_7OR>4#lhicas^*pyS z>#u_EDe%SVw!u8q0G6OSSdE&|LDU1zp|;`%Y6~8tFMdEh$hV4Vp8+!w=SNMfjxGNR zwIaP;GFsZPsD_JB58i`*codW3Y0Q8ZQ1!1+5B8~QRwNv?713A^i=j7;L#@O_)P3{O z50{}<(p^g?BboiE4sK!ze1U^7rkZ)penmYn5w#_Mp&oDx8{%Em49iqE52%NuiJPMO zyM`J_WDV0U5AsI3PFXUXD`yz;ncy76QW#LvEKz05Oxzr`5`!=RhocVN1k{74Vlc*| zR&pH{!4s%<{<X|~A*dBkjUjsfW5{Sp%Ah}1w$?!{Q4?zy3?UwZK{yfhz_}QRzn~83 zI$NKJIuoZ*D|G=i&?~43JjF<!@4U4IX=|IU$cp-8tAN^)7N|qj6?GU#pgR5m%i&^F z$2U>!|3#e@|2k#`b6^?bk{FBM+VZvN($+aZMjc1g<z!=C?1X-tq~h2UzrdBKkteC| zICC%<XXDSPl`Gr8SQWK$^-x>Z4z<FaQ3LCR+JY$!SbtS4w++`}QR2<0FOA2xJeU)v zI1JT3AL@azm<-FJwxW)$Z-G(7Z8022Vp*JnA$She&)*GMf9>5P+wiqDl<BHr7Mz7K z7=ww(d2(K16|CII%w!^J3x2_rxD|8Zant}_qqZQlF<)R<3$+sST{0=iY(tIc7-~Rg zP<wn0HS;^DGvU+3obHSmMO+5;;5byrJ#iK8MNObbQ}!KQRDZvrp0@|JHSQ@gI-TcH z9bHBZ<OyntU!fW%X=V<gzcmx8T`a2OGN_I#VNPs}dhLdw-j<Q5{-&VX&qY?sbr#x+ zjhLK@omc?>K#e>}bH{0qWv~mzqw2$2nAh!d)P2pYoiU1dAnI)Vh|#zTHIUPo4zFRP z-v75`A}C1P((Flo)JoJsmA67Ypf7SfoYAN~zK9v{32KFseaTlZMqp(efP5%AyHG0= z7-!l=qRvDXjNtiBEEzqpCTdCJa42@buko%eZ_vt|f##Ts@;<16jK?&%5Y^9COpQlT z1G|MfQ};0vUt(?yZ_WA_BvY1*3VNbW?R+eRhcF+$!&uDo6+bOu8!U_yQ1|b_qWA*U zPhOTmd)(C83UwAb*tiR7WqY+@{k1e>DbNGwVR}4{>gYZOVd}PKW?4})jzyjB8mRZa zDSBgj%!{2+{me#9Xgy}dE2x$8ZpUOWT|3vzs0sz8DCmJX@K@AKPh$!EfLX9ad-G+~ z64kMbzPQ}F7Io-0qXw90%g<nD;!8IEfZ7UQw}Uz5S<#n*IBR><%)6ow4nUpS!KlMC z2esF$P<y)z^}xfZ_UCN*Wegy`YvX6A6?>0<=mvG<W0Fibs-QpWv=6~-I0dzI8!<bc zM-5abGY9ygW}MGj2K9j2sDU;|_1n#s_d^YQ1TtXPnMI};1@X8IFQY%s>ts4wf_lJO z)G6PM>hQd+zm7?WAEEkrimNbpXEV@qs55pIbtayoRw%TKR+#+{C!-N(!cfeETFR0b zfYnd~Xn=v(2G!v=s4W?Sn&~{$;arWnZyyHXF&u^Gu^+bXYCb2<VFRA;l>UaFe7sm{ z)XYwHGoM%&P#t=AH}7#8)XHQ;4Imn|BE@Zeebm-8LA8rR4WJ{c-`=S915pE<fv%Qf z0U5aj^<FMRb+8k)5{amlIfHuNZ=pI))5E-W*--6sqWUR-IxDfLb}djV*b&oWe^mRa zJy?IeR`V#(jXO{?*o(UHchtzw*!s(;Ex3Ui=s(EUf#cKD+}{e56SqS>u&*s2g?ixk zsIxL1wPmw<vi=&;3JSE;`%xXAM-AvP>OFmdWiYIlS&@2}jX2K6BhWJ=)C1<BK9J_y zcoAwP<5AB^Kn*a_B@<5OENY1#*!T^mAWqiX>{({i152P*Bn~yu4mKWy!Nem`D>v1~ z^D!gw3e?K|j#}Z<s1GFfBAGwPyvIU#ypNf=cVBZz{82LtLoIcB8~4KwUsxn#>9 zVmR?j)Ib9{@|su}YHKp1`YVVG#C1xN(P^xJNwFqs#Pu;1_CR$s1~u}@sP?l^E3gpt zHM|M6Ri{zyo?=RTg;5yV-+a>MN3B$6kL-VcGJ3#B)Cx?%&v7?aMBf3XzB+0T>!Tjf z3Zt+CYH7!yW;V^%|6t?AsQy=?&eVF;K(}C+-v2XXv}bowOZfuzprqfL6$wW@FfHoF zC`^r|k==5>K+W_phT&DzS$T%)C(U=}%PR+}|7xg}ZHjJQGTq6f#rdeu`pu}raS?Ol zdsMqz1I^C?O|U5OBrJ#ru{^#;ZC$BBW`NC6d*2Il;V?{xt1uc92if=kF$EfN_+T^A zVyLBTj@sk4sF8n*8F3uy3@o$d38=SaFKR1JqxSeJY5)&WEAbZ9Um(k_6^L@ls6kQG z>D1o?)Inn`f?uJ|!fecmmrzUZJH&L55w)j9P+L?2wb$j*v(%WLxGAc>52}2ib-YVP z6+fT`vKVz1*4cOms>7qG8JtGV>;~#JdyK008EUpJ7`1XSsPf|0@~9Q6hPuBw>VCH) z8GT|6L=D76E$t{9Pez@IS*Vry4K<Jh*xHNVZZR`)>~MY$#3mStA213dNAS%`yV6*N zxaCNGvc`BUuJ`{LnNk$w9mT7KoiG}=V-()9`i?fgj7H;K%3EV2tTD#?Znp^4{yORb z-ec*92iL>0!~@2e=WN09#4m9o&v%M_Z+;j&h&t8JF%#w)Z+_9JiLu0kQKx+a#^W8- zKqpS%J|46Yvl0I@(X2rDB=dn2gWB3IsIB=9LvRFU;`z>0GTCt*>JXkqozA<cC3hy9 z8-q|=lM-`cMq6G5SxToa`r$ZCj#E*Gb1{13Qq<{RiA!-0x)sQDo?=G)D{3naBirZr zPvxD(@#u@mrkRdYp$=0t24iu|ft4{FyP_U66g7caSQ{7F@+Vfm>8!t&Cc|`o6v8~1 z8;787T#4#v6K24pw){ROC4Omri#l{kW|$?<gj(X}sFnO0wQ@sHTQm-}QmbdM{+ih) z3baH!tb1&O1NbB5$1noB%rs|X7-|a^VH!M)$?+y?WnQ5A^P9zAIWY*mdCeAKIpUw^ zm=C(YT{0SJ;9RqWX|M!wSxk%HVriU?d+`KnPiM|EXJQ_vBwm7Aku9h#-H94-k{^s| zP!r9Ey1xjjy<5T-ltXn?$;QpBaj30mi@Kp3hG8Gno{zy~xEi&UJ5dulhH8Hi^}y?x z3jfAj_#T7w{(r{$YGg%F4NIZ+raZD@PBmL!6Lp9hpq6?OYQPCtgx4<-br|2zH=m$E z3(UY%qh7l#*4);@9$l7~jAl?7(_tgj9`-;j;c(Q<=GgM3sCHXX13rzZ@jPngPf_<f zKbZl9VRpg{m>(-*X6z+hGBa$!Ce-Oahv_ltLeo)ZRL6x-TT$Lx6SWeJQ3L8|%ZH-| zJ`uxl397%%sKa^+HNc1Hs>2UtWZ)vxVFW79VdDa*8!DidvLR~5Ep2%p)Y6Ye4QM)M z#(2~N52EhBih2#7q9*iq5&Q3X@M5#X5vYdQFb~F}X3z$8LqF?C)ZR}+&1@;E-9F5M zXHosULA486Vg{TAwPhtxXRPcJ)?cTxif!;EYU%o-Mm`=jz<3+4K|SaYX2MIT8NWk4 zFf`r_C<?VgrBL@bMAgTk+V?=6sbMY|HJpMP`7+ef9YM|bA5_D)s2hWRHXTM<qfzY& zqE@0Ds=pStyf<nEhS~C2r~$1;P0-y+CIgw1s0Tl>72&^_26<2&S3(W2HAdqI)Qnc5 zAMQYPd;rz{Pt;rW5cPTS4)uYSoNpctFc;R>`(J^KHw80LGn|8bFgVN67dN72xE+h) zVH<ldGry9hKt2?mx~Q4nN40mBoBl#k{ba(<m;+Tm)l<&?|3qdI1;1cvEV{ye!lBN_ z7%Yqntf#OHallIR2ZySt6 M!3C%X?zSdckE6Eo3~HdaFrD82*JSj;5V6XvL?zTp zRKpzD40R}nVgSyvE<`<O1!_sxS%1e6;_KGO*7q1ldGN1hYg41E8OD$a#ww_-Xo%{t z396&+sKYWC^}t!E0nf+0cntM6yu|_-x!SBqZB+X%*c5xA&c;zxe|J~2|60l<Ys`|A z$MVFDun^8h4e$uI!W)<sKVQo|UVP%=XyVT6%+Gw!unlqf1T*k>97UXf92=*^dNZIK z)_dz&f9=H+Tj0CFEO7{GFH@sNT-=s_VU5F@lz)x6a3gA_*H9~W$HvY^Q|^aa$xs_d zTC=)jqN&J*$+13m!#0=<Ponnx25O5QVk&%%;h18RX_p<DgHr^x75A_r25vS#57fqt z#8WU7*I3=1Wb#mO3^jl^SQmr0m_NI>#C*j2P%HHeHB<ksW=6?S1I&O?7>#OQ6$@fN z>vBv@d<h%l3uHH4r`B&~2Jev%1SfQx>993wrkznsHw?XTGN#4p=#RgmPW2|#K=z;> zbi{homcK;}+;_V<oFN#^^PSveRMEiN41<VU+qf%668A=J!4%Ze&qtm9byyUy;vh`3 zgCE;)ChBcSvD3W16;UhP6g9C<810f7N=6+nLoMBQ)Po*iI(&s0Fy$^|L5w1<i@L7| zYJekA9WO#{;TF_=fxFG?niF-VnqVjFh;9=yC&_fdJbTR2%|R{YT2#lsp$^Yk)PVlA z^<jJYp@KLP2jFScO4iwD-kwINt>}-sf3kHo>a9AqkM++?<{kyQF?he(>u~&xI5QT; z8mJ{6f;v>2F+bk7ary)15ZA`ylz)dsa06CUJJfyo51L=)zQR((a}K)Z5S^jGmx3$S z+o&ygh+4wesHIMmXgbV;no)PugC?Or?nkvhhT4h;sFn0SWcEG;y@@koG-h$h=!WW8 z5<6lsTw)ts!WiPDznc|^#RkORU{>6N+3_}N=D~-}?+=wxhjS?E5Uxf|@S61wYC`TK zGMcIP5z`<!>JX;3ab;9TwXimRXUl)bVB$Yfujggd%pPDie2iM5h@)n&3!^?i>S79P zhYZMd`jH8tV6b((buMazOEDO?;dnfRnn|N$=DwDw73z$7@DG?5cVhrP!d&<Y)latL zW-AJKWdAFW(W!2Ly5SoP!TzWbkFxPRtVg^InVj<m)jshL)BXe|C%%e$Yo4GU82YDa zmmZT4XU8uvC+_C?&R{ZXSnh-wNG;S%I-+Je!8#rFz<H<v#-kpXV9WQ}_!#QGE2wt2 zQD^KqYDL3Nn!}j`-DDJ$CZqSX7Ouf|s=!pISPh(stuX0n&VU!+3#j`xoH2)PKk5*E zKs`A7UuK|rPy>uborNl>`u4WI>t8%Z1^p-p$HAzTn2Ne#0S4j@)XWdr`je>Be-(AV z&sj5|AXNMG*c@}91~3eDIH%eAS*ZIKon@K@$SkuBFQ5kU0CgtxpF?;$M14@@K;2l? zS_e}QH?na@Yd6$``=XY9G`7chTOWMhOeowXqYsJ<sE!NUijvk!n40oBs8iel)!|gk zhig#xUBMi98yjHA1v8P>n47o{s@+1=N-njryN-;OW{Y(@YUH~y3QySh8EPOt7fr{J zs5r)26}4rpP>0t=J#Y!8!xc6@fI5^%jjnUS7Tm?WRQ!uN1KBT`ffU5_#1%0uhc6Dx z5D&O)9<Uj87<bzEI5s9egX$;W74tJ<N!07S7d5c6m|VyF3K=cQW7OWi#<UoE)r>SJ zsy^1n<xm~eMjgU0FcJr#W;WBh1T}zlsL%SHm<Ip0I@joj=R0Z1Xepzx1Qx?m*b6n{ zHP-#82b@Q>yMqle>vi+fZ-3Ol4xk=<)W+vA1MxM~Vg6vt18=ZRx*-J_Rir`Pm=#qX zgBp2J)Ij1;1L%!fsbMyriE6*Zx&n2m*P$kM2xs9n%!mVSn)_$pWc>?Mu#y6Gd<`?> zBO9l<Wje@&`6(}o>YxK^Mx#*emtZU&u)f2R#D#C0Z`1Cmw`CdDz_XYgv;EEb>vUHC z+jQ6tOA`-Ab-WvO_-<G~SkvDzds!5HsjrKgX=BvPyWwXz3WIPBYVWt9CU6?{x$)e! z6~1@vOi>j@Q3I%k*|0Neg(jmOG~dP>Q8U?xn(;j>k2&s{33arNLJf3@bp>jx-F0Nb z$m~Qt@Puvf95v!M)|B_n4S7*BEP(-75A}J`+{Rr{Z%<!TyU7@Zzn}(?h}x>-$N>2K ze_&=7j%tt@wYND@hpaMc#?3GZ_C&SogBrj<8;`;m;t3dtJ5U2Vgd4ngYfxJ``5(>! zuEcbD{~J9r-vK?a5e*k(Z5q6O>^NUywI}@b1DB#+%YU#M=6-74`#z`@oNA3nwcm^( z^!FaS69+#x_YFdwfrHpq@BeKw<!M;sg*lxQUYfl;XN~xmS<$W@euh_2hcw_n^PNxy zD-!p_D!37~67O*j=6_}Gdy0d3PULIz)AGDGtbY+2B$8>Y8&FHu=&jl74yY~YjXH#5 zFc+@EtauhP<6G3>%=pfj$65ljP+k?aWu37g_C#%6{5#fP4_s{<?6Mw2?bR{7jrXuB z{`ubg**)C{6IaA_l#j;^Sl03Koaz^-fhY0u@_cZmLdCf;uiBvwX;&}T%QM2!6zH{E zj%{!oYGzUVl|i2m6;K_t#Jbo6tK%Nj%tO4rJOfUTd5DW*D7HmSuorsc2#mrpSQ3}H zw!tk-O~FglQib|>d6ql^6_>D<M|D)yTGN)-#d(xBM$POQY62liy*%Il`B8_iIY#2Q z7=i9AGU{*@>X5C)>AYuUeZ4&Y727F)Go$nYW+qipXQ3(TEf|a%cp}!pt9T1z0=+zY zoGQrN7lj&70n~&V;MaQpo7swe*4ws$f3O)?GSm&>Hcp2cKt>x^wDnamnDW}FrEiX! zaVONF9gEt+_0~j>ynh$TXaydl9`qb_YTsZ!%*acuJ+6g1waroO+MyoQ1$F;-r~$ax z2FGJkd~AJ*Uc|jfajfto1w7w#^-{o<i;7G5gt#?%J_4NMsA~^p4{<1QX<POh{YatI z{pe}MSs~_o%5x1SUyR%h)IofSx_XjEk$2~ksbC8qlmCf)1ZlJeK)ORJLDE%?a?LCo zaerG@pRyPd$Hi%B%Z6C}DSvG9oiG(Cn6mjKUG0-F{{l8>M!_nZ|BHs{$!{QKCdCrJ z!F|{cbx?K16CcE9o`|24iRY959$9VA6-fIf<R;)Zl-0H687TAhX8r%M1?6b)@oH@I z+p#l+Uyvebm(G?SwN|kAp1`}LD>ha?=g9v|yAihSZsIRV-H2aM{|fJFPezi;+D6W_ z6lKC)zA-!>l;FAg+S-4xBS{}I3vdhRIQe9x6{PZ{%_LoZoXM%w=i-t5iO*m@l3vnD zl#eC<IqK>_KAe0-^6#}Nw{1lVDs*)vzHPFee^>HvUD6clhhaP0PL=s-TaR>=vH>dK z%168(_v6Ql&vNGlZKjc$*!$aHKQA`rFB<+pI%8XBCBK%+Ys8Jne^1iYh}3|znYwV2 zt^j8K@zQ7E7Rt+$Msv@1#BIq>CjCuZh%|(<jkp%QC_m*AJR|cf1v|;>`jvPg=>}=K zDRO=$ud6t1rjY8{_)GFBZGIW~S|nXtDXUDnMw&|Elg{~-y6ULw7t(o>+mnI^ROmZU zzw^|z4G2AdxqCs~UD896KCG*fmVVM^CHdi$ouhp;`2nN{<o_hywC&ztM`B$`S+N<k zA4Pt=_P->R`8`#9vRg%N$|sOUe^SQBq%)AX1|8^XYCU3|NLe)b0=SX%9qpeJXCX}{ z{)+ULvbLzJoL4zMrzp^sinN)Ax*8fh|6Zdkk~XVxy{-RP{Y#Q*yUT^^leIl9j*v3Z zA}J}6^uadM|KV*8aSZkPe%MCpO#ag=JM|y0dp56cSzUglNXi1SF!$}X_YR<J9eH<| zEf}QM_Bw3K{vw`fJIq1Zcv3^+-IO;bzm23TkIG3qNIw(*OPdv>?d0_x6hr!WeQ7hZ zsPF5=Chnmi(RP%QLS3(kPvEyCeK~!+n%Mee)>~ApvXzr5Th4tykP=9bh`%BEQ(u*M zIO!|mFw$wtVzI1VVm~S_kv?99skrztz=_lypsYEm&?oJ3*fxtP8%O$yI4}0K<)_H= zvjbD)?~bHDNEb+-lRjSgR9TmfOWKmpFgNi?ZeD|1h_jMrko2?VAC&8gHF*9F<&5Z; zj&&qm`g#wfY#a_Jt);vYw&LEWq!HvlUhYIHy-D9vQHIi$<SUYN9V7io{&)M(1y<Ef zBK<}@laz!uvxygy;z;{Q`Z-lU$ZQ~~yB4(5e}9xf;H3hti+YK5btUa3<yF8{-{734 zVNKFK;sd0ANsDdWAUe}^-1-OYek5%qu0i=Xo?3p#q)b<F%6}u@n0#f@VHN6V7Np{) zZS=%b$gFLrUr-)z>n>B4>i;io7NUaGq~wl9<Oh-;iqr5cWp`{(qB|*qw2it+w9!@6 z;EbW{1WDH#gOiaqrAezumu%f3d!L%PYY5Jg$`W+MUNkyI{w&t9H}u9`_NKh#L#Tg6 zN<;eeT28Rn-ZO^wV{GL-%AS%Q+CI8bo`h3%TQ6-^TR7MHvBL($-AJWKoAgj_*p06~ zX_w@ad~0qzN&P{}N@6t}Oe#WNS1Zyc;_M_{%Sb-@ai}{5pObuO*nzaq-k?U?ZR0+; zpA<z|3sO<qw8Ie0MmkBphizNN-rt+DP||$L581Mu#8rrWNU2Gl|2rIIo{L}2JU{3w zILHkdO|df;=MvAM{tEeY_@C`iO->W*>Ogu<eyAtngP%4RNI{>pwN;)!r!}XoTaA*d zwlt1{!!(X39!5Ttd`?nNQf=Zp_%+s}{t)Q^d4H0w%hVk~FSbcnZqf+qZ0Px)!+R6o zqFpWgjFji&OM9PSJxSM5gYyM(OY%E$DPHBqb0l5&D0_}INyl^_PdSQTk#-W!B&D@& z{~`aFe1O{7>w9cKSs#69G^1h@1xfJ=g|EpE!5?r3sXggG>I#r9kfsqQr+zm1bmXUy zbhV)@4Q0Br8=M<hg%n2l99(VdRo#VrQ|-AfAH)Cu{g%$YBdw<7GyHf>C9{Nk-eQ<7 z>_L4+$}%g)>%@!H28)rplGpVcDTB>Fqg<CegMudn#ZBz_cM<=Nq->yV?9cLMvUxSA zL|rCNOWp|bAFolg=|Y?wy-5L7d~NFoq5DbO_0*l${x7018I3FBJ1m9y@xEdj%_Co% zysko|AWx3JuTq|cd`{d&{X)`MQm{R7oyqIkgKbIWNkO(w<y(onCt?3vkZD4}60D7H zNd3v{s!!*?kW$$A0eM~Hh<!=3Oz8PHntw~%xG+wqZ4KLg0r5>+_cvul$iL(Mbv)m> zPU=D$M!`iY-`ksR;b6*3ky2Ba8jIOFvd&ggb;|pYba^woPUOpxpN-2&k5s{xK>EPF zl}J6vpP}ww^6m~Y&DDbIyzRUSWwnSakp3fOB;G(>25d>vwG{i=e1P(%*hx>jGNhkK z$83EXY-RJ+X?LBv`=)(S=SKovMXZZy_&X_(IEu7_bdvNtZFZ5GQJ2{(A+$op<Owb7 zFY`*s-f)X|La!zrQzTsf`gTCVkzOgXB|Mrn%FjQhc-{iVq6!w8HfX^s??U+sCiMHM tTFQhI>sNXu%-xvYCt>VwH6ju&{IMY@Vfw{~xu%WY8nS7TpVv$O{{cK5B2WMT diff --git a/bin/resources/ru/cemu.mo b/bin/resources/ru/cemu.mo index f89098fdfc7ffa83f01393cda3ecb5ffe9f4ea3f..619ba6783440be10ac2b2c0c77cda8523926e8da 100644 GIT binary patch literal 89284 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 N&hx9^FD!o_{Sz8)Lk0i< literal 46500 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 diff --git a/bin/resources/zh/cemu.mo b/bin/resources/zh/cemu.mo index f9825b24febbfff53509fcd05588d31d42fd36e4..69e06e572cbc240cd3fa396bb0fb323e9dfb62cd 100644 GIT binary patch delta 18614 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 delta 17359 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 From 149fe10a4e0006963956d5dc9d103dc731c1cb6a Mon Sep 17 00:00:00 2001 From: qurious-pixel <62252937+qurious-pixel@users.noreply.github.com> Date: Fri, 24 May 2024 16:48:17 -0700 Subject: [PATCH 124/299] CI+MacOS: Use libusb dylib from vcpkg (#1219) --- src/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1b78b1fb..d5843c37 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -100,7 +100,7 @@ if (MACOS_BUNDLE) COMMAND ${CMAKE_COMMAND} ARGS -E copy "${CMAKE_BINARY_DIR}/vcpkg_installed/x64-osx/lib/libusb-1.0.0.dylib" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libusb-1.0.0.dylib" COMMAND ${CMAKE_COMMAND} ARGS -E copy "${CMAKE_SOURCE_DIR}/src/resource/update.sh" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/update.sh" COMMAND bash -c "install_name_tool -add_rpath @executable_path/../Frameworks ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}" - COMMAND bash -c "install_name_tool -change /usr/local/opt/libusb/lib/libusb-1.0.0.dylib @executable_path/../Frameworks/libusb-1.0.0.dylib ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}") + COMMAND bash -c "install_name_tool -change /Users/runner/work/Cemu/Cemu/build/vcpkg_installed/x64-osx/lib/libusb-1.0.0.dylib @executable_path/../Frameworks/libusb-1.0.0.dylib ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}") endif() set_target_properties(CemuBin PROPERTIES From aadd2f4a1af788d208a38a2d23161a4de517083a Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Sat, 25 May 2024 01:48:53 +0200 Subject: [PATCH 125/299] Input: Assign profile name correctly on save (#1217) --- src/input/InputManager.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/input/InputManager.cpp b/src/input/InputManager.cpp index d928e46c..64b238fc 100644 --- a/src/input/InputManager.cpp +++ b/src/input/InputManager.cpp @@ -472,15 +472,12 @@ bool InputManager::save(size_t player_index, std::string_view filename) emulated_controller->type_string() }.c_str()); + if(!is_default_file) + emulated_controller->m_profile_name = std::string{filename}; + if (emulated_controller->has_profile_name()) emulated_controller_node.append_child("profile").append_child(pugi::node_pcdata).set_value( emulated_controller->get_profile_name().c_str()); - else if (!is_default_file) - { - emulated_controller->m_profile_name = std::string{filename}; - emulated_controller_node.append_child("profile").append_child(pugi::node_pcdata).set_value( - emulated_controller->get_profile_name().c_str()); - } // custom settings emulated_controller->save(emulated_controller_node); From 1ee9d5c78c1c5216c92764977363fad38b0d4f0b Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Mon, 27 May 2024 01:24:24 +0200 Subject: [PATCH 126/299] coreinit: Tweak JD2019 workaround to avoid XCX softlock --- src/Cafe/OS/libs/coreinit/coreinit_Synchronization.cpp | 2 +- src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp | 8 ++++---- src/Cafe/OS/libs/coreinit/coreinit_Thread.h | 6 +++--- src/Cafe/OS/libs/coreinit/coreinit_ThreadQueue.cpp | 9 +++++---- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Synchronization.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Synchronization.cpp index 9e5de19e..c144c384 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Synchronization.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Synchronization.cpp @@ -310,7 +310,7 @@ namespace coreinit currentThread->mutexQueue.removeMutex(mutex); mutex->owner = nullptr; if (!mutex->threadQueue.isEmpty()) - mutex->threadQueue.wakeupSingleThreadWaitQueue(true); + mutex->threadQueue.wakeupSingleThreadWaitQueue(true, true); } // currentThread->cancelState = currentThread->cancelState & ~0x10000; } diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp index fbf498db..b53d04ed 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp @@ -758,14 +758,14 @@ namespace coreinit } // returns true if thread runs on same core and has higher priority - bool __OSCoreShouldSwitchToThread(OSThread_t* currentThread, OSThread_t* newThread) + bool __OSCoreShouldSwitchToThread(OSThread_t* currentThread, OSThread_t* newThread, bool sharedPriorityAndAffinityWorkaround) { uint32 coreIndex = OSGetCoreId(); if (!newThread->context.hasCoreAffinitySet(coreIndex)) return false; // special case: if current and new thread are running only on the same core then reschedule even if priority is equal // this resolves a deadlock in Just Dance 2019 where one thread would always reacquire the same mutex within it's timeslice, blocking another thread on the same core from acquiring it - if ((1<<coreIndex) == newThread->context.affinity && currentThread->context.affinity == newThread->context.affinity && currentThread->effectivePriority == newThread->effectivePriority) + if (sharedPriorityAndAffinityWorkaround && (1<<coreIndex) == newThread->context.affinity && currentThread->context.affinity == newThread->context.affinity && currentThread->effectivePriority == newThread->effectivePriority) return true; // otherwise reschedule if new thread has higher priority return newThread->effectivePriority < currentThread->effectivePriority; @@ -791,7 +791,7 @@ namespace coreinit // todo - only set this once? thread->wakeUpTime = PPCInterpreter_getMainCoreCycleCounter(); // reschedule if thread has higher priority - if (PPCInterpreter_getCurrentInstance() && __OSCoreShouldSwitchToThread(coreinit::OSGetCurrentThread(), thread)) + if (PPCInterpreter_getCurrentInstance() && __OSCoreShouldSwitchToThread(coreinit::OSGetCurrentThread(), thread, false)) PPCCore_switchToSchedulerWithLock(); } return previousSuspendCount; @@ -948,7 +948,7 @@ namespace coreinit OSThread_t* currentThread = OSGetCurrentThread(); if (currentThread && currentThread != thread) { - if (__OSCoreShouldSwitchToThread(currentThread, thread)) + if (__OSCoreShouldSwitchToThread(currentThread, thread, false)) PPCCore_switchToSchedulerWithLock(); } __OSUnlockScheduler(); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.h b/src/Cafe/OS/libs/coreinit/coreinit_Thread.h index fdbcfea7..8b144bd3 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.h @@ -126,8 +126,8 @@ namespace coreinit // counterparts for queueAndWait void cancelWait(OSThread_t* thread); - void wakeupEntireWaitQueue(bool reschedule); - void wakeupSingleThreadWaitQueue(bool reschedule); + void wakeupEntireWaitQueue(bool reschedule, bool sharedPriorityAndAffinityWorkaround = false); + void wakeupSingleThreadWaitQueue(bool reschedule, bool sharedPriorityAndAffinityWorkaround = false); private: OSThread_t* takeFirstFromQueue(size_t linkOffset) @@ -611,7 +611,7 @@ namespace coreinit // internal void __OSAddReadyThreadToRunQueue(OSThread_t* thread); - bool __OSCoreShouldSwitchToThread(OSThread_t* currentThread, OSThread_t* newThread); + bool __OSCoreShouldSwitchToThread(OSThread_t* currentThread, OSThread_t* newThread, bool sharedPriorityAndAffinityWorkaround); void __OSQueueThreadDeallocation(OSThread_t* thread); bool __OSIsThreadActive(OSThread_t* thread); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_ThreadQueue.cpp b/src/Cafe/OS/libs/coreinit/coreinit_ThreadQueue.cpp index 68cb22b3..b33aa888 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_ThreadQueue.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_ThreadQueue.cpp @@ -128,7 +128,8 @@ namespace coreinit // counterpart for queueAndWait // if reschedule is true then scheduler will switch to woken up thread (if it is runnable on the same core) - void OSThreadQueueInternal::wakeupEntireWaitQueue(bool reschedule) + // sharedPriorityAndAffinityWorkaround is currently a hack/placeholder for some special cases. A proper fix likely involves handling all the nuances of thread effective priority + void OSThreadQueueInternal::wakeupEntireWaitQueue(bool reschedule, bool sharedPriorityAndAffinityWorkaround) { cemu_assert_debug(__OSHasSchedulerLock()); bool shouldReschedule = false; @@ -139,7 +140,7 @@ namespace coreinit thread->state = OSThread_t::THREAD_STATE::STATE_READY; thread->currentWaitQueue = nullptr; coreinit::__OSAddReadyThreadToRunQueue(thread); - if (reschedule && thread->suspendCounter == 0 && PPCInterpreter_getCurrentInstance() && __OSCoreShouldSwitchToThread(coreinit::OSGetCurrentThread(), thread)) + if (reschedule && thread->suspendCounter == 0 && PPCInterpreter_getCurrentInstance() && __OSCoreShouldSwitchToThread(coreinit::OSGetCurrentThread(), thread, sharedPriorityAndAffinityWorkaround)) shouldReschedule = true; } if (shouldReschedule) @@ -148,7 +149,7 @@ namespace coreinit // counterpart for queueAndWait // if reschedule is true then scheduler will switch to woken up thread (if it is runnable on the same core) - void OSThreadQueueInternal::wakeupSingleThreadWaitQueue(bool reschedule) + void OSThreadQueueInternal::wakeupSingleThreadWaitQueue(bool reschedule, bool sharedPriorityAndAffinityWorkaround) { cemu_assert_debug(__OSHasSchedulerLock()); OSThread_t* thread = takeFirstFromQueue(offsetof(OSThread_t, waitQueueLink)); @@ -159,7 +160,7 @@ namespace coreinit thread->state = OSThread_t::THREAD_STATE::STATE_READY; thread->currentWaitQueue = nullptr; coreinit::__OSAddReadyThreadToRunQueue(thread); - if (reschedule && thread->suspendCounter == 0 && PPCInterpreter_getCurrentInstance() && __OSCoreShouldSwitchToThread(coreinit::OSGetCurrentThread(), thread)) + if (reschedule && thread->suspendCounter == 0 && PPCInterpreter_getCurrentInstance() && __OSCoreShouldSwitchToThread(coreinit::OSGetCurrentThread(), thread, sharedPriorityAndAffinityWorkaround)) shouldReschedule = true; } if (shouldReschedule) From da8fd5b7c7ba51816d477e00f22d9a2cc56cb60b Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 29 May 2024 00:07:37 +0200 Subject: [PATCH 127/299] nn_save: Refactor and modernize code --- .../Interpreter/PPCInterpreterMain.cpp | 2 +- src/Cafe/HW/Espresso/PPCState.h | 2 +- src/Cafe/OS/libs/nn_save/nn_save.cpp | 1195 +++++------------ src/Common/MemPtr.h | 2 +- src/Common/StackAllocator.h | 9 +- 5 files changed, 323 insertions(+), 887 deletions(-) diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterMain.cpp b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterMain.cpp index a9ab49a5..ace1601f 100644 --- a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterMain.cpp +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterMain.cpp @@ -90,7 +90,7 @@ uint8* PPCInterpreterGetStackPointer() return memory_getPointerFromVirtualOffset(PPCInterpreter_getCurrentInstance()->gpr[1]); } -uint8* PPCInterpreterGetAndModifyStackPointer(sint32 offset) +uint8* PPCInterpreter_PushAndReturnStackPointer(sint32 offset) { PPCInterpreter_t* hCPU = PPCInterpreter_getCurrentInstance(); uint8* result = memory_getPointerFromVirtualOffset(hCPU->gpr[1] - offset); diff --git a/src/Cafe/HW/Espresso/PPCState.h b/src/Cafe/HW/Espresso/PPCState.h index 134f73a8..c315ed0e 100644 --- a/src/Cafe/HW/Espresso/PPCState.h +++ b/src/Cafe/HW/Espresso/PPCState.h @@ -213,7 +213,7 @@ void PPCTimer_start(); // core info and control extern uint32 ppcThreadQuantum; -uint8* PPCInterpreterGetAndModifyStackPointer(sint32 offset); +uint8* PPCInterpreter_PushAndReturnStackPointer(sint32 offset); uint8* PPCInterpreterGetStackPointer(); void PPCInterpreterModifyStackPointer(sint32 offset); diff --git a/src/Cafe/OS/libs/nn_save/nn_save.cpp b/src/Cafe/OS/libs/nn_save/nn_save.cpp index 09d4413b..31f8ac8e 100644 --- a/src/Cafe/OS/libs/nn_save/nn_save.cpp +++ b/src/Cafe/OS/libs/nn_save/nn_save.cpp @@ -160,39 +160,60 @@ namespace save return FS_RESULT::FATAL_ERROR; } - typedef struct + struct AsyncResultData { - coreinit::OSEvent* event; - SAVEStatus returnStatus; + MEMPTR<coreinit::OSEvent> event; + betype<SAVEStatus> returnStatus; + }; - MEMPTR<OSThread_t> thread; // own stuff until cond + event rewritten - } AsyncCallbackParam_t; + void SaveAsyncFinishCallback(PPCInterpreter_t* hCPU); - void AsyncCallback(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 returnStatus, void* p) + struct AsyncToSyncWrapper : public FSAsyncParams { - cemu_assert_debug(p && ((AsyncCallbackParam_t*)p)->event); + AsyncToSyncWrapper() + { + coreinit::OSInitEvent(&_event, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); + ioMsgQueue = nullptr; + userContext = &_result; + userCallback = RPLLoader_MakePPCCallable(SaveAsyncFinishCallback); + _result.returnStatus = 0; + _result.event = &_event; + } - AsyncCallbackParam_t* param = (AsyncCallbackParam_t*)p; - param->returnStatus = returnStatus; - coreinit::OSSignalEvent(param->event); - } + ~AsyncToSyncWrapper() + { - void AsyncCallback(PPCInterpreter_t* hCPU) + } + + FSAsyncParams* GetAsyncParams() + { + return this; + } + + SAVEStatus GetResult() + { + return _result.returnStatus; + } + + void WaitForEvent() + { + coreinit::OSWaitEvent(&_event); + } + private: + coreinit::OSEvent _event; + AsyncResultData _result; + }; + + void SaveAsyncFinishCallback(PPCInterpreter_t* hCPU) { ppcDefineParamMEMPTR(client, coreinit::FSClient_t, 0); ppcDefineParamMEMPTR(block, coreinit::FSCmdBlock_t, 1); ppcDefineParamU32(returnStatus, 2); ppcDefineParamMEMPTR(userContext, void, 3); - MEMPTR<AsyncCallbackParam_t> param{ userContext }; - - // wait till thread is actually suspended - OSThread_t* thread = param->thread.GetPtr(); - while (thread->suspendCounter == 0 || thread->state == OSThread_t::THREAD_STATE::STATE_RUNNING) - coreinit::OSYieldThread(); - - param->returnStatus = returnStatus; - coreinit_resumeThread(param->thread.GetPtr(), 1000); + MEMPTR<AsyncResultData> resultPtr{ userContext }; + resultPtr->returnStatus = returnStatus; + coreinit::OSSignalEvent(resultPtr->event); osLib_returnFromFunction(hCPU, 0); } @@ -320,18 +341,16 @@ namespace save return SAVE_STATUS_OK; } - SAVEStatus SAVERemoveAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) + SAVEStatus SAVEInitSaveDir(uint8 accountSlot) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); - OSLockMutex(&g_nn_save->mutex); uint32 persistentId; if (GetPersistentIdEx(accountSlot, &persistentId)) { - char fullPath[SAVE_MAX_PATH_SIZE]; - if (GetAbsoluteFullPath(persistentId, path, fullPath)) - result = coreinit::FSRemoveAsync(client, block, (uint8*)fullPath, errHandling, (FSAsyncParams*)asyncParams); + acp::ACPStatus status = nn::acp::ACPCreateSaveDir(persistentId, iosu::acp::ACPDeviceType::InternalDeviceType); + result = ConvertACPToSaveStatus(status); } else result = (FSStatus)FS_RESULT::NOT_FOUND; @@ -340,27 +359,6 @@ namespace save return result; } - SAVEStatus SAVEMakeDirAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) - { - SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); - - OSLockMutex(&g_nn_save->mutex); - - uint32 persistentId; - if (GetPersistentIdEx(accountSlot, &persistentId)) - { - char fullPath[SAVE_MAX_PATH_SIZE]; - if (GetAbsoluteFullPath(persistentId, path, fullPath)) - result = coreinit::FSMakeDirAsync(client, block, fullPath, errHandling, (FSAsyncParams*)asyncParams); - } - else - result = (FSStatus)FS_RESULT::NOT_FOUND; - - OSUnlockMutex(&g_nn_save->mutex); - - return result; - } - SAVEStatus SAVEOpenDirAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -383,6 +381,69 @@ namespace save return result; } + SAVEStatus SAVEOpenDir(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) + { + StackAllocator<AsyncToSyncWrapper> asyncData; + SAVEStatus status = SAVEOpenDirAsync(client, block, accountSlot, path, hDir, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); + } + + SAVEStatus SAVEOpenDirOtherApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) + { + SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); + + OSLockMutex(&g_nn_save->mutex); + uint32 persistentId; + if (GetPersistentIdEx(accountSlot, &persistentId)) + { + char fullPath[SAVE_MAX_PATH_SIZE]; + if (GetAbsoluteFullPathOtherApplication(persistentId, titleId, path, fullPath) == FS_RESULT::SUCCESS) + result = coreinit::FSOpenDirAsync(client, block, fullPath, hDir, errHandling, (FSAsyncParams*)asyncParams); + } + else + result = (FSStatus)FS_RESULT::NOT_FOUND; + OSUnlockMutex(&g_nn_save->mutex); + + return result; + } + + SAVEStatus SAVEOpenDirOtherApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) + { + StackAllocator<AsyncToSyncWrapper> asyncData; + SAVEStatus status = SAVEOpenDirOtherApplicationAsync(client, block, titleId, accountSlot, path, hDir, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); + } + + SAVEStatus SAVEOpenDirOtherNormalApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) + { + uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); + return SAVEOpenDirOtherApplicationAsync(client, block, titleId, accountSlot, path, hDir, errHandling, asyncParams); + } + + SAVEStatus SAVEOpenDirOtherNormalApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) + { + uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); + return SAVEOpenDirOtherApplication(client, block, titleId, accountSlot, path, hDir, errHandling); + } + + SAVEStatus SAVEOpenDirOtherNormalApplicationVariationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) + { + uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); + return SAVEOpenDirOtherApplicationAsync(client, block, titleId, accountSlot, path, hDir, errHandling, asyncParams); + } + + SAVEStatus SAVEOpenDirOtherNormalApplicationVariation(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) + { + uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); + return SAVEOpenDirOtherApplication(client, block, titleId, accountSlot, path, hDir, errHandling); + } + SAVEStatus SAVEOpenFileAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -430,25 +491,12 @@ namespace save SAVEStatus SAVEOpenFileOtherApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling) { - MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams asyncParams; - asyncParams.ioMsgQueue = nullptr; - asyncParams.userCallback = PPCInterpreter_makeCallableExportDepr(AsyncCallback); - - StackAllocator<AsyncCallbackParam_t> param; - param->thread = currentThread; - param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = param.GetPointer(); - - SAVEStatus status = SAVEOpenFileOtherApplicationAsync(client, block, titleId, accountSlot, path, mode, outFileHandle, errHandling, &asyncParams); - if (status == (FSStatus)FS_RESULT::SUCCESS) - { - coreinit_suspendThread(currentThread, 1000); - PPCCore_switchToScheduler(); - return param->returnStatus; - } - - return status; + StackAllocator<AsyncToSyncWrapper> asyncData; + SAVEStatus status = SAVEOpenFileOtherApplicationAsync(client, block, titleId, accountSlot, path, mode, outFileHandle, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); } SAVEStatus SAVEOpenFileOtherNormalApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) @@ -475,27 +523,6 @@ namespace save return SAVEOpenFileOtherApplication(client, block, titleId, accountSlot, path, mode, outFileHandle, errHandling); } - SAVEStatus SAVEGetFreeSpaceSizeAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FSLargeSize* freeSize, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) - { - SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); - - OSLockMutex(&g_nn_save->mutex); - - uint32 persistentId; - if (GetPersistentIdEx(accountSlot, &persistentId)) - { - char fullPath[SAVE_MAX_PATH_SIZE]; - if (GetAbsoluteFullPath(persistentId, nullptr, fullPath)) - result = coreinit::FSGetFreeSpaceSizeAsync(client, block, fullPath, freeSize, errHandling, (FSAsyncParams*)asyncParams); - } - else - result = (FSStatus)FS_RESULT::NOT_FOUND; - - OSUnlockMutex(&g_nn_save->mutex); - - return result; - } - SAVEStatus SAVEGetStatAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -517,6 +544,16 @@ namespace save return result; } + SAVEStatus SAVEGetStat(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) + { + StackAllocator<AsyncToSyncWrapper> asyncData; + SAVEStatus status = SAVEGetStatAsync(client, block, accountSlot, path, stat, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); + } + SAVEStatus SAVEGetStatOtherApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -538,12 +575,28 @@ namespace save return result; } + SAVEStatus SAVEGetStatOtherApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) + { + StackAllocator<AsyncToSyncWrapper> asyncData; + SAVEStatus status = SAVEGetStatOtherApplicationAsync(client, block, titleId, accountSlot, path, stat, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); + } + SAVEStatus SAVEGetStatOtherNormalApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); return SAVEGetStatOtherApplicationAsync(client, block, titleId, accountSlot, path, stat, errHandling, asyncParams); } + SAVEStatus SAVEGetStatOtherNormalApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) + { + uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); + return SAVEGetStatOtherApplication(client, block, titleId, accountSlot, path, stat, errHandling); + } + SAVEStatus SAVEGetStatOtherDemoApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { uint64 titleId = SAVE_UNIQUE_DEMO_TO_TITLE_ID(uniqueId); @@ -562,644 +615,6 @@ namespace save return SAVEGetStatOtherApplicationAsync(client, block, titleId, accountSlot, path, stat, errHandling, asyncParams); } - SAVEStatus SAVEInitSaveDir(uint8 accountSlot) - { - SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); - OSLockMutex(&g_nn_save->mutex); - - uint32 persistentId; - if (GetPersistentIdEx(accountSlot, &persistentId)) - { - acp::ACPStatus status = nn::acp::ACPCreateSaveDir(persistentId, iosu::acp::ACPDeviceType::InternalDeviceType); - result = ConvertACPToSaveStatus(status); - } - else - result = (FSStatus)FS_RESULT::NOT_FOUND; - - OSUnlockMutex(&g_nn_save->mutex); - return result; - } - - SAVEStatus SAVEGetFreeSpaceSize(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FSLargeSize* freeSize, FS_ERROR_MASK errHandling) - { - MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams asyncParams; - asyncParams.ioMsgQueue = nullptr; - asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); - - StackAllocator<AsyncCallbackParam_t> param; - param->thread = currentThread; - param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = ¶m; - - SAVEStatus status = SAVEGetFreeSpaceSizeAsync(client, block, accountSlot, freeSize, errHandling, &asyncParams); - if (status == (FSStatus)FS_RESULT::SUCCESS) - { - coreinit_suspendThread(currentThread, 1000); - PPCCore_switchToScheduler(); - return param->returnStatus; - } - - return status; - } - - void export_SAVEGetFreeSpaceSize(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(returnedFreeSize, FSLargeSize, 3); - ppcDefineParamU32(errHandling, 4); - - const SAVEStatus result = SAVEGetFreeSpaceSize(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, returnedFreeSize.GetPtr(), errHandling); - cemuLog_log(LogType::Save, "SAVEGetFreeSpaceSize(0x{:08x}, 0x{:08x}, {:x}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - void export_SAVEGetFreeSpaceSizeAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(returnedFreeSize, FSLargeSize, 3); - ppcDefineParamU32(errHandling, 4); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 5); - - const SAVEStatus result = SAVEGetFreeSpaceSizeAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, returnedFreeSize.GetPtr(), errHandling, asyncParams.GetPtr()); - cemuLog_log(LogType::Save, "SAVEGetFreeSpaceSizeAsync(0x{:08x}, 0x{:08x}, {:x}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - void export_SAVEInit(PPCInterpreter_t* hCPU) - { - const SAVEStatus result = SAVEInit(); - cemuLog_log(LogType::Save, "SAVEInit() -> {:x}", result); - osLib_returnFromFunction(hCPU, result); - } - - void export_SAVERemoveAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamU32(errHandling, 4); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 5); - - const SAVEStatus result = SAVERemoveAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), errHandling, asyncParams.GetPtr()); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVERemove(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling) - { - MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams asyncParams; - asyncParams.ioMsgQueue = nullptr; - asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); - - StackAllocator<AsyncCallbackParam_t> param; - param->thread = currentThread; - param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = ¶m; - - SAVEStatus status = SAVERemoveAsync(client, block, accountSlot, path, errHandling, &asyncParams); - if (status == (FSStatus)FS_RESULT::SUCCESS) - { - coreinit_suspendThread(currentThread, 1000); - PPCCore_switchToScheduler(); - return param->returnStatus; - } - - return status; - } - - void export_SAVERemove(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamU32(errHandling, 4); - - const SAVEStatus result = SAVERemove(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), errHandling); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVERenameAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* oldPath, const char* newPath, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) - { - SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); - - OSLockMutex(&g_nn_save->mutex); - - uint32 persistentId; - if (GetPersistentIdEx(accountSlot, &persistentId)) - { - char fullOldPath[SAVE_MAX_PATH_SIZE]; - if (GetAbsoluteFullPath(persistentId, oldPath, fullOldPath)) - { - char fullNewPath[SAVE_MAX_PATH_SIZE]; - if (GetAbsoluteFullPath(persistentId, newPath, fullNewPath)) - result = coreinit::FSRenameAsync(client, block, fullOldPath, fullNewPath, errHandling, (FSAsyncParams*)asyncParams); - } - } - else - result = (FSStatus)FS_RESULT::NOT_FOUND; - - OSUnlockMutex(&g_nn_save->mutex); - - return result; - } - - void export_SAVERenameAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(oldPath, const char, 3); - ppcDefineParamMEMPTR(newPath, const char, 4); - ppcDefineParamU32(errHandling, 5); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 6); - - const SAVEStatus result = SAVERenameAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, oldPath.GetPtr(), newPath.GetPtr(), errHandling, asyncParams.GetPtr()); - cemuLog_log(LogType::Save, "SAVERenameAsync(0x{:08x}, 0x{:08x}, {:x}, {}, {}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, oldPath.GetPtr(), newPath.GetPtr(), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVERename(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* oldPath, const char* newPath, FS_ERROR_MASK errHandling) - { - SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); - - OSLockMutex(&g_nn_save->mutex); - uint32 persistentId; - if (GetPersistentIdEx(accountSlot, &persistentId)) - { - char fullOldPath[SAVE_MAX_PATH_SIZE]; - if (GetAbsoluteFullPath(persistentId, oldPath, fullOldPath)) - { - char fullNewPath[SAVE_MAX_PATH_SIZE]; - if (GetAbsoluteFullPath(persistentId, newPath, fullNewPath)) - result = coreinit::FSRename(client, block, fullOldPath, fullNewPath, errHandling); - } - } - else - result = (FSStatus)FS_RESULT::NOT_FOUND; - OSUnlockMutex(&g_nn_save->mutex); - - return result; - } - - void export_SAVEOpenDirAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamMEMPTR(hDir, betype<FSDirHandle2>, 4); - ppcDefineParamU32(errHandling, 5); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 6); - - const SAVEStatus result = SAVEOpenDirAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), hDir, errHandling, asyncParams.GetPtr()); - cemuLog_log(LogType::Save, "SAVEOpenDirAsync(0x{:08x}, 0x{:08x}, {:x}, {}, 0x{:08x} ({:x}), {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), hDir.GetMPTR(), - (hDir.GetPtr() == nullptr ? 0 : (uint32)*hDir.GetPtr()), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenDir(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) - { - MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams asyncParams; - asyncParams.ioMsgQueue = nullptr; - asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); - - StackAllocator<AsyncCallbackParam_t> param; - param->thread = currentThread; - param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = ¶m; - - SAVEStatus status = SAVEOpenDirAsync(client, block, accountSlot, path, hDir, errHandling, &asyncParams); - if (status == (FSStatus)FS_RESULT::SUCCESS) - { - coreinit_suspendThread(currentThread, 1000); - PPCCore_switchToScheduler(); - return param->returnStatus; - } - - return status; - } - - void export_SAVEOpenDir(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamMEMPTR(hDir, betype<FSDirHandle2>, 4); - ppcDefineParamU32(errHandling, 5); - - const SAVEStatus result = SAVEOpenDir(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), hDir, errHandling); - cemuLog_log(LogType::Save, "SAVEOpenDir(0x{:08x}, 0x{:08x}, {:x}, {}, 0x{:08x} ({:x}), {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), hDir.GetMPTR(), - (hDir.GetPtr() == nullptr ? 0 : (uint32)*hDir.GetPtr()), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenDirOtherApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) - { - SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); - - OSLockMutex(&g_nn_save->mutex); - uint32 persistentId; - if (GetPersistentIdEx(accountSlot, &persistentId)) - { - char fullPath[SAVE_MAX_PATH_SIZE]; - if (GetAbsoluteFullPathOtherApplication(persistentId, titleId, path, fullPath) == FS_RESULT::SUCCESS) - result = coreinit::FSOpenDirAsync(client, block, fullPath, hDir, errHandling, (FSAsyncParams*)asyncParams); - } - else - result = (FSStatus)FS_RESULT::NOT_FOUND; - OSUnlockMutex(&g_nn_save->mutex); - - return result; - } - - void export_SAVEOpenDirOtherApplicationAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU64(titleId, 2); - ppcDefineParamU8(accountSlot, 3); - ppcDefineParamMEMPTR(path, const char, 4); - ppcDefineParamMEMPTR(hDir, betype<FSDirHandle2>, 5); - ppcDefineParamU32(errHandling, 6); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 7); - - const SAVEStatus result = SAVEOpenDirOtherApplicationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), titleId, accountSlot, path.GetPtr(), hDir, errHandling, asyncParams.GetPtr()); - cemuLog_log(LogType::Save, "SAVEOpenDirOtherApplicationAsync(0x{:08x}, 0x{:08x}, {:x}, {:x}, {}, 0x{:08x} ({:x}), {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), titleId, accountSlot, path.GetPtr(), hDir.GetMPTR(), - (hDir.GetPtr() == nullptr ? 0 : (uint32)*hDir.GetPtr()), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenDirOtherApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) - { - MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams asyncParams; - asyncParams.ioMsgQueue = nullptr; - asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); - - StackAllocator<AsyncCallbackParam_t> param; - param->thread = currentThread; - param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = ¶m; - - SAVEStatus status = SAVEOpenDirOtherApplicationAsync(client, block, titleId, accountSlot, path, hDir, errHandling, &asyncParams); - if (status == (FSStatus)FS_RESULT::SUCCESS) - { - coreinit_suspendThread(currentThread, 1000); - PPCCore_switchToScheduler(); - return param->returnStatus; - } - - return status; - } - - void export_SAVEOpenDirOtherApplication(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU64(titleId, 2); - ppcDefineParamU8(accountSlot, 3); - ppcDefineParamMEMPTR(path, const char, 4); - ppcDefineParamMEMPTR(hDir, betype<FSDirHandle2>, 5); - ppcDefineParamU32(errHandling, 6); - - const SAVEStatus result = SAVEOpenDirOtherApplication(fsClient.GetPtr(), fsCmdBlock.GetPtr(), titleId, accountSlot, path.GetPtr(), hDir, errHandling); - cemuLog_log(LogType::Save, "SAVEOpenDirOtherApplication(0x{:08x}, 0x{:08x}, {:x}, {:x}, {}, 0x{:08x} ({:x}), {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), titleId, accountSlot, path.GetPtr(), hDir.GetMPTR(), - (hDir.GetPtr() == nullptr ? 0 : (uint32)*hDir.GetPtr()), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenDirOtherNormalApplicationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) - { - uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); - return SAVEOpenDirOtherApplicationAsync(client, block, titleId, accountSlot, path, hDir, errHandling, asyncParams); - } - - void export_SAVEOpenDirOtherNormalApplicationAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU32(uniqueId, 2); - ppcDefineParamU8(accountSlot, 3); - ppcDefineParamMEMPTR(path, const char, 4); - ppcDefineParamMEMPTR(hDir, betype<FSDirHandle2>, 5); - ppcDefineParamU32(errHandling, 6); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 7); - - const SAVEStatus result = SAVEOpenDirOtherNormalApplicationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, accountSlot, path.GetPtr(), hDir, errHandling, asyncParams.GetPtr()); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenDirOtherNormalApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) - { - uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); - return SAVEOpenDirOtherApplication(client, block, titleId, accountSlot, path, hDir, errHandling); - } - - void export_SAVEOpenDirOtherNormalApplication(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU32(uniqueId, 2); - ppcDefineParamU8(accountSlot, 3); - ppcDefineParamMEMPTR(path, const char, 4); - ppcDefineParamMEMPTR(hDir, betype<FSDirHandle2>, 5); - ppcDefineParamU32(errHandling, 6); - - const SAVEStatus result = SAVEOpenDirOtherNormalApplication(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, accountSlot, path.GetPtr(), hDir, errHandling); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenDirOtherNormalApplicationVariationAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) - { - uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); - return SAVEOpenDirOtherApplicationAsync(client, block, titleId, accountSlot, path, hDir, errHandling, asyncParams); - } - - void export_SAVEOpenDirOtherNormalApplicationVariationAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU32(uniqueId, 2); - ppcDefineParamU8(variation, 3); - ppcDefineParamU8(accountSlot, 4); - ppcDefineParamMEMPTR(path, const char, 5); - ppcDefineParamMEMPTR(hDir, betype<FSDirHandle2>, 6); - ppcDefineParamU32(errHandling, 7); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 8); - - const SAVEStatus result = SAVEOpenDirOtherNormalApplicationVariationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, variation, accountSlot, path.GetPtr(), hDir, errHandling, asyncParams.GetPtr()); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenDirOtherNormalApplicationVariation(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSDirHandlePtr hDir, FS_ERROR_MASK errHandling) - { - uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID_VARIATION(uniqueId, variation); - return SAVEOpenDirOtherApplication(client, block, titleId, accountSlot, path, hDir, errHandling); - } - - void export_SAVEOpenDirOtherNormalApplicationVariation(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU32(uniqueId, 2); - ppcDefineParamU8(variation, 3); - ppcDefineParamU8(accountSlot, 4); - ppcDefineParamMEMPTR(path, const char, 5); - ppcDefineParamMEMPTR(hDir, betype<FSDirHandle2>, 6); - ppcDefineParamU32(errHandling, 7); - - const SAVEStatus result = SAVEOpenDirOtherNormalApplicationVariation(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, variation, accountSlot, path.GetPtr(), hDir, errHandling); - osLib_returnFromFunction(hCPU, result); - } - - void export_SAVEMakeDirAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamU32(errHandling, 4); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 5); - - const SAVEStatus result = SAVEMakeDirAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), errHandling, asyncParams.GetPtr()); - cemuLog_log(LogType::Save, "SAVEMakeDirAsync(0x{:08x}, 0x{:08x}, {:x}, {}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEMakeDir(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling) - { - MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams asyncParams; - asyncParams.ioMsgQueue = nullptr; - asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); - - StackAllocator<AsyncCallbackParam_t> param; - param->thread = currentThread; - param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = ¶m; - - SAVEStatus status = SAVEMakeDirAsync(client, block, accountSlot, path, errHandling, &asyncParams); - if (status == (FSStatus)FS_RESULT::SUCCESS) - { - coreinit_suspendThread(currentThread, 1000); - PPCCore_switchToScheduler(); - return param->returnStatus; - } - - return status; - } - - void export_SAVEMakeDir(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamU32(errHandling, 4); - - const SAVEStatus result = SAVEMakeDir(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), errHandling); - cemuLog_log(LogType::Save, "SAVEMakeDir(0x{:08x}, 0x{:08x}, {:x}, {}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEOpenFile(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling) - { - MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams asyncParams; - asyncParams.ioMsgQueue = nullptr; - asyncParams.userCallback = PPCInterpreter_makeCallableExportDepr(AsyncCallback); - - StackAllocator<AsyncCallbackParam_t> param; - param->thread = currentThread; - param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = param.GetPointer(); - - SAVEStatus status = SAVEOpenFileAsync(client, block, accountSlot, path, mode, outFileHandle, errHandling, &asyncParams); - if (status == (FSStatus)FS_RESULT::SUCCESS) - { - coreinit_suspendThread(currentThread, 1000); - PPCCore_switchToScheduler(); - return param->returnStatus; - } - - return status; - } - - void export_SAVEInitSaveDir(PPCInterpreter_t* hCPU) - { - ppcDefineParamU8(accountSlot, 0); - const SAVEStatus result = SAVEInitSaveDir(accountSlot); - cemuLog_log(LogType::Save, "SAVEInitSaveDir({:x}) -> {:x}", accountSlot, result); - osLib_returnFromFunction(hCPU, result); - } - - void export_SAVEGetStatAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamMEMPTR(stat, FSStat_t, 4); - ppcDefineParamU32(errHandling, 5); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 6); - - const SAVEStatus result = SAVEGetStatAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), stat.GetPtr(), errHandling, asyncParams.GetPtr()); - cemuLog_log(LogType::Save, "SAVEGetStatAsync(0x{:08x}, 0x{:08x}, {:x}, {}, 0x{:08x}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), stat.GetMPTR(), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEGetStat(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) - { - MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams asyncParams; - asyncParams.ioMsgQueue = nullptr; - asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); - - StackAllocator<AsyncCallbackParam_t> param; - param->thread = currentThread; - param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = ¶m; - - SAVEStatus status = SAVEGetStatAsync(client, block, accountSlot, path, stat, errHandling, &asyncParams); - if (status == (FSStatus)FS_RESULT::SUCCESS) - { - coreinit_suspendThread(currentThread, 1000); - PPCCore_switchToScheduler(); - return param->returnStatus; - } - - return status; - } - - void export_SAVEGetStat(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamMEMPTR(stat, FSStat_t, 4); - ppcDefineParamU32(errHandling, 5); - - const SAVEStatus result = SAVEGetStat(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), stat.GetPtr(), errHandling); - cemuLog_log(LogType::Save, "SAVEGetStat(0x{:08x}, 0x{:08x}, {:x}, {}, 0x{:08x}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), stat.GetMPTR(), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - - void export_SAVEGetStatOtherApplicationAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU64(titleId, 2); - ppcDefineParamU8(accountSlot, 4); - ppcDefineParamMEMPTR(path, const char, 5); - ppcDefineParamMEMPTR(stat, FSStat_t, 6); - ppcDefineParamU32(errHandling, 7); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 8); - - const SAVEStatus result = SAVEGetStatOtherApplicationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), titleId, accountSlot, path.GetPtr(), stat.GetPtr(), errHandling, asyncParams.GetPtr()); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEGetStatOtherApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint64 titleId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) - { - MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams asyncParams; - asyncParams.ioMsgQueue = nullptr; - asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); - - StackAllocator<AsyncCallbackParam_t> param; - param->thread = currentThread; - param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = ¶m; - - SAVEStatus status = SAVEGetStatOtherApplicationAsync(client, block, titleId, accountSlot, path, stat, errHandling, &asyncParams); - if (status == (FSStatus)FS_RESULT::SUCCESS) - { - coreinit_suspendThread(currentThread, 1000); - PPCCore_switchToScheduler(); - return param->returnStatus; - } - - return status; - } - - void export_SAVEGetStatOtherApplication(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU64(titleId, 2); - ppcDefineParamU8(accountSlot, 4); - ppcDefineParamMEMPTR(path, const char, 5); - ppcDefineParamMEMPTR(stat, FSStat_t, 6); - ppcDefineParamU32(errHandling, 7); - - const SAVEStatus result = SAVEGetStatOtherApplication(fsClient.GetPtr(), fsCmdBlock.GetPtr(), titleId, accountSlot, path.GetPtr(), stat.GetPtr(), errHandling); - osLib_returnFromFunction(hCPU, result); - } - - - void export_SAVEGetStatOtherNormalApplicationAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU32(uniqueId, 2); - ppcDefineParamU8(accountSlot, 3); - ppcDefineParamMEMPTR(path, const char, 4); - ppcDefineParamMEMPTR(stat, FSStat_t, 5); - ppcDefineParamU32(errHandling, 6); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 8); - - const SAVEStatus result = SAVEGetStatOtherNormalApplicationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, accountSlot, path.GetPtr(), stat.GetPtr(), errHandling, asyncParams.GetPtr()); - osLib_returnFromFunction(hCPU, result); - } - - SAVEStatus SAVEGetStatOtherNormalApplication(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) - { - uint64 titleId = SAVE_UNIQUE_TO_TITLE_ID(uniqueId); - return SAVEGetStatOtherApplication(client, block, titleId, accountSlot, path, stat, errHandling); - } - - void export_SAVEGetStatOtherNormalApplication(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU32(uniqueId, 2); - ppcDefineParamU8(accountSlot, 3); - ppcDefineParamMEMPTR(path, const char, 4); - ppcDefineParamMEMPTR(stat, FSStat_t, 5); - ppcDefineParamU32(errHandling, 6); - - const SAVEStatus result = SAVEGetStatOtherNormalApplication(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, accountSlot, path.GetPtr(), stat.GetPtr(), errHandling); - osLib_returnFromFunction(hCPU, result); - } - - - - void export_SAVEGetStatOtherNormalApplicationVariationAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU32(uniqueId, 2); - ppcDefineParamU8(variation, 3); - ppcDefineParamU8(accountSlot, 4); - ppcDefineParamMEMPTR(path, const char, 5); - ppcDefineParamMEMPTR(stat, FSStat_t, 6); - ppcDefineParamU32(errHandling, 7); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 8); - - const SAVEStatus result = SAVEGetStatOtherNormalApplicationVariationAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, variation, accountSlot, path.GetPtr(), stat.GetPtr(), errHandling, asyncParams.GetPtr()); - osLib_returnFromFunction(hCPU, result); - } - SAVEStatus SAVEGetStatOtherNormalApplicationVariation(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint32 uniqueId, uint8 variation, uint8 accountSlot, const char* path, FSStat_t* stat, FS_ERROR_MASK errHandling) { //peterBreak(); @@ -1208,21 +623,140 @@ namespace save return SAVEGetStatOtherApplication(client, block, titleId, accountSlot, path, stat, errHandling); } - void export_SAVEGetStatOtherNormalApplicationVariation(PPCInterpreter_t* hCPU) + SAVEStatus SAVEGetFreeSpaceSizeAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FSLargeSize* freeSize, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU32(uniqueId, 2); - ppcDefineParamU8(variation, 3); - ppcDefineParamU8(accountSlot, 4); - ppcDefineParamMEMPTR(path, const char, 5); - ppcDefineParamMEMPTR(stat, FSStat_t, 6); - ppcDefineParamU32(errHandling, 7); + SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); - const SAVEStatus result = SAVEGetStatOtherNormalApplicationVariation(fsClient.GetPtr(), fsCmdBlock.GetPtr(), uniqueId, variation, accountSlot, path.GetPtr(), stat.GetPtr(), errHandling); - osLib_returnFromFunction(hCPU, result); + OSLockMutex(&g_nn_save->mutex); + + uint32 persistentId; + if (GetPersistentIdEx(accountSlot, &persistentId)) + { + char fullPath[SAVE_MAX_PATH_SIZE]; + if (GetAbsoluteFullPath(persistentId, nullptr, fullPath)) + result = coreinit::FSGetFreeSpaceSizeAsync(client, block, fullPath, freeSize, errHandling, (FSAsyncParams*)asyncParams); + } + else + result = (FSStatus)FS_RESULT::NOT_FOUND; + + OSUnlockMutex(&g_nn_save->mutex); + + return result; } + SAVEStatus SAVEGetFreeSpaceSize(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FSLargeSize* freeSize, FS_ERROR_MASK errHandling) + { + StackAllocator<AsyncToSyncWrapper> asyncData; + SAVEStatus status = SAVEGetFreeSpaceSizeAsync(client, block, accountSlot, freeSize, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); + } + + SAVEStatus SAVERemoveAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) + { + SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); + + OSLockMutex(&g_nn_save->mutex); + + uint32 persistentId; + if (GetPersistentIdEx(accountSlot, &persistentId)) + { + char fullPath[SAVE_MAX_PATH_SIZE]; + if (GetAbsoluteFullPath(persistentId, path, fullPath)) + result = coreinit::FSRemoveAsync(client, block, (uint8*)fullPath, errHandling, (FSAsyncParams*)asyncParams); + } + else + result = (FSStatus)FS_RESULT::NOT_FOUND; + + OSUnlockMutex(&g_nn_save->mutex); + return result; + } + + SAVEStatus SAVERemove(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling) + { + StackAllocator<AsyncToSyncWrapper> asyncData; + SAVEStatus status = SAVERemoveAsync(client, block, accountSlot, path, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); + } + + SAVEStatus SAVERenameAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* oldPath, const char* newPath, FS_ERROR_MASK errHandling, FSAsyncParams* asyncParams) + { + SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); + OSLockMutex(&g_nn_save->mutex); + + uint32 persistentId; + if (GetPersistentIdEx(accountSlot, &persistentId)) + { + char fullOldPath[SAVE_MAX_PATH_SIZE]; + if (GetAbsoluteFullPath(persistentId, oldPath, fullOldPath)) + { + char fullNewPath[SAVE_MAX_PATH_SIZE]; + if (GetAbsoluteFullPath(persistentId, newPath, fullNewPath)) + result = coreinit::FSRenameAsync(client, block, fullOldPath, fullNewPath, errHandling, asyncParams); + } + } + else + result = (FSStatus)FS_RESULT::NOT_FOUND; + + OSUnlockMutex(&g_nn_save->mutex); + return result; + } + + SAVEStatus SAVERename(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* oldPath, const char* newPath, FS_ERROR_MASK errHandling) + { + StackAllocator<AsyncToSyncWrapper> asyncData; + SAVEStatus status = SAVERenameAsync(client, block, accountSlot, oldPath, newPath, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); + } + + SAVEStatus SAVEMakeDirAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) + { + SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); + + OSLockMutex(&g_nn_save->mutex); + + uint32 persistentId; + if (GetPersistentIdEx(accountSlot, &persistentId)) + { + char fullPath[SAVE_MAX_PATH_SIZE]; + if (GetAbsoluteFullPath(persistentId, path, fullPath)) + result = coreinit::FSMakeDirAsync(client, block, fullPath, errHandling, (FSAsyncParams*)asyncParams); + } + else + result = (FSStatus)FS_RESULT::NOT_FOUND; + + OSUnlockMutex(&g_nn_save->mutex); + + return result; + } + + SAVEStatus SAVEMakeDir(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling) + { + StackAllocator<AsyncToSyncWrapper> asyncData; + SAVEStatus status = SAVEMakeDirAsync(client, block, accountSlot, path, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); + } + + SAVEStatus SAVEOpenFile(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, const char* mode, FSFileHandlePtr outFileHandle, FS_ERROR_MASK errHandling) + { + StackAllocator<AsyncToSyncWrapper> asyncData; + SAVEStatus status = SAVEOpenFileAsync(client, block, accountSlot, path, mode, outFileHandle, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); + } SAVEStatus SAVEGetSharedDataTitlePath(uint64 titleId, const char* dataFileName, char* output, sint32 outputLength) { @@ -1234,17 +768,6 @@ namespace save return result; } - void export_SAVEGetSharedDataTitlePath(PPCInterpreter_t* hCPU) - { - ppcDefineParamU64(titleId, 0); - ppcDefineParamMEMPTR(dataFileName, const char, 2); - ppcDefineParamMEMPTR(output, char, 3); - ppcDefineParamS32(outputLength, 4); - const SAVEStatus result = SAVEGetSharedDataTitlePath(titleId, dataFileName.GetPtr(), output.GetPtr(), outputLength); - cemuLog_log(LogType::Save, "SAVEGetSharedDataTitlePath(0x{:x}, {}, {}, 0x{:x}) -> {:x}", titleId, dataFileName.GetPtr(), output.GetPtr(), outputLength, result); - osLib_returnFromFunction(hCPU, result); - } - SAVEStatus SAVEGetSharedSaveDataPath(uint64 titleId, const char* dataFileName, char* output, uint32 outputLength) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -1255,16 +778,6 @@ namespace save return result; } - void export_SAVEGetSharedSaveDataPath(PPCInterpreter_t* hCPU) - { - ppcDefineParamU64(titleId, 0); - ppcDefineParamMEMPTR(dataFileName, const char, 2); - ppcDefineParamMEMPTR(output, char, 3); - ppcDefineParamU32(outputLength, 4); - const SAVEStatus result = SAVEGetSharedSaveDataPath(titleId, dataFileName.GetPtr(), output.GetPtr(), outputLength); - osLib_returnFromFunction(hCPU, result); - } - SAVEStatus SAVEChangeDirAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) { SAVEStatus result = (FSStatus)(FS_RESULT::FATAL_ERROR); @@ -1284,52 +797,14 @@ namespace save return result; } - void export_SAVEChangeDirAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamU32(errHandling, 4); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 5); - const SAVEStatus result = SAVEChangeDirAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), errHandling, asyncParams.GetPtr()); - cemuLog_log(LogType::Save, "SAVEChangeDirAsync(0x{:08x}, 0x{:08x}, {:x}, {}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - SAVEStatus SAVEChangeDir(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, const char* path, FS_ERROR_MASK errHandling) { - MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams asyncParams; - asyncParams.ioMsgQueue = nullptr; - asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); - - StackAllocator<AsyncCallbackParam_t> param; - param->thread = currentThread; - param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = ¶m; - - SAVEStatus status = SAVEChangeDirAsync(client, block, accountSlot, path, errHandling, &asyncParams); - if (status == (FSStatus)FS_RESULT::SUCCESS) - { - coreinit_suspendThread(currentThread, 1000); - PPCCore_switchToScheduler(); - return param->returnStatus; - } - - return status; - } - - void export_SAVEChangeDir(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamMEMPTR(path, const char, 3); - ppcDefineParamU32(errHandling, 4); - const SAVEStatus result = SAVEChangeDir(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, path.GetPtr(), errHandling); - cemuLog_log(LogType::Save, "SAVEChangeDir(0x{:08x}, 0x{:08x}, {:x}, {}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, path.GetPtr(), errHandling, result); - osLib_returnFromFunction(hCPU, result); + StackAllocator<AsyncToSyncWrapper> asyncData; + SAVEStatus status = SAVEChangeDirAsync(client, block, accountSlot, path, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); } SAVEStatus SAVEFlushQuotaAsync(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FS_ERROR_MASK errHandling, const FSAsyncParams* asyncParams) @@ -1355,71 +830,45 @@ namespace save return result; } - void export_SAVEFlushQuotaAsync(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamU32(errHandling, 3); - ppcDefineParamMEMPTR(asyncParams, FSAsyncParams, 4); - const SAVEStatus result = SAVEFlushQuotaAsync(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, errHandling, asyncParams.GetPtr()); - cemuLog_log(LogType::Save, "SAVEFlushQuotaAsync(0x{:08x}, 0x{:08x}, {:x}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, errHandling, result); - osLib_returnFromFunction(hCPU, result); - } - SAVEStatus SAVEFlushQuota(coreinit::FSClient_t* client, coreinit::FSCmdBlock_t* block, uint8 accountSlot, FS_ERROR_MASK errHandling) { - MEMPTR<OSThread_t> currentThread{coreinit::OSGetCurrentThread()}; - FSAsyncParams asyncParams; - asyncParams.ioMsgQueue = nullptr; - asyncParams.userCallback = RPLLoader_MakePPCCallable(AsyncCallback); - - StackAllocator<AsyncCallbackParam_t> param; - param->thread = currentThread; - param->returnStatus = (FSStatus)FS_RESULT::SUCCESS; - asyncParams.userContext = ¶m; - - SAVEStatus status = SAVEFlushQuotaAsync(client, block, accountSlot, errHandling, &asyncParams); - if (status == (FSStatus)FS_RESULT::SUCCESS) - { - coreinit_suspendThread(currentThread, 1000); - PPCCore_switchToScheduler(); - return param->returnStatus; - } - return status; - } - - void export_SAVEFlushQuota(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 0); - ppcDefineParamMEMPTR(fsCmdBlock, coreinit::FSCmdBlock_t, 1); - ppcDefineParamU8(accountSlot, 2); - ppcDefineParamU32(errHandling, 3); - const SAVEStatus result = SAVEFlushQuota(fsClient.GetPtr(), fsCmdBlock.GetPtr(), accountSlot, errHandling); - cemuLog_log(LogType::Save, "SAVEFlushQuota(0x{:08x}, 0x{:08x}, {:x}, {:x}) -> {:x}", fsClient.GetMPTR(), fsCmdBlock.GetMPTR(), accountSlot, errHandling, result); - osLib_returnFromFunction(hCPU, result); + StackAllocator<AsyncToSyncWrapper> asyncData; + SAVEStatus status = SAVEFlushQuotaAsync(client, block, accountSlot, errHandling, asyncData->GetAsyncParams()); + if (status != (FSStatus)FS_RESULT::SUCCESS) + return status; + asyncData->WaitForEvent(); + return asyncData->GetResult(); } void load() { + cafeExportRegister("nn_save", SAVEInit, LogType::Save); + cafeExportRegister("nn_save", SAVEInitSaveDir, LogType::Save); + cafeExportRegister("nn_save", SAVEGetSharedDataTitlePath, LogType::Save); + cafeExportRegister("nn_save", SAVEGetSharedSaveDataPath, LogType::Save); - osLib_addFunction("nn_save", "SAVEInit", export_SAVEInit); - osLib_addFunction("nn_save", "SAVEInitSaveDir", export_SAVEInitSaveDir); - osLib_addFunction("nn_save", "SAVEGetSharedDataTitlePath", export_SAVEGetSharedDataTitlePath); - osLib_addFunction("nn_save", "SAVEGetSharedSaveDataPath", export_SAVEGetSharedSaveDataPath); + cafeExportRegister("nn_save", SAVEGetFreeSpaceSize, LogType::Save); + cafeExportRegister("nn_save", SAVEGetFreeSpaceSizeAsync, LogType::Save); + cafeExportRegister("nn_save", SAVEMakeDir, LogType::Save); + cafeExportRegister("nn_save", SAVEMakeDirAsync, LogType::Save); + cafeExportRegister("nn_save", SAVERemove, LogType::Save); + cafeExportRegister("nn_save", SAVERemoveAsync, LogType::Save); + cafeExportRegister("nn_save", SAVEChangeDir, LogType::Save); + cafeExportRegister("nn_save", SAVEChangeDirAsync, LogType::Save); + cafeExportRegister("nn_save", SAVERename, LogType::Save); + cafeExportRegister("nn_save", SAVERenameAsync, LogType::Save); + cafeExportRegister("nn_save", SAVEFlushQuota, LogType::Save); + cafeExportRegister("nn_save", SAVEFlushQuotaAsync, LogType::Save); - // sync functions - osLib_addFunction("nn_save", "SAVEGetFreeSpaceSize", export_SAVEGetFreeSpaceSize); - osLib_addFunction("nn_save", "SAVEMakeDir", export_SAVEMakeDir); - osLib_addFunction("nn_save", "SAVERemove", export_SAVERemove); - osLib_addFunction("nn_save", "SAVEChangeDir", export_SAVEChangeDir); - osLib_addFunction("nn_save", "SAVEFlushQuota", export_SAVEFlushQuota); - - osLib_addFunction("nn_save", "SAVEGetStat", export_SAVEGetStat); - osLib_addFunction("nn_save", "SAVEGetStatOtherApplication", export_SAVEGetStatOtherApplication); - osLib_addFunction("nn_save", "SAVEGetStatOtherNormalApplication", export_SAVEGetStatOtherNormalApplication); - osLib_addFunction("nn_save", "SAVEGetStatOtherNormalApplicationVariation", export_SAVEGetStatOtherNormalApplicationVariation); + cafeExportRegister("nn_save", SAVEGetStat, LogType::Save); + cafeExportRegister("nn_save", SAVEGetStatAsync, LogType::Save); + cafeExportRegister("nn_save", SAVEGetStatOtherApplication, LogType::Save); + cafeExportRegister("nn_save", SAVEGetStatOtherApplicationAsync, LogType::Save); + cafeExportRegister("nn_save", SAVEGetStatOtherNormalApplication, LogType::Save); + cafeExportRegister("nn_save", SAVEGetStatOtherNormalApplicationAsync, LogType::Save); + cafeExportRegister("nn_save", SAVEGetStatOtherNormalApplicationVariation, LogType::Save); + cafeExportRegister("nn_save", SAVEGetStatOtherNormalApplicationVariationAsync, LogType::Save); cafeExportRegister("nn_save", SAVEOpenFile, LogType::Save); cafeExportRegister("nn_save", SAVEOpenFileAsync, LogType::Save); @@ -1430,30 +879,14 @@ namespace save cafeExportRegister("nn_save", SAVEOpenFileOtherNormalApplicationVariation, LogType::Save); cafeExportRegister("nn_save", SAVEOpenFileOtherNormalApplicationVariationAsync, LogType::Save); - osLib_addFunction("nn_save", "SAVEOpenDir", export_SAVEOpenDir); - osLib_addFunction("nn_save", "SAVEOpenDirOtherApplication", export_SAVEOpenDirOtherApplication); - osLib_addFunction("nn_save", "SAVEOpenDirOtherNormalApplication", export_SAVEOpenDirOtherNormalApplication); - osLib_addFunction("nn_save", "SAVEOpenDirOtherNormalApplicationVariation", export_SAVEOpenDirOtherNormalApplicationVariation); - - // async functions - osLib_addFunction("nn_save", "SAVEGetFreeSpaceSizeAsync", export_SAVEGetFreeSpaceSizeAsync); - osLib_addFunction("nn_save", "SAVEMakeDirAsync", export_SAVEMakeDirAsync); - osLib_addFunction("nn_save", "SAVERemoveAsync", export_SAVERemoveAsync); - osLib_addFunction("nn_save", "SAVERenameAsync", export_SAVERenameAsync); - cafeExportRegister("nn_save", SAVERename, LogType::Save); - - osLib_addFunction("nn_save", "SAVEChangeDirAsync", export_SAVEChangeDirAsync); - osLib_addFunction("nn_save", "SAVEFlushQuotaAsync", export_SAVEFlushQuotaAsync); - - osLib_addFunction("nn_save", "SAVEGetStatAsync", export_SAVEGetStatAsync); - osLib_addFunction("nn_save", "SAVEGetStatOtherApplicationAsync", export_SAVEGetStatOtherApplicationAsync); - osLib_addFunction("nn_save", "SAVEGetStatOtherNormalApplicationAsync", export_SAVEGetStatOtherNormalApplicationAsync); - osLib_addFunction("nn_save", "SAVEGetStatOtherNormalApplicationVariationAsync", export_SAVEGetStatOtherNormalApplicationVariationAsync); - - osLib_addFunction("nn_save", "SAVEOpenDirAsync", export_SAVEOpenDirAsync); - osLib_addFunction("nn_save", "SAVEOpenDirOtherApplicationAsync", export_SAVEOpenDirOtherApplicationAsync); - osLib_addFunction("nn_save", "SAVEOpenDirOtherNormalApplicationAsync", export_SAVEOpenDirOtherNormalApplicationAsync); - osLib_addFunction("nn_save", "SAVEOpenDirOtherNormalApplicationVariationAsync", export_SAVEOpenDirOtherNormalApplicationVariationAsync); + cafeExportRegister("nn_save", SAVEOpenDir, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenDirAsync, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenDirOtherApplication, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenDirOtherApplicationAsync, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenDirOtherNormalApplication, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenDirOtherNormalApplicationVariation, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenDirOtherNormalApplicationAsync, LogType::Save); + cafeExportRegister("nn_save", SAVEOpenDirOtherNormalApplicationVariationAsync, LogType::Save); } void ResetToDefaultState() diff --git a/src/Common/MemPtr.h b/src/Common/MemPtr.h index b2362d0b..5fb73479 100644 --- a/src/Common/MemPtr.h +++ b/src/Common/MemPtr.h @@ -11,7 +11,7 @@ using PAddr = uint32; // physical address extern uint8* memory_base; extern uint8* PPCInterpreterGetStackPointer(); -extern uint8* PPCInterpreterGetAndModifyStackPointer(sint32 offset); +extern uint8* PPCInterpreter_PushAndReturnStackPointer(sint32 offset); extern void PPCInterpreterModifyStackPointer(sint32 offset); class MEMPTRBase {}; diff --git a/src/Common/StackAllocator.h b/src/Common/StackAllocator.h index 1dc52d51..856224cf 100644 --- a/src/Common/StackAllocator.h +++ b/src/Common/StackAllocator.h @@ -10,14 +10,16 @@ public: explicit StackAllocator(const uint32 items) { + m_items = items; m_modified_size = count * sizeof(T) * items + kStaticMemOffset * 2; - - auto tmp = PPCInterpreterGetStackPointer(); - m_ptr = (T*)(PPCInterpreterGetAndModifyStackPointer(m_modified_size) + kStaticMemOffset); + m_modified_size = (m_modified_size/8+7) * 8; // pad to 8 bytes + m_ptr = new(PPCInterpreter_PushAndReturnStackPointer(m_modified_size) + kStaticMemOffset) T[count * items](); } ~StackAllocator() { + for (size_t i = 0; i < count * m_items; ++i) + m_ptr[i].~T(); PPCInterpreterModifyStackPointer(-m_modified_size); } @@ -64,4 +66,5 @@ private: T* m_ptr; sint32 m_modified_size; + uint32 m_items; }; \ No newline at end of file From f576269ed0e52f5487a1e8ad19a109b5d4214bf0 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 29 May 2024 00:34:11 +0200 Subject: [PATCH 128/299] Refactor legacy method of emulating thread events --- src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp | 18 --------- src/Cafe/OS/libs/coreinit/coreinit_Thread.h | 5 --- src/Cafe/OS/libs/nn_idbe/nn_idbe.cpp | 13 ++++--- src/Cafe/OS/libs/nsyshid/nsyshid.cpp | 38 +++++++++---------- 4 files changed, 26 insertions(+), 48 deletions(-) diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp index b53d04ed..2f3808b7 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp @@ -1608,21 +1608,3 @@ namespace coreinit } } - -void coreinit_suspendThread(OSThread_t* OSThreadBE, sint32 count) -{ - // for legacy source - OSThreadBE->suspendCounter += count; -} - -void coreinit_resumeThread(OSThread_t* OSThreadBE, sint32 count) -{ - __OSLockScheduler(); - coreinit::__OSResumeThreadInternal(OSThreadBE, count); - __OSUnlockScheduler(); -} - -OSThread_t* coreinitThread_getCurrentThreadDepr(PPCInterpreter_t* hCPU) -{ - return coreinit::__currentCoreThread[PPCInterpreter_getCoreIndex(hCPU)]; -} diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.h b/src/Cafe/OS/libs/coreinit/coreinit_Thread.h index 8b144bd3..df787bf0 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.h @@ -621,11 +621,6 @@ namespace coreinit #pragma pack() // deprecated / clean up required -void coreinit_suspendThread(OSThread_t* OSThreadBE, sint32 count = 1); -void coreinit_resumeThread(OSThread_t* OSThreadBE, sint32 count = 1); - -OSThread_t* coreinitThread_getCurrentThreadDepr(PPCInterpreter_t* hCPU); - extern MPTR activeThread[256]; extern sint32 activeThreadCount; diff --git a/src/Cafe/OS/libs/nn_idbe/nn_idbe.cpp b/src/Cafe/OS/libs/nn_idbe/nn_idbe.cpp index a69f32a3..eb0178fe 100644 --- a/src/Cafe/OS/libs/nn_idbe/nn_idbe.cpp +++ b/src/Cafe/OS/libs/nn_idbe/nn_idbe.cpp @@ -40,7 +40,7 @@ namespace nn static_assert(offsetof(nnIdbeEncryptedIcon_t, iconData) == 0x22, ""); static_assert(sizeof(nnIdbeEncryptedIcon_t) == 0x12082); - void asyncDownloadIconFile(uint64 titleId, nnIdbeEncryptedIcon_t* iconOut, OSThread_t* thread) + void asyncDownloadIconFile(uint64 titleId, nnIdbeEncryptedIcon_t* iconOut, coreinit::OSEvent* event) { std::vector<uint8> idbeData = NAPI::IDBE_RequestRawEncrypted(ActiveSettings::GetNetworkService(), titleId); if (idbeData.size() != sizeof(nnIdbeEncryptedIcon_t)) @@ -48,11 +48,11 @@ namespace nn // icon does not exist or has the wrong size cemuLog_log(LogType::Force, "IDBE: Failed to retrieve icon for title {:016x}", titleId); memset(iconOut, 0, sizeof(nnIdbeEncryptedIcon_t)); - coreinit_resumeThread(thread); + coreinit::OSSignalEvent(event); return; } memcpy(iconOut, idbeData.data(), sizeof(nnIdbeEncryptedIcon_t)); - coreinit_resumeThread(thread); + coreinit::OSSignalEvent(event); } void export_DownloadIconFile(PPCInterpreter_t* hCPU) @@ -62,9 +62,10 @@ namespace nn ppcDefineParamU32(uknR7, 4); ppcDefineParamU32(uknR8, 5); - auto asyncTask = std::async(std::launch::async, asyncDownloadIconFile, titleId, encryptedIconData, coreinit::OSGetCurrentThread()); - coreinit::OSSuspendThread(coreinit::OSGetCurrentThread()); - PPCCore_switchToScheduler(); + StackAllocator<coreinit::OSEvent> event; + coreinit::OSInitEvent(&event, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); + auto asyncTask = std::async(std::launch::async, asyncDownloadIconFile, titleId, encryptedIconData, &event); + coreinit::OSWaitEvent(&event); osLib_returnFromFunction(hCPU, 1); } diff --git a/src/Cafe/OS/libs/nsyshid/nsyshid.cpp b/src/Cafe/OS/libs/nsyshid/nsyshid.cpp index ba3e3b96..ff5c4f45 100644 --- a/src/Cafe/OS/libs/nsyshid/nsyshid.cpp +++ b/src/Cafe/OS/libs/nsyshid/nsyshid.cpp @@ -429,8 +429,7 @@ namespace nsyshid // handler for synchronous HIDSetReport transfers sint32 _hidSetReportSync(std::shared_ptr<Device> device, uint8* reportData, sint32 length, - uint8* originalData, - sint32 originalLength, OSThread_t* osThread) + uint8* originalData, sint32 originalLength, coreinit::OSEvent* event) { _debugPrintHex("_hidSetReportSync Begin", reportData, length); sint32 returnCode = 0; @@ -440,7 +439,7 @@ namespace nsyshid } free(reportData); cemuLog_logDebug(LogType::Force, "_hidSetReportSync end. returnCode: {}", returnCode); - coreinit_resumeThread(osThread, 1000); + coreinit::OSSignalEvent(event); return returnCode; } @@ -484,11 +483,12 @@ namespace nsyshid sint32 returnCode = 0; if (callbackFuncMPTR == MPTR_NULL) { + // synchronous + StackAllocator<coreinit::OSEvent> event; + coreinit::OSInitEvent(&event, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); std::future<sint32> res = std::async(std::launch::async, &_hidSetReportSync, device, reportData, - paddedLength + 1, data, dataLength, - coreinitThread_getCurrentThreadDepr(hCPU)); - coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(hCPU), 1000); - PPCCore_switchToScheduler(); + paddedLength + 1, data, dataLength, &event); + coreinit::OSWaitEvent(&event); returnCode = res.get(); } else @@ -557,10 +557,10 @@ namespace nsyshid sint32 _hidReadSync(std::shared_ptr<Device> device, uint8* data, sint32 maxLength, - OSThread_t* osThread) + coreinit::OSEvent* event) { sint32 returnCode = _hidReadInternalSync(device, data, maxLength); - coreinit_resumeThread(osThread, 1000); + coreinit::OSSignalEvent(event); return returnCode; } @@ -591,10 +591,10 @@ namespace nsyshid else { // synchronous transfer - std::future<sint32> res = std::async(std::launch::async, &_hidReadSync, device, data, maxLength, - coreinitThread_getCurrentThreadDepr(hCPU)); - coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(hCPU), 1000); - PPCCore_switchToScheduler(); + StackAllocator<coreinit::OSEvent> event; + coreinit::OSInitEvent(&event, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); + std::future<sint32> res = std::async(std::launch::async, &_hidReadSync, device, data, maxLength, &event); + coreinit::OSWaitEvent(&event); returnCode = res.get(); } @@ -654,10 +654,10 @@ namespace nsyshid sint32 _hidWriteSync(std::shared_ptr<Device> device, uint8* data, sint32 maxLength, - OSThread_t* osThread) + coreinit::OSEvent* event) { sint32 returnCode = _hidWriteInternalSync(device, data, maxLength); - coreinit_resumeThread(osThread, 1000); + coreinit::OSSignalEvent(event); return returnCode; } @@ -688,10 +688,10 @@ namespace nsyshid else { // synchronous transfer - std::future<sint32> res = std::async(std::launch::async, &_hidWriteSync, device, data, maxLength, - coreinitThread_getCurrentThreadDepr(hCPU)); - coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(hCPU), 1000); - PPCCore_switchToScheduler(); + StackAllocator<coreinit::OSEvent> event; + coreinit::OSInitEvent(&event, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); + std::future<sint32> res = std::async(std::launch::async, &_hidWriteSync, device, data, maxLength, &event); + coreinit::OSWaitEvent(&event); returnCode = res.get(); } From d33337d5394754a738989fe4736abc534cefe8cb Mon Sep 17 00:00:00 2001 From: Colin Kinloch <colin@kinlo.ch> Date: Tue, 28 May 2024 23:36:12 +0100 Subject: [PATCH 129/299] Fix GamePad window size (#1224) --- src/gui/PadViewFrame.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/PadViewFrame.cpp b/src/gui/PadViewFrame.cpp index 744df323..f2da2ca7 100644 --- a/src/gui/PadViewFrame.cpp +++ b/src/gui/PadViewFrame.cpp @@ -72,7 +72,7 @@ void PadViewFrame::InitializeRenderCanvas() m_render_canvas = GLCanvas_Create(this, wxSize(854, 480), false); sizer->Add(m_render_canvas, 1, wxEXPAND, 0, nullptr); } - SetSizer(sizer); + SetSizerAndFit(sizer); Layout(); m_render_canvas->Bind(wxEVT_KEY_UP, &PadViewFrame::OnKeyUp, this); From 5f825a1fa8eacf87b18a5b5666080c7c0dd55926 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 2 Jun 2024 20:29:10 +0200 Subject: [PATCH 130/299] Latte: Always allow views with the same format as base texture Fixes crash/assert in VC N64 titles --- src/Cafe/HW/Latte/Core/LatteTexture.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Cafe/HW/Latte/Core/LatteTexture.cpp b/src/Cafe/HW/Latte/Core/LatteTexture.cpp index 3754fb19..d8852891 100644 --- a/src/Cafe/HW/Latte/Core/LatteTexture.cpp +++ b/src/Cafe/HW/Latte/Core/LatteTexture.cpp @@ -235,6 +235,9 @@ void LatteTexture_InitSliceAndMipInfo(LatteTexture* texture) // if this function returns false, textures will not be synchronized even if their data overlaps bool LatteTexture_IsFormatViewCompatible(Latte::E_GX2SURFFMT formatA, Latte::E_GX2SURFFMT formatB) { + if(formatA == formatB) + return true; // if the format is identical then compatibility must be guaranteed (otherwise we can't create the necessary default view of a texture) + // todo - find a better way to handle this for (sint32 swap = 0; swap < 2; swap++) { From 16070458edddb8bd73baf3cbb1678f5a8b2dbc5e Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 2 Jun 2024 21:39:06 +0200 Subject: [PATCH 131/299] Logging: Restructure menu + allow toggeling APIErrors logtype The logtype "APIErrors" previously was always enabled. This option is intended to help homebrew developers notice mistakes in how they use CafeOS API. But some commercial games trigger these a lot and cause log.txt bloat (e.g. seen in XCX). Thus this commit changes it so that it's off by default and instead can be toggled if desired. Additionally in this commit: - COS module logging options are no longer translatable (our debug logging is fundamentally English) - Restructured the log menu and moved the logging options that are mainly of interest to Cemu devs into a separate submenu --- src/Cafe/OS/libs/gx2/GX2_Command.cpp | 8 ----- src/Cafe/OS/libs/gx2/GX2_Command.h | 4 +-- src/Cafe/OS/libs/gx2/GX2_ContextState.cpp | 3 +- src/Cafe/OS/libs/gx2/GX2_Shader.cpp | 2 +- src/Cemu/Logging/CemuLogging.cpp | 1 + src/Cemu/Logging/CemuLogging.h | 2 +- src/gui/MainWindow.cpp | 44 +++++++++++++---------- 7 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/Cafe/OS/libs/gx2/GX2_Command.cpp b/src/Cafe/OS/libs/gx2/GX2_Command.cpp index 804e3da0..ec96a4ff 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Command.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_Command.cpp @@ -154,14 +154,6 @@ namespace GX2 return gx2WriteGatherPipe.displayListStart[coreIndex] != MPTR_NULL; } - bool GX2WriteGather_isDisplayListActive() - { - uint32 coreIndex = coreinit::OSGetCoreId(); - if (gx2WriteGatherPipe.displayListStart[coreIndex] != MPTR_NULL) - return true; - return false; - } - uint32 GX2WriteGather_getReadWriteDistance() { uint32 coreIndex = sGX2MainCoreIndex; diff --git a/src/Cafe/OS/libs/gx2/GX2_Command.h b/src/Cafe/OS/libs/gx2/GX2_Command.h index 635680e0..51c04928 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Command.h +++ b/src/Cafe/OS/libs/gx2/GX2_Command.h @@ -84,8 +84,6 @@ inline void gx2WriteGather_submit(Targs... args) namespace GX2 { - - bool GX2WriteGather_isDisplayListActive(); uint32 GX2WriteGather_getReadWriteDistance(); void GX2WriteGather_checkAndInsertWrapAroundMark(); @@ -96,6 +94,8 @@ namespace GX2 void GX2CallDisplayList(MPTR addr, uint32 size); void GX2DirectCallDisplayList(void* addr, uint32 size); + bool GX2GetDisplayListWriteStatus(); + void GX2Init_writeGather(); void GX2CommandInit(); void GX2CommandResetToDefaultState(); diff --git a/src/Cafe/OS/libs/gx2/GX2_ContextState.cpp b/src/Cafe/OS/libs/gx2/GX2_ContextState.cpp index a4cfc93d..cf150b47 100644 --- a/src/Cafe/OS/libs/gx2/GX2_ContextState.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_ContextState.cpp @@ -291,8 +291,7 @@ void gx2Export_GX2SetDefaultState(PPCInterpreter_t* hCPU) void _GX2ContextCreateRestoreStateDL(GX2ContextState_t* gx2ContextState) { // begin display list - if (GX2::GX2WriteGather_isDisplayListActive()) - assert_dbg(); + cemu_assert_debug(!GX2::GX2GetDisplayListWriteStatus()); // must not already be writing to a display list GX2::GX2BeginDisplayList((void*)gx2ContextState->loadDL_buffer, sizeof(gx2ContextState->loadDL_buffer)); _GX2Context_WriteCmdRestoreState(gx2ContextState, 0); uint32 displayListSize = GX2::GX2EndDisplayList((void*)gx2ContextState->loadDL_buffer); diff --git a/src/Cafe/OS/libs/gx2/GX2_Shader.cpp b/src/Cafe/OS/libs/gx2/GX2_Shader.cpp index d004288b..dfbbfcff 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Shader.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_Shader.cpp @@ -426,7 +426,7 @@ namespace GX2 } if((aluRegisterOffset+sizeInU32s) > 0x400) { - cemuLog_logOnce(LogType::APIErrors, "GX2SetVertexUniformReg values are out of range (offset {} + size {} must be equal or smaller than 0x400)", aluRegisterOffset, sizeInU32s); + cemuLog_logOnce(LogType::APIErrors, "GX2SetVertexUniformReg values are out of range (offset {} + size {} must be equal or smaller than 1024)", aluRegisterOffset, sizeInU32s); } if( (sizeInU32s&3) != 0) { diff --git a/src/Cemu/Logging/CemuLogging.cpp b/src/Cemu/Logging/CemuLogging.cpp index e49ece94..5cde2a7f 100644 --- a/src/Cemu/Logging/CemuLogging.cpp +++ b/src/Cemu/Logging/CemuLogging.cpp @@ -36,6 +36,7 @@ struct _LogContext const std::map<LogType, std::string> g_logging_window_mapping { {LogType::UnsupportedAPI, "Unsupported API calls"}, + {LogType::APIErrors, "Invalid API usage"}, {LogType::CoreinitLogging, "Coreinit Logging"}, {LogType::CoreinitFile, "Coreinit File-Access"}, {LogType::CoreinitThreadSync, "Coreinit Thread-Synchronization"}, diff --git a/src/Cemu/Logging/CemuLogging.h b/src/Cemu/Logging/CemuLogging.h index 5fd652b3..a671ce51 100644 --- a/src/Cemu/Logging/CemuLogging.h +++ b/src/Cemu/Logging/CemuLogging.h @@ -7,7 +7,7 @@ enum class LogType : sint32 // note: IDs must be in range 1-64 Force = 63, // always enabled Placeholder = 62, // always disabled - APIErrors = Force, // alias for Force. Logs bad parameters or other API usage mistakes or unintended errors in OS libs + APIErrors = 61, // Logs bad parameters or other API usage mistakes or unintended errors in OS libs. Intended for homebrew developers CoreinitFile = 0, GX2 = 1, diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 33e2cdc1..03c69a7f 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -2191,27 +2191,35 @@ void MainWindow::RecreateMenu() m_menuBar->Append(nfcMenu, _("&NFC")); m_nfcMenuSeparator0 = nullptr; // debug->logging submenu - wxMenu* debugLoggingMenu = new wxMenu; + wxMenu* debugLoggingMenu = new wxMenu(); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::UnsupportedAPI), _("&Unsupported API calls"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::UnsupportedAPI)); + debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::APIErrors), _("&Invalid API usage"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::APIErrors)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitLogging), _("&Coreinit Logging (OSReport/OSConsole)"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitLogging)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitFile), _("&Coreinit File-Access API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitFile)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitThreadSync), _("&Coreinit Thread-Synchronization API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitThreadSync)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitMem), _("&Coreinit Memory API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitMem)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitMP), _("&Coreinit MP API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitMP)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitThread), _("&Coreinit Thread API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitThread)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_NFP), _("&NN NFP"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NN_NFP)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_FP), _("&NN FP"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NN_FP)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::PRUDP), _("&PRUDP (for NN FP)"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::PRUDP)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_BOSS), _("&NN BOSS"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NN_BOSS)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::GX2), _("&GX2 API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::GX2)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::SoundAPI), _("&Audio API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::SoundAPI)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::InputAPI), _("&Input API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::InputAPI)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::Socket), _("&Socket API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::Socket)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::Save), _("&Save API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::Save)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::H264), _("&H264 API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::H264)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NFC), _("&NFC API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NFC)); - debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NTAG), _("&NTAG API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NTAG)); + debugLoggingMenu->AppendSeparator(); + + wxMenu* logCosModulesMenu = new wxMenu(); + logCosModulesMenu->AppendCheckItem(0, _("&Options below are for experts. Leave off if unsure"), wxEmptyString)->Enable(false); + logCosModulesMenu->AppendSeparator(); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitFile), _("coreinit File-Access API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitFile)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitThreadSync), _("coreinit Thread-Synchronization API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitThreadSync)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitMem), _("coreinit Memory API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitMem)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitMP), _("coreinit MP API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitMP)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitThread), _("coreinit Thread API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitThread)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::Save), _("nn_save API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::Save)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_NFP), _("nn_nfp API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NN_NFP)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_FP), _("nn_fp API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NN_FP)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::PRUDP), _("nn_fp PRUDP"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::PRUDP)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NN_BOSS), _("nn_boss API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NN_BOSS)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NFC), _("nfc API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NFC)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::NTAG), _("ntag API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::NTAG)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::Socket), _("nsysnet API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::Socket)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::H264), _("h264 API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::H264)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::GX2), _("gx2 API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::GX2)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::SoundAPI), _("Audio API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::SoundAPI)); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::InputAPI), _("Input API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::InputAPI)); + + debugLoggingMenu->AppendSubMenu(logCosModulesMenu, _("&CafeOS modules logging")); debugLoggingMenu->AppendSeparator(); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::Patches), _("&Graphic pack patches"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::Patches)); debugLoggingMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::TextureCache), _("&Texture cache warnings"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::TextureCache)); From 6772b1993ff678050d9a064dbd8c625eede272a1 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Wed, 5 Jun 2024 16:34:42 +0200 Subject: [PATCH 132/299] vcpkg: Update dependencies (#1229) --- dependencies/vcpkg | 2 +- .../sdl2/alsa-dep-fix.patch | 13 -- .../vcpkg_overlay_ports/sdl2/deps.patch | 13 -- .../vcpkg_overlay_ports/sdl2/portfile.cmake | 137 ------------------ dependencies/vcpkg_overlay_ports/sdl2/usage | 8 - .../vcpkg_overlay_ports/sdl2/vcpkg.json | 68 --------- .../vcpkg_overlay_ports/tiff/FindCMath.patch | 13 -- .../vcpkg_overlay_ports/tiff/portfile.cmake | 86 ----------- dependencies/vcpkg_overlay_ports/tiff/usage | 9 -- .../tiff/vcpkg-cmake-wrapper.cmake.in | 104 ------------- .../vcpkg_overlay_ports/tiff/vcpkg.json | 67 --------- .../dbus/cmake.dep.patch | 15 -- .../dbus/getpeereid.patch | 26 ---- .../dbus/libsystemd.patch | 15 -- .../dbus/pkgconfig.patch | 21 --- .../dbus/portfile.cmake | 88 ----------- .../vcpkg_overlay_ports_linux/dbus/vcpkg.json | 30 ---- .../sdl2/alsa-dep-fix.patch | 13 -- .../vcpkg_overlay_ports_linux/sdl2/deps.patch | 13 -- .../sdl2/portfile.cmake | 137 ------------------ .../vcpkg_overlay_ports_linux/sdl2/usage | 8 - .../vcpkg_overlay_ports_linux/sdl2/vcpkg.json | 68 --------- .../tiff/FindCMath.patch | 13 -- .../tiff/portfile.cmake | 86 ----------- .../vcpkg_overlay_ports_linux/tiff/usage | 9 -- .../tiff/vcpkg-cmake-wrapper.cmake.in | 104 ------------- .../vcpkg_overlay_ports_linux/tiff/vcpkg.json | 67 --------- .../sdl2/alsa-dep-fix.patch | 13 -- .../vcpkg_overlay_ports_mac/sdl2/deps.patch | 13 -- .../sdl2/portfile.cmake | 137 ------------------ .../vcpkg_overlay_ports_mac/sdl2/usage | 8 - .../vcpkg_overlay_ports_mac/sdl2/vcpkg.json | 68 --------- .../tiff/FindCMath.patch | 13 -- .../tiff/portfile.cmake | 86 ----------- .../vcpkg_overlay_ports_mac/tiff/usage | 9 -- .../tiff/vcpkg-cmake-wrapper.cmake.in | 104 ------------- .../vcpkg_overlay_ports_mac/tiff/vcpkg.json | 67 --------- vcpkg.json | 18 ++- 38 files changed, 18 insertions(+), 1751 deletions(-) delete mode 100644 dependencies/vcpkg_overlay_ports/sdl2/alsa-dep-fix.patch delete mode 100644 dependencies/vcpkg_overlay_ports/sdl2/deps.patch delete mode 100644 dependencies/vcpkg_overlay_ports/sdl2/portfile.cmake delete mode 100644 dependencies/vcpkg_overlay_ports/sdl2/usage delete mode 100644 dependencies/vcpkg_overlay_ports/sdl2/vcpkg.json delete mode 100644 dependencies/vcpkg_overlay_ports/tiff/FindCMath.patch delete mode 100644 dependencies/vcpkg_overlay_ports/tiff/portfile.cmake delete mode 100644 dependencies/vcpkg_overlay_ports/tiff/usage delete mode 100644 dependencies/vcpkg_overlay_ports/tiff/vcpkg-cmake-wrapper.cmake.in delete mode 100644 dependencies/vcpkg_overlay_ports/tiff/vcpkg.json delete mode 100644 dependencies/vcpkg_overlay_ports_linux/dbus/cmake.dep.patch delete mode 100644 dependencies/vcpkg_overlay_ports_linux/dbus/getpeereid.patch delete mode 100644 dependencies/vcpkg_overlay_ports_linux/dbus/libsystemd.patch delete mode 100644 dependencies/vcpkg_overlay_ports_linux/dbus/pkgconfig.patch delete mode 100644 dependencies/vcpkg_overlay_ports_linux/dbus/portfile.cmake delete mode 100644 dependencies/vcpkg_overlay_ports_linux/dbus/vcpkg.json delete mode 100644 dependencies/vcpkg_overlay_ports_linux/sdl2/alsa-dep-fix.patch delete mode 100644 dependencies/vcpkg_overlay_ports_linux/sdl2/deps.patch delete mode 100644 dependencies/vcpkg_overlay_ports_linux/sdl2/portfile.cmake delete mode 100644 dependencies/vcpkg_overlay_ports_linux/sdl2/usage delete mode 100644 dependencies/vcpkg_overlay_ports_linux/sdl2/vcpkg.json delete mode 100644 dependencies/vcpkg_overlay_ports_linux/tiff/FindCMath.patch delete mode 100644 dependencies/vcpkg_overlay_ports_linux/tiff/portfile.cmake delete mode 100644 dependencies/vcpkg_overlay_ports_linux/tiff/usage delete mode 100644 dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg-cmake-wrapper.cmake.in delete mode 100644 dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg.json delete mode 100644 dependencies/vcpkg_overlay_ports_mac/sdl2/alsa-dep-fix.patch delete mode 100644 dependencies/vcpkg_overlay_ports_mac/sdl2/deps.patch delete mode 100644 dependencies/vcpkg_overlay_ports_mac/sdl2/portfile.cmake delete mode 100644 dependencies/vcpkg_overlay_ports_mac/sdl2/usage delete mode 100644 dependencies/vcpkg_overlay_ports_mac/sdl2/vcpkg.json delete mode 100644 dependencies/vcpkg_overlay_ports_mac/tiff/FindCMath.patch delete mode 100644 dependencies/vcpkg_overlay_ports_mac/tiff/portfile.cmake delete mode 100644 dependencies/vcpkg_overlay_ports_mac/tiff/usage delete mode 100644 dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg-cmake-wrapper.cmake.in delete mode 100644 dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg.json diff --git a/dependencies/vcpkg b/dependencies/vcpkg index cbf4a664..a4275b7e 160000 --- a/dependencies/vcpkg +++ b/dependencies/vcpkg @@ -1 +1 @@ -Subproject commit cbf4a6641528cee6f172328984576f51698de726 +Subproject commit a4275b7eee79fb24ec2e135481ef5fce8b41c339 diff --git a/dependencies/vcpkg_overlay_ports/sdl2/alsa-dep-fix.patch b/dependencies/vcpkg_overlay_ports/sdl2/alsa-dep-fix.patch deleted file mode 100644 index 5b2c77b9..00000000 --- a/dependencies/vcpkg_overlay_ports/sdl2/alsa-dep-fix.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/SDL2Config.cmake.in b/SDL2Config.cmake.in -index cc8bcf26d..ead829767 100644 ---- a/SDL2Config.cmake.in -+++ b/SDL2Config.cmake.in -@@ -35,7 +35,7 @@ include("${CMAKE_CURRENT_LIST_DIR}/sdlfind.cmake") - - set(SDL_ALSA @SDL_ALSA@) - set(SDL_ALSA_SHARED @SDL_ALSA_SHARED@) --if(SDL_ALSA AND NOT SDL_ALSA_SHARED AND TARGET SDL2::SDL2-static) -+if(SDL_ALSA) - sdlFindALSA() - endif() - unset(SDL_ALSA) diff --git a/dependencies/vcpkg_overlay_ports/sdl2/deps.patch b/dependencies/vcpkg_overlay_ports/sdl2/deps.patch deleted file mode 100644 index a8637d8c..00000000 --- a/dependencies/vcpkg_overlay_ports/sdl2/deps.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/cmake/sdlchecks.cmake b/cmake/sdlchecks.cmake -index 65a98efbe..2f99f28f1 100644 ---- a/cmake/sdlchecks.cmake -+++ b/cmake/sdlchecks.cmake -@@ -352,7 +352,7 @@ endmacro() - # - HAVE_SDL_LOADSO opt - macro(CheckLibSampleRate) - if(SDL_LIBSAMPLERATE) -- find_package(SampleRate QUIET) -+ find_package(SampleRate CONFIG REQUIRED) - if(SampleRate_FOUND AND TARGET SampleRate::samplerate) - set(HAVE_LIBSAMPLERATE TRUE) - set(HAVE_LIBSAMPLERATE_H TRUE) diff --git a/dependencies/vcpkg_overlay_ports/sdl2/portfile.cmake b/dependencies/vcpkg_overlay_ports/sdl2/portfile.cmake deleted file mode 100644 index 22685e6a..00000000 --- a/dependencies/vcpkg_overlay_ports/sdl2/portfile.cmake +++ /dev/null @@ -1,137 +0,0 @@ -vcpkg_from_github( - OUT_SOURCE_PATH SOURCE_PATH - REPO libsdl-org/SDL - REF "release-${VERSION}" - SHA512 c7635a83a52f3970a372b804a8631f0a7e6b8d89aed1117bcc54a2040ad0928122175004cf2b42cf84a4fd0f86236f779229eaa63dfa6ca9c89517f999c5ff1c - HEAD_REF main - PATCHES - deps.patch - alsa-dep-fix.patch -) - -string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "static" SDL_STATIC) -string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "dynamic" SDL_SHARED) -string(COMPARE EQUAL "${VCPKG_CRT_LINKAGE}" "static" FORCE_STATIC_VCRT) - -vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS - FEATURES - alsa SDL_ALSA - alsa CMAKE_REQUIRE_FIND_PACKAGE_ALSA - ibus SDL_IBUS - samplerate SDL_LIBSAMPLERATE - vulkan SDL_VULKAN - wayland SDL_WAYLAND - x11 SDL_X11 - INVERTED_FEATURES - alsa CMAKE_DISABLE_FIND_PACKAGE_ALSA -) - -if ("x11" IN_LIST FEATURES) - message(WARNING "You will need to install Xorg dependencies to use feature x11:\nsudo apt install libx11-dev libxft-dev libxext-dev\n") -endif() -if ("wayland" IN_LIST FEATURES) - message(WARNING "You will need to install Wayland dependencies to use feature wayland:\nsudo apt install libwayland-dev libxkbcommon-dev libegl1-mesa-dev\n") -endif() -if ("ibus" IN_LIST FEATURES) - message(WARNING "You will need to install ibus dependencies to use feature ibus:\nsudo apt install libibus-1.0-dev\n") -endif() - -if(VCPKG_TARGET_IS_UWP) - set(configure_opts WINDOWS_USE_MSBUILD) -endif() - -vcpkg_cmake_configure( - SOURCE_PATH "${SOURCE_PATH}" - ${configure_opts} - OPTIONS ${FEATURE_OPTIONS} - -DSDL_STATIC=${SDL_STATIC} - -DSDL_SHARED=${SDL_SHARED} - -DSDL_FORCE_STATIC_VCRT=${FORCE_STATIC_VCRT} - -DSDL_LIBC=ON - -DSDL_TEST=OFF - -DSDL_INSTALL_CMAKEDIR="cmake" - -DCMAKE_DISABLE_FIND_PACKAGE_Git=ON - -DPKG_CONFIG_USE_CMAKE_PREFIX_PATH=ON - -DSDL_LIBSAMPLERATE_SHARED=OFF - MAYBE_UNUSED_VARIABLES - SDL_FORCE_STATIC_VCRT - PKG_CONFIG_USE_CMAKE_PREFIX_PATH -) - -vcpkg_cmake_install() -vcpkg_cmake_config_fixup(CONFIG_PATH cmake) - -file(REMOVE_RECURSE - "${CURRENT_PACKAGES_DIR}/debug/include" - "${CURRENT_PACKAGES_DIR}/debug/share" - "${CURRENT_PACKAGES_DIR}/bin/sdl2-config" - "${CURRENT_PACKAGES_DIR}/debug/bin/sdl2-config" - "${CURRENT_PACKAGES_DIR}/SDL2.framework" - "${CURRENT_PACKAGES_DIR}/debug/SDL2.framework" - "${CURRENT_PACKAGES_DIR}/share/licenses" - "${CURRENT_PACKAGES_DIR}/share/aclocal" -) - -file(GLOB BINS "${CURRENT_PACKAGES_DIR}/debug/bin/*" "${CURRENT_PACKAGES_DIR}/bin/*") -if(NOT BINS) - file(REMOVE_RECURSE - "${CURRENT_PACKAGES_DIR}/bin" - "${CURRENT_PACKAGES_DIR}/debug/bin" - ) -endif() - -if(VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_UWP AND NOT VCPKG_TARGET_IS_MINGW) - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") - file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/lib/manual-link") - file(RENAME "${CURRENT_PACKAGES_DIR}/lib/SDL2main.lib" "${CURRENT_PACKAGES_DIR}/lib/manual-link/SDL2main.lib") - endif() - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link") - file(RENAME "${CURRENT_PACKAGES_DIR}/debug/lib/SDL2maind.lib" "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link/SDL2maind.lib") - endif() - - file(GLOB SHARE_FILES "${CURRENT_PACKAGES_DIR}/share/sdl2/*.cmake") - foreach(SHARE_FILE ${SHARE_FILES}) - vcpkg_replace_string("${SHARE_FILE}" "lib/SDL2main" "lib/manual-link/SDL2main") - endforeach() -endif() - -vcpkg_copy_pdbs() - -set(DYLIB_COMPATIBILITY_VERSION_REGEX "set\\(DYLIB_COMPATIBILITY_VERSION (.+)\\)") -set(DYLIB_CURRENT_VERSION_REGEX "set\\(DYLIB_CURRENT_VERSION (.+)\\)") -file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_COMPATIBILITY_VERSION REGEX ${DYLIB_COMPATIBILITY_VERSION_REGEX}) -file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_CURRENT_VERSION REGEX ${DYLIB_CURRENT_VERSION_REGEX}) -string(REGEX REPLACE ${DYLIB_COMPATIBILITY_VERSION_REGEX} "\\1" DYLIB_COMPATIBILITY_VERSION "${DYLIB_COMPATIBILITY_VERSION}") -string(REGEX REPLACE ${DYLIB_CURRENT_VERSION_REGEX} "\\1" DYLIB_CURRENT_VERSION "${DYLIB_CURRENT_VERSION}") - -if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2main" "-lSDL2maind") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2 " "-lSDL2d ") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-static " "-lSDL2-staticd ") -endif() - -if(VCPKG_LIBRARY_LINKAGE STREQUAL "dynamic" AND VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_MINGW) - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-lSDL2-static " " ") - endif() - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-staticd " " ") - endif() -endif() - -if(VCPKG_TARGET_IS_UWP) - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "$<$<CONFIG:Debug>:d>.lib" "") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:") - endif() - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "$<$<CONFIG:Debug>:d>.lib" "d") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:") - endif() -endif() - -vcpkg_fixup_pkgconfig() - -file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") -vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.txt") diff --git a/dependencies/vcpkg_overlay_ports/sdl2/usage b/dependencies/vcpkg_overlay_ports/sdl2/usage deleted file mode 100644 index 1cddcd46..00000000 --- a/dependencies/vcpkg_overlay_ports/sdl2/usage +++ /dev/null @@ -1,8 +0,0 @@ -sdl2 provides CMake targets: - - find_package(SDL2 CONFIG REQUIRED) - target_link_libraries(main - PRIVATE - $<TARGET_NAME_IF_EXISTS:SDL2::SDL2main> - $<IF:$<TARGET_EXISTS:SDL2::SDL2>,SDL2::SDL2,SDL2::SDL2-static> - ) diff --git a/dependencies/vcpkg_overlay_ports/sdl2/vcpkg.json b/dependencies/vcpkg_overlay_ports/sdl2/vcpkg.json deleted file mode 100644 index 1f460375..00000000 --- a/dependencies/vcpkg_overlay_ports/sdl2/vcpkg.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "name": "sdl2", - "version": "2.30.0", - "description": "Simple DirectMedia Layer is a cross-platform development library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL and Direct3D.", - "homepage": "https://www.libsdl.org/download-2.0.php", - "license": "Zlib", - "dependencies": [ - { - "name": "dbus", - "default-features": false, - "platform": "linux" - }, - { - "name": "vcpkg-cmake", - "host": true - }, - { - "name": "vcpkg-cmake-config", - "host": true - } - ], - "default-features": [ - { - "name": "ibus", - "platform": "linux" - }, - { - "name": "wayland", - "platform": "linux" - }, - { - "name": "x11", - "platform": "linux" - } - ], - "features": { - "alsa": { - "description": "Support for alsa audio", - "dependencies": [ - { - "name": "alsa", - "platform": "linux" - } - ] - }, - "ibus": { - "description": "Build with ibus IME support", - "supports": "linux" - }, - "samplerate": { - "description": "Use libsamplerate for audio rate conversion", - "dependencies": [ - "libsamplerate" - ] - }, - "vulkan": { - "description": "Vulkan functionality for SDL" - }, - "wayland": { - "description": "Build with Wayland support", - "supports": "linux" - }, - "x11": { - "description": "Build with X11 support", - "supports": "!windows" - } - } -} diff --git a/dependencies/vcpkg_overlay_ports/tiff/FindCMath.patch b/dependencies/vcpkg_overlay_ports/tiff/FindCMath.patch deleted file mode 100644 index 70654cf8..00000000 --- a/dependencies/vcpkg_overlay_ports/tiff/FindCMath.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/cmake/FindCMath.cmake b/cmake/FindCMath.cmake -index ad92218..dd42aba 100644 ---- a/cmake/FindCMath.cmake -+++ b/cmake/FindCMath.cmake -@@ -31,7 +31,7 @@ include(CheckSymbolExists) - include(CheckLibraryExists) - - check_symbol_exists(pow "math.h" CMath_HAVE_LIBC_POW) --find_library(CMath_LIBRARY NAMES m) -+find_library(CMath_LIBRARY NAMES m PATHS ${CMAKE_C_IMPLICIT_LINK_DIRECTORIES}) - - if(NOT CMath_HAVE_LIBC_POW) - set(CMAKE_REQUIRED_LIBRARIES_SAVE ${CMAKE_REQUIRED_LIBRARIES}) diff --git a/dependencies/vcpkg_overlay_ports/tiff/portfile.cmake b/dependencies/vcpkg_overlay_ports/tiff/portfile.cmake deleted file mode 100644 index 426d8af7..00000000 --- a/dependencies/vcpkg_overlay_ports/tiff/portfile.cmake +++ /dev/null @@ -1,86 +0,0 @@ -vcpkg_from_gitlab( - GITLAB_URL https://gitlab.com - OUT_SOURCE_PATH SOURCE_PATH - REPO libtiff/libtiff - REF "v${VERSION}" - SHA512 ef2f1d424219d9e245069b7d23e78f5e817cf6ee516d46694915ab6c8909522166f84997513d20a702f4e52c3f18467813935b328fafa34bea5156dee00f66fa - HEAD_REF master - PATCHES - FindCMath.patch -) - -vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS - FEATURES - cxx cxx - jpeg jpeg - jpeg CMAKE_REQUIRE_FIND_PACKAGE_JPEG - libdeflate libdeflate - libdeflate CMAKE_REQUIRE_FIND_PACKAGE_Deflate - lzma lzma - lzma CMAKE_REQUIRE_FIND_PACKAGE_liblzma - tools tiff-tools - webp webp - webp CMAKE_REQUIRE_FIND_PACKAGE_WebP - zip zlib - zip CMAKE_REQUIRE_FIND_PACKAGE_ZLIB - zstd zstd - zstd CMAKE_REQUIRE_FIND_PACKAGE_ZSTD -) - -vcpkg_cmake_configure( - SOURCE_PATH "${SOURCE_PATH}" - OPTIONS - ${FEATURE_OPTIONS} - -DCMAKE_FIND_PACKAGE_PREFER_CONFIG=ON - -Dtiff-docs=OFF - -Dtiff-contrib=OFF - -Dtiff-tests=OFF - -Djbig=OFF # This is disabled by default due to GPL/Proprietary licensing. - -Djpeg12=OFF - -Dlerc=OFF - -DCMAKE_DISABLE_FIND_PACKAGE_OpenGL=ON - -DCMAKE_DISABLE_FIND_PACKAGE_GLUT=ON - -DZSTD_HAVE_DECOMPRESS_STREAM=ON - -DHAVE_JPEGTURBO_DUAL_MODE_8_12=OFF - OPTIONS_DEBUG - -DCMAKE_DEBUG_POSTFIX=d # tiff sets "d" for MSVC only. - MAYBE_UNUSED_VARIABLES - CMAKE_DISABLE_FIND_PACKAGE_GLUT - CMAKE_DISABLE_FIND_PACKAGE_OpenGL - ZSTD_HAVE_DECOMPRESS_STREAM -) - -vcpkg_cmake_install() - -# CMake config wasn't packaged in the past and is not yet usable now, -# cf. https://gitlab.com/libtiff/libtiff/-/merge_requests/496 -# vcpkg_cmake_config_fixup(CONFIG_PATH "lib/cmake/tiff") -file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/lib/cmake" "${CURRENT_PACKAGES_DIR}/debug/lib/cmake") - -set(_file "${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/libtiff-4.pc") -if(EXISTS "${_file}") - vcpkg_replace_string("${_file}" "-ltiff" "-ltiffd") -endif() -vcpkg_fixup_pkgconfig() - -file(REMOVE_RECURSE - "${CURRENT_PACKAGES_DIR}/debug/include" - "${CURRENT_PACKAGES_DIR}/debug/share" -) - -configure_file("${CMAKE_CURRENT_LIST_DIR}/vcpkg-cmake-wrapper.cmake.in" "${CURRENT_PACKAGES_DIR}/share/${PORT}/vcpkg-cmake-wrapper.cmake" @ONLY) - -if ("tools" IN_LIST FEATURES) - vcpkg_copy_tools(TOOL_NAMES - tiffcp - tiffdump - tiffinfo - tiffset - tiffsplit - AUTO_CLEAN - ) -endif() - -vcpkg_copy_pdbs() -file(COPY "${CURRENT_PORT_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") -vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.md") diff --git a/dependencies/vcpkg_overlay_ports/tiff/usage b/dependencies/vcpkg_overlay_ports/tiff/usage deleted file mode 100644 index d47265b1..00000000 --- a/dependencies/vcpkg_overlay_ports/tiff/usage +++ /dev/null @@ -1,9 +0,0 @@ -tiff is compatible with built-in CMake targets: - - find_package(TIFF REQUIRED) - target_link_libraries(main PRIVATE TIFF::TIFF) - -tiff provides pkg-config modules: - - # Tag Image File Format (TIFF) library. - libtiff-4 diff --git a/dependencies/vcpkg_overlay_ports/tiff/vcpkg-cmake-wrapper.cmake.in b/dependencies/vcpkg_overlay_ports/tiff/vcpkg-cmake-wrapper.cmake.in deleted file mode 100644 index 1d04ec7a..00000000 --- a/dependencies/vcpkg_overlay_ports/tiff/vcpkg-cmake-wrapper.cmake.in +++ /dev/null @@ -1,104 +0,0 @@ -cmake_policy(PUSH) -cmake_policy(SET CMP0012 NEW) -cmake_policy(SET CMP0057 NEW) -set(z_vcpkg_tiff_find_options "") -if("REQUIRED" IN_LIST ARGS) - list(APPEND z_vcpkg_tiff_find_options "REQUIRED") -endif() -if("QUIET" IN_LIST ARGS) - list(APPEND z_vcpkg_tiff_find_options "QUIET") -endif() - -_find_package(${ARGS}) - -if(TIFF_FOUND AND "@VCPKG_LIBRARY_LINKAGE@" STREQUAL "static") - include(SelectLibraryConfigurations) - set(z_vcpkg_tiff_link_libraries "") - set(z_vcpkg_tiff_libraries "") - if("@webp@") - find_package(WebP CONFIG ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:WebP::WebP>") - list(APPEND z_vcpkg_tiff_libraries ${WebP_LIBRARIES}) - endif() - if("@lzma@") - find_package(LibLZMA ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:LibLZMA::LibLZMA>") - list(APPEND z_vcpkg_tiff_libraries ${LIBLZMA_LIBRARIES}) - endif() - if("@jpeg@") - find_package(JPEG ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:JPEG::JPEG>") - list(APPEND z_vcpkg_tiff_libraries ${JPEG_LIBRARIES}) - endif() - if("@zstd@") - find_package(zstd CONFIG ${z_vcpkg_tiff_find_options}) - set(z_vcpkg_tiff_zstd_target_property "IMPORTED_LOCATION_") - if(TARGET zstd::libzstd_shared) - set(z_vcpkg_tiff_zstd "\$<LINK_ONLY:zstd::libzstd_shared>") - set(z_vcpkg_tiff_zstd_target zstd::libzstd_shared) - if(WIN32) - set(z_vcpkg_tiff_zstd_target_property "IMPORTED_IMPLIB_") - endif() - else() - set(z_vcpkg_tiff_zstd "\$<LINK_ONLY:zstd::libzstd_static>") - set(z_vcpkg_tiff_zstd_target zstd::libzstd_static) - endif() - get_target_property(z_vcpkg_tiff_zstd_configs "${z_vcpkg_tiff_zstd_target}" IMPORTED_CONFIGURATIONS) - foreach(z_vcpkg_config IN LISTS z_vcpkg_tiff_zstd_configs) - get_target_property(ZSTD_LIBRARY_${z_vcpkg_config} "${z_vcpkg_tiff_zstd_target}" "${z_vcpkg_tiff_zstd_target_property}${z_vcpkg_config}") - endforeach() - select_library_configurations(ZSTD) - if(NOT TARGET ZSTD::ZSTD) - add_library(ZSTD::ZSTD INTERFACE IMPORTED) - set_property(TARGET ZSTD::ZSTD APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${z_vcpkg_tiff_zstd}) - endif() - list(APPEND z_vcpkg_tiff_link_libraries ${z_vcpkg_tiff_zstd}) - list(APPEND z_vcpkg_tiff_libraries ${ZSTD_LIBRARIES}) - unset(z_vcpkg_tiff_zstd) - unset(z_vcpkg_tiff_zstd_configs) - unset(z_vcpkg_config) - unset(z_vcpkg_tiff_zstd_target) - endif() - if("@libdeflate@") - find_package(libdeflate ${z_vcpkg_tiff_find_options}) - set(z_vcpkg_property "IMPORTED_LOCATION_") - if(TARGET libdeflate::libdeflate_shared) - set(z_vcpkg_libdeflate_target libdeflate::libdeflate_shared) - if(WIN32) - set(z_vcpkg_property "IMPORTED_IMPLIB_") - endif() - else() - set(z_vcpkg_libdeflate_target libdeflate::libdeflate_static) - endif() - get_target_property(z_vcpkg_libdeflate_configs "${z_vcpkg_libdeflate_target}" IMPORTED_CONFIGURATIONS) - foreach(z_vcpkg_config IN LISTS z_vcpkg_libdeflate_configs) - get_target_property(Z_VCPKG_DEFLATE_LIBRARY_${z_vcpkg_config} "${z_vcpkg_libdeflate_target}" "${z_vcpkg_property}${z_vcpkg_config}") - endforeach() - select_library_configurations(Z_VCPKG_DEFLATE) - list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:${z_vcpkg_libdeflate_target}>") - list(APPEND z_vcpkg_tiff_libraries ${Z_VCPKG_DEFLATE_LIBRARIES}) - unset(z_vcpkg_config) - unset(z_vcpkg_libdeflate_configs) - unset(z_vcpkg_libdeflate_target) - unset(z_vcpkg_property) - unset(Z_VCPKG_DEFLATE_FOUND) - endif() - if("@zlib@") - find_package(ZLIB ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:ZLIB::ZLIB>") - list(APPEND z_vcpkg_tiff_libraries ${ZLIB_LIBRARIES}) - endif() - if(UNIX) - list(APPEND z_vcpkg_tiff_link_libraries m) - list(APPEND z_vcpkg_tiff_libraries m) - endif() - - if(TARGET TIFF::TIFF) - set_property(TARGET TIFF::TIFF APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${z_vcpkg_tiff_link_libraries}) - endif() - list(APPEND TIFF_LIBRARIES ${z_vcpkg_tiff_libraries}) - unset(z_vcpkg_tiff_link_libraries) - unset(z_vcpkg_tiff_libraries) -endif() -unset(z_vcpkg_tiff_find_options) -cmake_policy(POP) diff --git a/dependencies/vcpkg_overlay_ports/tiff/vcpkg.json b/dependencies/vcpkg_overlay_ports/tiff/vcpkg.json deleted file mode 100644 index 9b36e1a8..00000000 --- a/dependencies/vcpkg_overlay_ports/tiff/vcpkg.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "name": "tiff", - "version": "4.6.0", - "port-version": 2, - "description": "A library that supports the manipulation of TIFF image files", - "homepage": "https://libtiff.gitlab.io/libtiff/", - "license": "libtiff", - "dependencies": [ - { - "name": "vcpkg-cmake", - "host": true - }, - { - "name": "vcpkg-cmake-config", - "host": true - } - ], - "default-features": [ - "jpeg", - "zip" - ], - "features": { - "cxx": { - "description": "Build C++ libtiffxx library" - }, - "jpeg": { - "description": "Support JPEG compression in TIFF image files", - "dependencies": [ - "libjpeg-turbo" - ] - }, - "libdeflate": { - "description": "Use libdeflate for faster ZIP support", - "dependencies": [ - "libdeflate", - { - "name": "tiff", - "default-features": false, - "features": [ - "zip" - ] - } - ] - }, - "tools": { - "description": "Build tools" - }, - "webp": { - "description": "Support WEBP compression in TIFF image files", - "dependencies": [ - "libwebp" - ] - }, - "zip": { - "description": "Support ZIP/deflate compression in TIFF image files", - "dependencies": [ - "zlib" - ] - }, - "zstd": { - "description": "Support ZSTD compression in TIFF image files", - "dependencies": [ - "zstd" - ] - } - } -} diff --git a/dependencies/vcpkg_overlay_ports_linux/dbus/cmake.dep.patch b/dependencies/vcpkg_overlay_ports_linux/dbus/cmake.dep.patch deleted file mode 100644 index ac827f0c..00000000 --- a/dependencies/vcpkg_overlay_ports_linux/dbus/cmake.dep.patch +++ /dev/null @@ -1,15 +0,0 @@ -diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt -index 8cde1ffe0..d4d09f223 100644 ---- a/tools/CMakeLists.txt -+++ b/tools/CMakeLists.txt -@@ -91,7 +91,9 @@ endif() - add_executable(dbus-launch ${dbus_launch_SOURCES}) - target_link_libraries(dbus-launch ${DBUS_LIBRARIES}) - if(DBUS_BUILD_X11) -- target_link_libraries(dbus-launch ${X11_LIBRARIES} ) -+ find_package(Threads REQUIRED) -+ target_link_libraries(dbus-launch ${X11_LIBRARIES} ${X11_xcb_LIB} ${X11_Xau_LIB} ${X11_Xdmcp_LIB} Threads::Threads) -+ target_include_directories(dbus-launch PRIVATE ${X11_INCLUDE_DIR}) - endif() - install(TARGETS dbus-launch ${INSTALL_TARGETS_DEFAULT_ARGS}) - diff --git a/dependencies/vcpkg_overlay_ports_linux/dbus/getpeereid.patch b/dependencies/vcpkg_overlay_ports_linux/dbus/getpeereid.patch deleted file mode 100644 index 5cd2309e..00000000 --- a/dependencies/vcpkg_overlay_ports_linux/dbus/getpeereid.patch +++ /dev/null @@ -1,26 +0,0 @@ -diff --git a/cmake/ConfigureChecks.cmake b/cmake/ConfigureChecks.cmake -index b7f3702..e2336ba 100644 ---- a/cmake/ConfigureChecks.cmake -+++ b/cmake/ConfigureChecks.cmake -@@ -51,6 +51,7 @@ check_symbol_exists(closefrom "unistd.h" HAVE_CLOSEFROM) # - check_symbol_exists(environ "unistd.h" HAVE_DECL_ENVIRON) - check_symbol_exists(fstatfs "sys/vfs.h" HAVE_FSTATFS) - check_symbol_exists(getgrouplist "grp.h" HAVE_GETGROUPLIST) # dbus-sysdeps.c -+check_symbol_exists(getpeereid "sys/types.h;unistd.h" HAVE_GETPEEREID) # dbus-sysdeps.c, - check_symbol_exists(getpeerucred "ucred.h" HAVE_GETPEERUCRED) # dbus-sysdeps.c, dbus-sysdeps-win.c - check_symbol_exists(getpwnam_r "errno.h;pwd.h" HAVE_GETPWNAM_R) # dbus-sysdeps-util-unix.c - check_symbol_exists(getrandom "sys/random.h" HAVE_GETRANDOM) -diff --git a/cmake/config.h.cmake b/cmake/config.h.cmake -index 77fc19c..2f25643 100644 ---- a/cmake/config.h.cmake -+++ b/cmake/config.h.cmake -@@ -140,6 +140,9 @@ - /* Define to 1 if you have getgrouplist */ - #cmakedefine HAVE_GETGROUPLIST 1 - -+/* Define to 1 if you have getpeereid */ -+#cmakedefine HAVE_GETPEEREID 1 -+ - /* Define to 1 if you have getpeerucred */ - #cmakedefine HAVE_GETPEERUCRED 1 - diff --git a/dependencies/vcpkg_overlay_ports_linux/dbus/libsystemd.patch b/dependencies/vcpkg_overlay_ports_linux/dbus/libsystemd.patch deleted file mode 100644 index 74193dc4..00000000 --- a/dependencies/vcpkg_overlay_ports_linux/dbus/libsystemd.patch +++ /dev/null @@ -1,15 +0,0 @@ -diff --git a/CMakeLists.txt b/CMakeLists.txt -index d3ec71b..932066a 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -141,6 +141,10 @@ if(DBUS_LINUX) - if(ENABLE_SYSTEMD AND SYSTEMD_FOUND) - set(DBUS_BUS_ENABLE_SYSTEMD ON) - set(HAVE_SYSTEMD ${SYSTEMD_FOUND}) -+ pkg_check_modules(SYSTEMD libsystemd IMPORTED_TARGET) -+ set(SYSTEMD_LIBRARIES PkgConfig::SYSTEMD CACHE INTERNAL "") -+ else() -+ set(SYSTEMD_LIBRARIES "" CACHE INTERNAL "") - endif() - option(ENABLE_USER_SESSION "enable user-session semantics for session bus under systemd" OFF) - set(DBUS_ENABLE_USER_SESSION ${ENABLE_USER_SESSION}) diff --git a/dependencies/vcpkg_overlay_ports_linux/dbus/pkgconfig.patch b/dependencies/vcpkg_overlay_ports_linux/dbus/pkgconfig.patch deleted file mode 100644 index 63581487..00000000 --- a/dependencies/vcpkg_overlay_ports_linux/dbus/pkgconfig.patch +++ /dev/null @@ -1,21 +0,0 @@ -diff --git a/CMakeLists.txt b/CMakeLists.txt -index caef738..b878f42 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -724,11 +724,11 @@ add_custom_target(help-options - # - if(DBUS_ENABLE_PKGCONFIG) - set(PLATFORM_LIBS pthread ${LIBRT}) -- if(PKG_CONFIG_FOUND) -- # convert lists of link libraries into -lstdc++ -lm etc.. -- foreach(LIB ${CMAKE_C_IMPLICIT_LINK_LIBRARIES} ${PLATFORM_LIBS}) -- set(LIBDBUS_LIBS "${LIBDBUS_LIBS} -l${LIB}") -- endforeach() -+ if(1) -+ set(LIBDBUS_LIBS "${CMAKE_THREAD_LIBS_INIT}") -+ if(LIBRT) -+ string(APPEND LIBDBUS_LIBS " -lrt") -+ endif() - set(original_prefix "${CMAKE_INSTALL_PREFIX}") - if(DBUS_RELOCATABLE) - set(pkgconfig_prefix "\${pcfiledir}/../..") diff --git a/dependencies/vcpkg_overlay_ports_linux/dbus/portfile.cmake b/dependencies/vcpkg_overlay_ports_linux/dbus/portfile.cmake deleted file mode 100644 index 56c7e182..00000000 --- a/dependencies/vcpkg_overlay_ports_linux/dbus/portfile.cmake +++ /dev/null @@ -1,88 +0,0 @@ -vcpkg_check_linkage(ONLY_DYNAMIC_LIBRARY) - -vcpkg_from_gitlab( - GITLAB_URL https://gitlab.freedesktop.org/ - OUT_SOURCE_PATH SOURCE_PATH - REPO dbus/dbus - REF "dbus-${VERSION}" - SHA512 8e476b408514e6540c36beb84e8025827c22cda8958b6eb74d22b99c64765eb3cd5a6502aea546e3e5f0534039857b37edee89c659acef40e7cab0939947d4af - HEAD_REF master - PATCHES - cmake.dep.patch - pkgconfig.patch - getpeereid.patch # missing check from configure.ac - libsystemd.patch -) - -vcpkg_check_features(OUT_FEATURE_OPTIONS options - FEATURES - systemd ENABLE_SYSTEMD - x11 DBUS_BUILD_X11 - x11 CMAKE_REQUIRE_FIND_PACKAGE_X11 -) - -unset(ENV{DBUSDIR}) - -vcpkg_cmake_configure( - SOURCE_PATH "${SOURCE_PATH}" - OPTIONS - -DDBUS_BUILD_TESTS=OFF - -DDBUS_ENABLE_DOXYGEN_DOCS=OFF - -DDBUS_ENABLE_XML_DOCS=OFF - -DDBUS_INSTALL_SYSTEM_LIBS=OFF - #-DDBUS_SERVICE=ON - -DDBUS_WITH_GLIB=OFF - -DTHREADS_PREFER_PTHREAD_FLAG=ON - -DXSLTPROC_EXECUTABLE=FALSE - "-DCMAKE_INSTALL_SYSCONFDIR=${CURRENT_PACKAGES_DIR}/etc/${PORT}" - "-DWITH_SYSTEMD_SYSTEMUNITDIR=lib/systemd/system" - "-DWITH_SYSTEMD_USERUNITDIR=lib/systemd/user" - ${options} - OPTIONS_RELEASE - -DDBUS_DISABLE_ASSERT=OFF - -DDBUS_ENABLE_STATS=OFF - -DDBUS_ENABLE_VERBOSE_MODE=OFF - MAYBE_UNUSED_VARIABLES - DBUS_BUILD_X11 - DBUS_WITH_GLIB - ENABLE_SYSTEMD - THREADS_PREFER_PTHREAD_FLAG - WITH_SYSTEMD_SYSTEMUNITDIR - WITH_SYSTEMD_USERUNITDIR -) -vcpkg_cmake_install() -vcpkg_copy_pdbs() -vcpkg_cmake_config_fixup(PACKAGE_NAME "DBus1" CONFIG_PATH "lib/cmake/DBus1") -vcpkg_fixup_pkgconfig() - -file(REMOVE_RECURSE - "${CURRENT_PACKAGES_DIR}/debug/include" - "${CURRENT_PACKAGES_DIR}/debug/share" - "${CURRENT_PACKAGES_DIR}/debug/var/" - "${CURRENT_PACKAGES_DIR}/etc" - "${CURRENT_PACKAGES_DIR}/share/dbus-1/services" - "${CURRENT_PACKAGES_DIR}/share/dbus-1/session.d" - "${CURRENT_PACKAGES_DIR}/share/dbus-1/system-services" - "${CURRENT_PACKAGES_DIR}/share/dbus-1/system.d" - "${CURRENT_PACKAGES_DIR}/share/dbus-1/system.conf" - "${CURRENT_PACKAGES_DIR}/share/dbus-1/system.conf" - "${CURRENT_PACKAGES_DIR}/share/doc" - "${CURRENT_PACKAGES_DIR}/var" -) - -vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/share/dbus-1/session.conf" "<include ignore_missing=\"yes\">${CURRENT_PACKAGES_DIR}/etc/dbus/dbus-1/session.conf</include>" "") -vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/share/dbus-1/session.conf" "<includedir>${CURRENT_PACKAGES_DIR}/etc/dbus/dbus-1/session.d</includedir>" "") -vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/share/dbus-1/session.conf" "<include ignore_missing=\"yes\">${CURRENT_PACKAGES_DIR}/etc/dbus/dbus-1/session-local.conf</include>" "") - -set(TOOLS daemon launch monitor run-session send test-tool update-activation-environment) -if(VCPKG_TARGET_IS_WINDOWS) - file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/tools/${PORT}") - file(RENAME "${CURRENT_PACKAGES_DIR}/bin/dbus-env.bat" "${CURRENT_PACKAGES_DIR}/tools/${PORT}/dbus-env.bat") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/tools/${PORT}/dbus-env.bat" "${CURRENT_PACKAGES_DIR}" "%~dp0/../..") -else() - list(APPEND TOOLS cleanup-sockets uuidgen) -endif() -list(TRANSFORM TOOLS PREPEND "dbus-" ) -vcpkg_copy_tools(TOOL_NAMES ${TOOLS} AUTO_CLEAN) - -file(INSTALL "${SOURCE_PATH}/COPYING" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" RENAME copyright) diff --git a/dependencies/vcpkg_overlay_ports_linux/dbus/vcpkg.json b/dependencies/vcpkg_overlay_ports_linux/dbus/vcpkg.json deleted file mode 100644 index 853dff05..00000000 --- a/dependencies/vcpkg_overlay_ports_linux/dbus/vcpkg.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "dbus", - "version": "1.15.8", - "port-version": 2, - "description": "D-Bus specification and reference implementation, including libdbus and dbus-daemon", - "homepage": "https://gitlab.freedesktop.org/dbus/dbus", - "license": "AFL-2.1 OR GPL-2.0-or-later", - "supports": "!uwp & !staticcrt", - "dependencies": [ - "expat", - { - "name": "vcpkg-cmake", - "host": true - }, - { - "name": "vcpkg-cmake-config", - "host": true - } - ], - "default-features": [ - ], - "features": { - "x11": { - "description": "Build with X11 autolaunch support", - "dependencies": [ - "libx11" - ] - } - } -} diff --git a/dependencies/vcpkg_overlay_ports_linux/sdl2/alsa-dep-fix.patch b/dependencies/vcpkg_overlay_ports_linux/sdl2/alsa-dep-fix.patch deleted file mode 100644 index 5b2c77b9..00000000 --- a/dependencies/vcpkg_overlay_ports_linux/sdl2/alsa-dep-fix.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/SDL2Config.cmake.in b/SDL2Config.cmake.in -index cc8bcf26d..ead829767 100644 ---- a/SDL2Config.cmake.in -+++ b/SDL2Config.cmake.in -@@ -35,7 +35,7 @@ include("${CMAKE_CURRENT_LIST_DIR}/sdlfind.cmake") - - set(SDL_ALSA @SDL_ALSA@) - set(SDL_ALSA_SHARED @SDL_ALSA_SHARED@) --if(SDL_ALSA AND NOT SDL_ALSA_SHARED AND TARGET SDL2::SDL2-static) -+if(SDL_ALSA) - sdlFindALSA() - endif() - unset(SDL_ALSA) diff --git a/dependencies/vcpkg_overlay_ports_linux/sdl2/deps.patch b/dependencies/vcpkg_overlay_ports_linux/sdl2/deps.patch deleted file mode 100644 index a8637d8c..00000000 --- a/dependencies/vcpkg_overlay_ports_linux/sdl2/deps.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/cmake/sdlchecks.cmake b/cmake/sdlchecks.cmake -index 65a98efbe..2f99f28f1 100644 ---- a/cmake/sdlchecks.cmake -+++ b/cmake/sdlchecks.cmake -@@ -352,7 +352,7 @@ endmacro() - # - HAVE_SDL_LOADSO opt - macro(CheckLibSampleRate) - if(SDL_LIBSAMPLERATE) -- find_package(SampleRate QUIET) -+ find_package(SampleRate CONFIG REQUIRED) - if(SampleRate_FOUND AND TARGET SampleRate::samplerate) - set(HAVE_LIBSAMPLERATE TRUE) - set(HAVE_LIBSAMPLERATE_H TRUE) diff --git a/dependencies/vcpkg_overlay_ports_linux/sdl2/portfile.cmake b/dependencies/vcpkg_overlay_ports_linux/sdl2/portfile.cmake deleted file mode 100644 index 22685e6a..00000000 --- a/dependencies/vcpkg_overlay_ports_linux/sdl2/portfile.cmake +++ /dev/null @@ -1,137 +0,0 @@ -vcpkg_from_github( - OUT_SOURCE_PATH SOURCE_PATH - REPO libsdl-org/SDL - REF "release-${VERSION}" - SHA512 c7635a83a52f3970a372b804a8631f0a7e6b8d89aed1117bcc54a2040ad0928122175004cf2b42cf84a4fd0f86236f779229eaa63dfa6ca9c89517f999c5ff1c - HEAD_REF main - PATCHES - deps.patch - alsa-dep-fix.patch -) - -string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "static" SDL_STATIC) -string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "dynamic" SDL_SHARED) -string(COMPARE EQUAL "${VCPKG_CRT_LINKAGE}" "static" FORCE_STATIC_VCRT) - -vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS - FEATURES - alsa SDL_ALSA - alsa CMAKE_REQUIRE_FIND_PACKAGE_ALSA - ibus SDL_IBUS - samplerate SDL_LIBSAMPLERATE - vulkan SDL_VULKAN - wayland SDL_WAYLAND - x11 SDL_X11 - INVERTED_FEATURES - alsa CMAKE_DISABLE_FIND_PACKAGE_ALSA -) - -if ("x11" IN_LIST FEATURES) - message(WARNING "You will need to install Xorg dependencies to use feature x11:\nsudo apt install libx11-dev libxft-dev libxext-dev\n") -endif() -if ("wayland" IN_LIST FEATURES) - message(WARNING "You will need to install Wayland dependencies to use feature wayland:\nsudo apt install libwayland-dev libxkbcommon-dev libegl1-mesa-dev\n") -endif() -if ("ibus" IN_LIST FEATURES) - message(WARNING "You will need to install ibus dependencies to use feature ibus:\nsudo apt install libibus-1.0-dev\n") -endif() - -if(VCPKG_TARGET_IS_UWP) - set(configure_opts WINDOWS_USE_MSBUILD) -endif() - -vcpkg_cmake_configure( - SOURCE_PATH "${SOURCE_PATH}" - ${configure_opts} - OPTIONS ${FEATURE_OPTIONS} - -DSDL_STATIC=${SDL_STATIC} - -DSDL_SHARED=${SDL_SHARED} - -DSDL_FORCE_STATIC_VCRT=${FORCE_STATIC_VCRT} - -DSDL_LIBC=ON - -DSDL_TEST=OFF - -DSDL_INSTALL_CMAKEDIR="cmake" - -DCMAKE_DISABLE_FIND_PACKAGE_Git=ON - -DPKG_CONFIG_USE_CMAKE_PREFIX_PATH=ON - -DSDL_LIBSAMPLERATE_SHARED=OFF - MAYBE_UNUSED_VARIABLES - SDL_FORCE_STATIC_VCRT - PKG_CONFIG_USE_CMAKE_PREFIX_PATH -) - -vcpkg_cmake_install() -vcpkg_cmake_config_fixup(CONFIG_PATH cmake) - -file(REMOVE_RECURSE - "${CURRENT_PACKAGES_DIR}/debug/include" - "${CURRENT_PACKAGES_DIR}/debug/share" - "${CURRENT_PACKAGES_DIR}/bin/sdl2-config" - "${CURRENT_PACKAGES_DIR}/debug/bin/sdl2-config" - "${CURRENT_PACKAGES_DIR}/SDL2.framework" - "${CURRENT_PACKAGES_DIR}/debug/SDL2.framework" - "${CURRENT_PACKAGES_DIR}/share/licenses" - "${CURRENT_PACKAGES_DIR}/share/aclocal" -) - -file(GLOB BINS "${CURRENT_PACKAGES_DIR}/debug/bin/*" "${CURRENT_PACKAGES_DIR}/bin/*") -if(NOT BINS) - file(REMOVE_RECURSE - "${CURRENT_PACKAGES_DIR}/bin" - "${CURRENT_PACKAGES_DIR}/debug/bin" - ) -endif() - -if(VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_UWP AND NOT VCPKG_TARGET_IS_MINGW) - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") - file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/lib/manual-link") - file(RENAME "${CURRENT_PACKAGES_DIR}/lib/SDL2main.lib" "${CURRENT_PACKAGES_DIR}/lib/manual-link/SDL2main.lib") - endif() - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link") - file(RENAME "${CURRENT_PACKAGES_DIR}/debug/lib/SDL2maind.lib" "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link/SDL2maind.lib") - endif() - - file(GLOB SHARE_FILES "${CURRENT_PACKAGES_DIR}/share/sdl2/*.cmake") - foreach(SHARE_FILE ${SHARE_FILES}) - vcpkg_replace_string("${SHARE_FILE}" "lib/SDL2main" "lib/manual-link/SDL2main") - endforeach() -endif() - -vcpkg_copy_pdbs() - -set(DYLIB_COMPATIBILITY_VERSION_REGEX "set\\(DYLIB_COMPATIBILITY_VERSION (.+)\\)") -set(DYLIB_CURRENT_VERSION_REGEX "set\\(DYLIB_CURRENT_VERSION (.+)\\)") -file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_COMPATIBILITY_VERSION REGEX ${DYLIB_COMPATIBILITY_VERSION_REGEX}) -file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_CURRENT_VERSION REGEX ${DYLIB_CURRENT_VERSION_REGEX}) -string(REGEX REPLACE ${DYLIB_COMPATIBILITY_VERSION_REGEX} "\\1" DYLIB_COMPATIBILITY_VERSION "${DYLIB_COMPATIBILITY_VERSION}") -string(REGEX REPLACE ${DYLIB_CURRENT_VERSION_REGEX} "\\1" DYLIB_CURRENT_VERSION "${DYLIB_CURRENT_VERSION}") - -if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2main" "-lSDL2maind") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2 " "-lSDL2d ") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-static " "-lSDL2-staticd ") -endif() - -if(VCPKG_LIBRARY_LINKAGE STREQUAL "dynamic" AND VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_MINGW) - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-lSDL2-static " " ") - endif() - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-staticd " " ") - endif() -endif() - -if(VCPKG_TARGET_IS_UWP) - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "$<$<CONFIG:Debug>:d>.lib" "") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:") - endif() - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "$<$<CONFIG:Debug>:d>.lib" "d") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:") - endif() -endif() - -vcpkg_fixup_pkgconfig() - -file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") -vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.txt") diff --git a/dependencies/vcpkg_overlay_ports_linux/sdl2/usage b/dependencies/vcpkg_overlay_ports_linux/sdl2/usage deleted file mode 100644 index 1cddcd46..00000000 --- a/dependencies/vcpkg_overlay_ports_linux/sdl2/usage +++ /dev/null @@ -1,8 +0,0 @@ -sdl2 provides CMake targets: - - find_package(SDL2 CONFIG REQUIRED) - target_link_libraries(main - PRIVATE - $<TARGET_NAME_IF_EXISTS:SDL2::SDL2main> - $<IF:$<TARGET_EXISTS:SDL2::SDL2>,SDL2::SDL2,SDL2::SDL2-static> - ) diff --git a/dependencies/vcpkg_overlay_ports_linux/sdl2/vcpkg.json b/dependencies/vcpkg_overlay_ports_linux/sdl2/vcpkg.json deleted file mode 100644 index 1f460375..00000000 --- a/dependencies/vcpkg_overlay_ports_linux/sdl2/vcpkg.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "name": "sdl2", - "version": "2.30.0", - "description": "Simple DirectMedia Layer is a cross-platform development library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL and Direct3D.", - "homepage": "https://www.libsdl.org/download-2.0.php", - "license": "Zlib", - "dependencies": [ - { - "name": "dbus", - "default-features": false, - "platform": "linux" - }, - { - "name": "vcpkg-cmake", - "host": true - }, - { - "name": "vcpkg-cmake-config", - "host": true - } - ], - "default-features": [ - { - "name": "ibus", - "platform": "linux" - }, - { - "name": "wayland", - "platform": "linux" - }, - { - "name": "x11", - "platform": "linux" - } - ], - "features": { - "alsa": { - "description": "Support for alsa audio", - "dependencies": [ - { - "name": "alsa", - "platform": "linux" - } - ] - }, - "ibus": { - "description": "Build with ibus IME support", - "supports": "linux" - }, - "samplerate": { - "description": "Use libsamplerate for audio rate conversion", - "dependencies": [ - "libsamplerate" - ] - }, - "vulkan": { - "description": "Vulkan functionality for SDL" - }, - "wayland": { - "description": "Build with Wayland support", - "supports": "linux" - }, - "x11": { - "description": "Build with X11 support", - "supports": "!windows" - } - } -} diff --git a/dependencies/vcpkg_overlay_ports_linux/tiff/FindCMath.patch b/dependencies/vcpkg_overlay_ports_linux/tiff/FindCMath.patch deleted file mode 100644 index 70654cf8..00000000 --- a/dependencies/vcpkg_overlay_ports_linux/tiff/FindCMath.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/cmake/FindCMath.cmake b/cmake/FindCMath.cmake -index ad92218..dd42aba 100644 ---- a/cmake/FindCMath.cmake -+++ b/cmake/FindCMath.cmake -@@ -31,7 +31,7 @@ include(CheckSymbolExists) - include(CheckLibraryExists) - - check_symbol_exists(pow "math.h" CMath_HAVE_LIBC_POW) --find_library(CMath_LIBRARY NAMES m) -+find_library(CMath_LIBRARY NAMES m PATHS ${CMAKE_C_IMPLICIT_LINK_DIRECTORIES}) - - if(NOT CMath_HAVE_LIBC_POW) - set(CMAKE_REQUIRED_LIBRARIES_SAVE ${CMAKE_REQUIRED_LIBRARIES}) diff --git a/dependencies/vcpkg_overlay_ports_linux/tiff/portfile.cmake b/dependencies/vcpkg_overlay_ports_linux/tiff/portfile.cmake deleted file mode 100644 index 426d8af7..00000000 --- a/dependencies/vcpkg_overlay_ports_linux/tiff/portfile.cmake +++ /dev/null @@ -1,86 +0,0 @@ -vcpkg_from_gitlab( - GITLAB_URL https://gitlab.com - OUT_SOURCE_PATH SOURCE_PATH - REPO libtiff/libtiff - REF "v${VERSION}" - SHA512 ef2f1d424219d9e245069b7d23e78f5e817cf6ee516d46694915ab6c8909522166f84997513d20a702f4e52c3f18467813935b328fafa34bea5156dee00f66fa - HEAD_REF master - PATCHES - FindCMath.patch -) - -vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS - FEATURES - cxx cxx - jpeg jpeg - jpeg CMAKE_REQUIRE_FIND_PACKAGE_JPEG - libdeflate libdeflate - libdeflate CMAKE_REQUIRE_FIND_PACKAGE_Deflate - lzma lzma - lzma CMAKE_REQUIRE_FIND_PACKAGE_liblzma - tools tiff-tools - webp webp - webp CMAKE_REQUIRE_FIND_PACKAGE_WebP - zip zlib - zip CMAKE_REQUIRE_FIND_PACKAGE_ZLIB - zstd zstd - zstd CMAKE_REQUIRE_FIND_PACKAGE_ZSTD -) - -vcpkg_cmake_configure( - SOURCE_PATH "${SOURCE_PATH}" - OPTIONS - ${FEATURE_OPTIONS} - -DCMAKE_FIND_PACKAGE_PREFER_CONFIG=ON - -Dtiff-docs=OFF - -Dtiff-contrib=OFF - -Dtiff-tests=OFF - -Djbig=OFF # This is disabled by default due to GPL/Proprietary licensing. - -Djpeg12=OFF - -Dlerc=OFF - -DCMAKE_DISABLE_FIND_PACKAGE_OpenGL=ON - -DCMAKE_DISABLE_FIND_PACKAGE_GLUT=ON - -DZSTD_HAVE_DECOMPRESS_STREAM=ON - -DHAVE_JPEGTURBO_DUAL_MODE_8_12=OFF - OPTIONS_DEBUG - -DCMAKE_DEBUG_POSTFIX=d # tiff sets "d" for MSVC only. - MAYBE_UNUSED_VARIABLES - CMAKE_DISABLE_FIND_PACKAGE_GLUT - CMAKE_DISABLE_FIND_PACKAGE_OpenGL - ZSTD_HAVE_DECOMPRESS_STREAM -) - -vcpkg_cmake_install() - -# CMake config wasn't packaged in the past and is not yet usable now, -# cf. https://gitlab.com/libtiff/libtiff/-/merge_requests/496 -# vcpkg_cmake_config_fixup(CONFIG_PATH "lib/cmake/tiff") -file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/lib/cmake" "${CURRENT_PACKAGES_DIR}/debug/lib/cmake") - -set(_file "${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/libtiff-4.pc") -if(EXISTS "${_file}") - vcpkg_replace_string("${_file}" "-ltiff" "-ltiffd") -endif() -vcpkg_fixup_pkgconfig() - -file(REMOVE_RECURSE - "${CURRENT_PACKAGES_DIR}/debug/include" - "${CURRENT_PACKAGES_DIR}/debug/share" -) - -configure_file("${CMAKE_CURRENT_LIST_DIR}/vcpkg-cmake-wrapper.cmake.in" "${CURRENT_PACKAGES_DIR}/share/${PORT}/vcpkg-cmake-wrapper.cmake" @ONLY) - -if ("tools" IN_LIST FEATURES) - vcpkg_copy_tools(TOOL_NAMES - tiffcp - tiffdump - tiffinfo - tiffset - tiffsplit - AUTO_CLEAN - ) -endif() - -vcpkg_copy_pdbs() -file(COPY "${CURRENT_PORT_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") -vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.md") diff --git a/dependencies/vcpkg_overlay_ports_linux/tiff/usage b/dependencies/vcpkg_overlay_ports_linux/tiff/usage deleted file mode 100644 index d47265b1..00000000 --- a/dependencies/vcpkg_overlay_ports_linux/tiff/usage +++ /dev/null @@ -1,9 +0,0 @@ -tiff is compatible with built-in CMake targets: - - find_package(TIFF REQUIRED) - target_link_libraries(main PRIVATE TIFF::TIFF) - -tiff provides pkg-config modules: - - # Tag Image File Format (TIFF) library. - libtiff-4 diff --git a/dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg-cmake-wrapper.cmake.in b/dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg-cmake-wrapper.cmake.in deleted file mode 100644 index 1d04ec7a..00000000 --- a/dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg-cmake-wrapper.cmake.in +++ /dev/null @@ -1,104 +0,0 @@ -cmake_policy(PUSH) -cmake_policy(SET CMP0012 NEW) -cmake_policy(SET CMP0057 NEW) -set(z_vcpkg_tiff_find_options "") -if("REQUIRED" IN_LIST ARGS) - list(APPEND z_vcpkg_tiff_find_options "REQUIRED") -endif() -if("QUIET" IN_LIST ARGS) - list(APPEND z_vcpkg_tiff_find_options "QUIET") -endif() - -_find_package(${ARGS}) - -if(TIFF_FOUND AND "@VCPKG_LIBRARY_LINKAGE@" STREQUAL "static") - include(SelectLibraryConfigurations) - set(z_vcpkg_tiff_link_libraries "") - set(z_vcpkg_tiff_libraries "") - if("@webp@") - find_package(WebP CONFIG ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:WebP::WebP>") - list(APPEND z_vcpkg_tiff_libraries ${WebP_LIBRARIES}) - endif() - if("@lzma@") - find_package(LibLZMA ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:LibLZMA::LibLZMA>") - list(APPEND z_vcpkg_tiff_libraries ${LIBLZMA_LIBRARIES}) - endif() - if("@jpeg@") - find_package(JPEG ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:JPEG::JPEG>") - list(APPEND z_vcpkg_tiff_libraries ${JPEG_LIBRARIES}) - endif() - if("@zstd@") - find_package(zstd CONFIG ${z_vcpkg_tiff_find_options}) - set(z_vcpkg_tiff_zstd_target_property "IMPORTED_LOCATION_") - if(TARGET zstd::libzstd_shared) - set(z_vcpkg_tiff_zstd "\$<LINK_ONLY:zstd::libzstd_shared>") - set(z_vcpkg_tiff_zstd_target zstd::libzstd_shared) - if(WIN32) - set(z_vcpkg_tiff_zstd_target_property "IMPORTED_IMPLIB_") - endif() - else() - set(z_vcpkg_tiff_zstd "\$<LINK_ONLY:zstd::libzstd_static>") - set(z_vcpkg_tiff_zstd_target zstd::libzstd_static) - endif() - get_target_property(z_vcpkg_tiff_zstd_configs "${z_vcpkg_tiff_zstd_target}" IMPORTED_CONFIGURATIONS) - foreach(z_vcpkg_config IN LISTS z_vcpkg_tiff_zstd_configs) - get_target_property(ZSTD_LIBRARY_${z_vcpkg_config} "${z_vcpkg_tiff_zstd_target}" "${z_vcpkg_tiff_zstd_target_property}${z_vcpkg_config}") - endforeach() - select_library_configurations(ZSTD) - if(NOT TARGET ZSTD::ZSTD) - add_library(ZSTD::ZSTD INTERFACE IMPORTED) - set_property(TARGET ZSTD::ZSTD APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${z_vcpkg_tiff_zstd}) - endif() - list(APPEND z_vcpkg_tiff_link_libraries ${z_vcpkg_tiff_zstd}) - list(APPEND z_vcpkg_tiff_libraries ${ZSTD_LIBRARIES}) - unset(z_vcpkg_tiff_zstd) - unset(z_vcpkg_tiff_zstd_configs) - unset(z_vcpkg_config) - unset(z_vcpkg_tiff_zstd_target) - endif() - if("@libdeflate@") - find_package(libdeflate ${z_vcpkg_tiff_find_options}) - set(z_vcpkg_property "IMPORTED_LOCATION_") - if(TARGET libdeflate::libdeflate_shared) - set(z_vcpkg_libdeflate_target libdeflate::libdeflate_shared) - if(WIN32) - set(z_vcpkg_property "IMPORTED_IMPLIB_") - endif() - else() - set(z_vcpkg_libdeflate_target libdeflate::libdeflate_static) - endif() - get_target_property(z_vcpkg_libdeflate_configs "${z_vcpkg_libdeflate_target}" IMPORTED_CONFIGURATIONS) - foreach(z_vcpkg_config IN LISTS z_vcpkg_libdeflate_configs) - get_target_property(Z_VCPKG_DEFLATE_LIBRARY_${z_vcpkg_config} "${z_vcpkg_libdeflate_target}" "${z_vcpkg_property}${z_vcpkg_config}") - endforeach() - select_library_configurations(Z_VCPKG_DEFLATE) - list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:${z_vcpkg_libdeflate_target}>") - list(APPEND z_vcpkg_tiff_libraries ${Z_VCPKG_DEFLATE_LIBRARIES}) - unset(z_vcpkg_config) - unset(z_vcpkg_libdeflate_configs) - unset(z_vcpkg_libdeflate_target) - unset(z_vcpkg_property) - unset(Z_VCPKG_DEFLATE_FOUND) - endif() - if("@zlib@") - find_package(ZLIB ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:ZLIB::ZLIB>") - list(APPEND z_vcpkg_tiff_libraries ${ZLIB_LIBRARIES}) - endif() - if(UNIX) - list(APPEND z_vcpkg_tiff_link_libraries m) - list(APPEND z_vcpkg_tiff_libraries m) - endif() - - if(TARGET TIFF::TIFF) - set_property(TARGET TIFF::TIFF APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${z_vcpkg_tiff_link_libraries}) - endif() - list(APPEND TIFF_LIBRARIES ${z_vcpkg_tiff_libraries}) - unset(z_vcpkg_tiff_link_libraries) - unset(z_vcpkg_tiff_libraries) -endif() -unset(z_vcpkg_tiff_find_options) -cmake_policy(POP) diff --git a/dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg.json b/dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg.json deleted file mode 100644 index 9b36e1a8..00000000 --- a/dependencies/vcpkg_overlay_ports_linux/tiff/vcpkg.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "name": "tiff", - "version": "4.6.0", - "port-version": 2, - "description": "A library that supports the manipulation of TIFF image files", - "homepage": "https://libtiff.gitlab.io/libtiff/", - "license": "libtiff", - "dependencies": [ - { - "name": "vcpkg-cmake", - "host": true - }, - { - "name": "vcpkg-cmake-config", - "host": true - } - ], - "default-features": [ - "jpeg", - "zip" - ], - "features": { - "cxx": { - "description": "Build C++ libtiffxx library" - }, - "jpeg": { - "description": "Support JPEG compression in TIFF image files", - "dependencies": [ - "libjpeg-turbo" - ] - }, - "libdeflate": { - "description": "Use libdeflate for faster ZIP support", - "dependencies": [ - "libdeflate", - { - "name": "tiff", - "default-features": false, - "features": [ - "zip" - ] - } - ] - }, - "tools": { - "description": "Build tools" - }, - "webp": { - "description": "Support WEBP compression in TIFF image files", - "dependencies": [ - "libwebp" - ] - }, - "zip": { - "description": "Support ZIP/deflate compression in TIFF image files", - "dependencies": [ - "zlib" - ] - }, - "zstd": { - "description": "Support ZSTD compression in TIFF image files", - "dependencies": [ - "zstd" - ] - } - } -} diff --git a/dependencies/vcpkg_overlay_ports_mac/sdl2/alsa-dep-fix.patch b/dependencies/vcpkg_overlay_ports_mac/sdl2/alsa-dep-fix.patch deleted file mode 100644 index 5b2c77b9..00000000 --- a/dependencies/vcpkg_overlay_ports_mac/sdl2/alsa-dep-fix.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/SDL2Config.cmake.in b/SDL2Config.cmake.in -index cc8bcf26d..ead829767 100644 ---- a/SDL2Config.cmake.in -+++ b/SDL2Config.cmake.in -@@ -35,7 +35,7 @@ include("${CMAKE_CURRENT_LIST_DIR}/sdlfind.cmake") - - set(SDL_ALSA @SDL_ALSA@) - set(SDL_ALSA_SHARED @SDL_ALSA_SHARED@) --if(SDL_ALSA AND NOT SDL_ALSA_SHARED AND TARGET SDL2::SDL2-static) -+if(SDL_ALSA) - sdlFindALSA() - endif() - unset(SDL_ALSA) diff --git a/dependencies/vcpkg_overlay_ports_mac/sdl2/deps.patch b/dependencies/vcpkg_overlay_ports_mac/sdl2/deps.patch deleted file mode 100644 index a8637d8c..00000000 --- a/dependencies/vcpkg_overlay_ports_mac/sdl2/deps.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/cmake/sdlchecks.cmake b/cmake/sdlchecks.cmake -index 65a98efbe..2f99f28f1 100644 ---- a/cmake/sdlchecks.cmake -+++ b/cmake/sdlchecks.cmake -@@ -352,7 +352,7 @@ endmacro() - # - HAVE_SDL_LOADSO opt - macro(CheckLibSampleRate) - if(SDL_LIBSAMPLERATE) -- find_package(SampleRate QUIET) -+ find_package(SampleRate CONFIG REQUIRED) - if(SampleRate_FOUND AND TARGET SampleRate::samplerate) - set(HAVE_LIBSAMPLERATE TRUE) - set(HAVE_LIBSAMPLERATE_H TRUE) diff --git a/dependencies/vcpkg_overlay_ports_mac/sdl2/portfile.cmake b/dependencies/vcpkg_overlay_ports_mac/sdl2/portfile.cmake deleted file mode 100644 index 22685e6a..00000000 --- a/dependencies/vcpkg_overlay_ports_mac/sdl2/portfile.cmake +++ /dev/null @@ -1,137 +0,0 @@ -vcpkg_from_github( - OUT_SOURCE_PATH SOURCE_PATH - REPO libsdl-org/SDL - REF "release-${VERSION}" - SHA512 c7635a83a52f3970a372b804a8631f0a7e6b8d89aed1117bcc54a2040ad0928122175004cf2b42cf84a4fd0f86236f779229eaa63dfa6ca9c89517f999c5ff1c - HEAD_REF main - PATCHES - deps.patch - alsa-dep-fix.patch -) - -string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "static" SDL_STATIC) -string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "dynamic" SDL_SHARED) -string(COMPARE EQUAL "${VCPKG_CRT_LINKAGE}" "static" FORCE_STATIC_VCRT) - -vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS - FEATURES - alsa SDL_ALSA - alsa CMAKE_REQUIRE_FIND_PACKAGE_ALSA - ibus SDL_IBUS - samplerate SDL_LIBSAMPLERATE - vulkan SDL_VULKAN - wayland SDL_WAYLAND - x11 SDL_X11 - INVERTED_FEATURES - alsa CMAKE_DISABLE_FIND_PACKAGE_ALSA -) - -if ("x11" IN_LIST FEATURES) - message(WARNING "You will need to install Xorg dependencies to use feature x11:\nsudo apt install libx11-dev libxft-dev libxext-dev\n") -endif() -if ("wayland" IN_LIST FEATURES) - message(WARNING "You will need to install Wayland dependencies to use feature wayland:\nsudo apt install libwayland-dev libxkbcommon-dev libegl1-mesa-dev\n") -endif() -if ("ibus" IN_LIST FEATURES) - message(WARNING "You will need to install ibus dependencies to use feature ibus:\nsudo apt install libibus-1.0-dev\n") -endif() - -if(VCPKG_TARGET_IS_UWP) - set(configure_opts WINDOWS_USE_MSBUILD) -endif() - -vcpkg_cmake_configure( - SOURCE_PATH "${SOURCE_PATH}" - ${configure_opts} - OPTIONS ${FEATURE_OPTIONS} - -DSDL_STATIC=${SDL_STATIC} - -DSDL_SHARED=${SDL_SHARED} - -DSDL_FORCE_STATIC_VCRT=${FORCE_STATIC_VCRT} - -DSDL_LIBC=ON - -DSDL_TEST=OFF - -DSDL_INSTALL_CMAKEDIR="cmake" - -DCMAKE_DISABLE_FIND_PACKAGE_Git=ON - -DPKG_CONFIG_USE_CMAKE_PREFIX_PATH=ON - -DSDL_LIBSAMPLERATE_SHARED=OFF - MAYBE_UNUSED_VARIABLES - SDL_FORCE_STATIC_VCRT - PKG_CONFIG_USE_CMAKE_PREFIX_PATH -) - -vcpkg_cmake_install() -vcpkg_cmake_config_fixup(CONFIG_PATH cmake) - -file(REMOVE_RECURSE - "${CURRENT_PACKAGES_DIR}/debug/include" - "${CURRENT_PACKAGES_DIR}/debug/share" - "${CURRENT_PACKAGES_DIR}/bin/sdl2-config" - "${CURRENT_PACKAGES_DIR}/debug/bin/sdl2-config" - "${CURRENT_PACKAGES_DIR}/SDL2.framework" - "${CURRENT_PACKAGES_DIR}/debug/SDL2.framework" - "${CURRENT_PACKAGES_DIR}/share/licenses" - "${CURRENT_PACKAGES_DIR}/share/aclocal" -) - -file(GLOB BINS "${CURRENT_PACKAGES_DIR}/debug/bin/*" "${CURRENT_PACKAGES_DIR}/bin/*") -if(NOT BINS) - file(REMOVE_RECURSE - "${CURRENT_PACKAGES_DIR}/bin" - "${CURRENT_PACKAGES_DIR}/debug/bin" - ) -endif() - -if(VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_UWP AND NOT VCPKG_TARGET_IS_MINGW) - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") - file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/lib/manual-link") - file(RENAME "${CURRENT_PACKAGES_DIR}/lib/SDL2main.lib" "${CURRENT_PACKAGES_DIR}/lib/manual-link/SDL2main.lib") - endif() - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - file(MAKE_DIRECTORY "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link") - file(RENAME "${CURRENT_PACKAGES_DIR}/debug/lib/SDL2maind.lib" "${CURRENT_PACKAGES_DIR}/debug/lib/manual-link/SDL2maind.lib") - endif() - - file(GLOB SHARE_FILES "${CURRENT_PACKAGES_DIR}/share/sdl2/*.cmake") - foreach(SHARE_FILE ${SHARE_FILES}) - vcpkg_replace_string("${SHARE_FILE}" "lib/SDL2main" "lib/manual-link/SDL2main") - endforeach() -endif() - -vcpkg_copy_pdbs() - -set(DYLIB_COMPATIBILITY_VERSION_REGEX "set\\(DYLIB_COMPATIBILITY_VERSION (.+)\\)") -set(DYLIB_CURRENT_VERSION_REGEX "set\\(DYLIB_CURRENT_VERSION (.+)\\)") -file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_COMPATIBILITY_VERSION REGEX ${DYLIB_COMPATIBILITY_VERSION_REGEX}) -file(STRINGS "${SOURCE_PATH}/CMakeLists.txt" DYLIB_CURRENT_VERSION REGEX ${DYLIB_CURRENT_VERSION_REGEX}) -string(REGEX REPLACE ${DYLIB_COMPATIBILITY_VERSION_REGEX} "\\1" DYLIB_COMPATIBILITY_VERSION "${DYLIB_COMPATIBILITY_VERSION}") -string(REGEX REPLACE ${DYLIB_CURRENT_VERSION_REGEX} "\\1" DYLIB_CURRENT_VERSION "${DYLIB_CURRENT_VERSION}") - -if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2main" "-lSDL2maind") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2 " "-lSDL2d ") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-static " "-lSDL2-staticd ") -endif() - -if(VCPKG_LIBRARY_LINKAGE STREQUAL "dynamic" AND VCPKG_TARGET_IS_WINDOWS AND NOT VCPKG_TARGET_IS_MINGW) - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-lSDL2-static " " ") - endif() - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-lSDL2-staticd " " ") - endif() -endif() - -if(VCPKG_TARGET_IS_UWP) - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "$<$<CONFIG:Debug>:d>.lib" "") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:") - endif() - if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "debug") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "$<$<CONFIG:Debug>:d>.lib" "d") - vcpkg_replace_string("${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/sdl2.pc" "-l-nodefaultlib:" "-nodefaultlib:") - endif() -endif() - -vcpkg_fixup_pkgconfig() - -file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") -vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.txt") diff --git a/dependencies/vcpkg_overlay_ports_mac/sdl2/usage b/dependencies/vcpkg_overlay_ports_mac/sdl2/usage deleted file mode 100644 index 1cddcd46..00000000 --- a/dependencies/vcpkg_overlay_ports_mac/sdl2/usage +++ /dev/null @@ -1,8 +0,0 @@ -sdl2 provides CMake targets: - - find_package(SDL2 CONFIG REQUIRED) - target_link_libraries(main - PRIVATE - $<TARGET_NAME_IF_EXISTS:SDL2::SDL2main> - $<IF:$<TARGET_EXISTS:SDL2::SDL2>,SDL2::SDL2,SDL2::SDL2-static> - ) diff --git a/dependencies/vcpkg_overlay_ports_mac/sdl2/vcpkg.json b/dependencies/vcpkg_overlay_ports_mac/sdl2/vcpkg.json deleted file mode 100644 index 1f460375..00000000 --- a/dependencies/vcpkg_overlay_ports_mac/sdl2/vcpkg.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "name": "sdl2", - "version": "2.30.0", - "description": "Simple DirectMedia Layer is a cross-platform development library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL and Direct3D.", - "homepage": "https://www.libsdl.org/download-2.0.php", - "license": "Zlib", - "dependencies": [ - { - "name": "dbus", - "default-features": false, - "platform": "linux" - }, - { - "name": "vcpkg-cmake", - "host": true - }, - { - "name": "vcpkg-cmake-config", - "host": true - } - ], - "default-features": [ - { - "name": "ibus", - "platform": "linux" - }, - { - "name": "wayland", - "platform": "linux" - }, - { - "name": "x11", - "platform": "linux" - } - ], - "features": { - "alsa": { - "description": "Support for alsa audio", - "dependencies": [ - { - "name": "alsa", - "platform": "linux" - } - ] - }, - "ibus": { - "description": "Build with ibus IME support", - "supports": "linux" - }, - "samplerate": { - "description": "Use libsamplerate for audio rate conversion", - "dependencies": [ - "libsamplerate" - ] - }, - "vulkan": { - "description": "Vulkan functionality for SDL" - }, - "wayland": { - "description": "Build with Wayland support", - "supports": "linux" - }, - "x11": { - "description": "Build with X11 support", - "supports": "!windows" - } - } -} diff --git a/dependencies/vcpkg_overlay_ports_mac/tiff/FindCMath.patch b/dependencies/vcpkg_overlay_ports_mac/tiff/FindCMath.patch deleted file mode 100644 index 70654cf8..00000000 --- a/dependencies/vcpkg_overlay_ports_mac/tiff/FindCMath.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/cmake/FindCMath.cmake b/cmake/FindCMath.cmake -index ad92218..dd42aba 100644 ---- a/cmake/FindCMath.cmake -+++ b/cmake/FindCMath.cmake -@@ -31,7 +31,7 @@ include(CheckSymbolExists) - include(CheckLibraryExists) - - check_symbol_exists(pow "math.h" CMath_HAVE_LIBC_POW) --find_library(CMath_LIBRARY NAMES m) -+find_library(CMath_LIBRARY NAMES m PATHS ${CMAKE_C_IMPLICIT_LINK_DIRECTORIES}) - - if(NOT CMath_HAVE_LIBC_POW) - set(CMAKE_REQUIRED_LIBRARIES_SAVE ${CMAKE_REQUIRED_LIBRARIES}) diff --git a/dependencies/vcpkg_overlay_ports_mac/tiff/portfile.cmake b/dependencies/vcpkg_overlay_ports_mac/tiff/portfile.cmake deleted file mode 100644 index 426d8af7..00000000 --- a/dependencies/vcpkg_overlay_ports_mac/tiff/portfile.cmake +++ /dev/null @@ -1,86 +0,0 @@ -vcpkg_from_gitlab( - GITLAB_URL https://gitlab.com - OUT_SOURCE_PATH SOURCE_PATH - REPO libtiff/libtiff - REF "v${VERSION}" - SHA512 ef2f1d424219d9e245069b7d23e78f5e817cf6ee516d46694915ab6c8909522166f84997513d20a702f4e52c3f18467813935b328fafa34bea5156dee00f66fa - HEAD_REF master - PATCHES - FindCMath.patch -) - -vcpkg_check_features(OUT_FEATURE_OPTIONS FEATURE_OPTIONS - FEATURES - cxx cxx - jpeg jpeg - jpeg CMAKE_REQUIRE_FIND_PACKAGE_JPEG - libdeflate libdeflate - libdeflate CMAKE_REQUIRE_FIND_PACKAGE_Deflate - lzma lzma - lzma CMAKE_REQUIRE_FIND_PACKAGE_liblzma - tools tiff-tools - webp webp - webp CMAKE_REQUIRE_FIND_PACKAGE_WebP - zip zlib - zip CMAKE_REQUIRE_FIND_PACKAGE_ZLIB - zstd zstd - zstd CMAKE_REQUIRE_FIND_PACKAGE_ZSTD -) - -vcpkg_cmake_configure( - SOURCE_PATH "${SOURCE_PATH}" - OPTIONS - ${FEATURE_OPTIONS} - -DCMAKE_FIND_PACKAGE_PREFER_CONFIG=ON - -Dtiff-docs=OFF - -Dtiff-contrib=OFF - -Dtiff-tests=OFF - -Djbig=OFF # This is disabled by default due to GPL/Proprietary licensing. - -Djpeg12=OFF - -Dlerc=OFF - -DCMAKE_DISABLE_FIND_PACKAGE_OpenGL=ON - -DCMAKE_DISABLE_FIND_PACKAGE_GLUT=ON - -DZSTD_HAVE_DECOMPRESS_STREAM=ON - -DHAVE_JPEGTURBO_DUAL_MODE_8_12=OFF - OPTIONS_DEBUG - -DCMAKE_DEBUG_POSTFIX=d # tiff sets "d" for MSVC only. - MAYBE_UNUSED_VARIABLES - CMAKE_DISABLE_FIND_PACKAGE_GLUT - CMAKE_DISABLE_FIND_PACKAGE_OpenGL - ZSTD_HAVE_DECOMPRESS_STREAM -) - -vcpkg_cmake_install() - -# CMake config wasn't packaged in the past and is not yet usable now, -# cf. https://gitlab.com/libtiff/libtiff/-/merge_requests/496 -# vcpkg_cmake_config_fixup(CONFIG_PATH "lib/cmake/tiff") -file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/lib/cmake" "${CURRENT_PACKAGES_DIR}/debug/lib/cmake") - -set(_file "${CURRENT_PACKAGES_DIR}/debug/lib/pkgconfig/libtiff-4.pc") -if(EXISTS "${_file}") - vcpkg_replace_string("${_file}" "-ltiff" "-ltiffd") -endif() -vcpkg_fixup_pkgconfig() - -file(REMOVE_RECURSE - "${CURRENT_PACKAGES_DIR}/debug/include" - "${CURRENT_PACKAGES_DIR}/debug/share" -) - -configure_file("${CMAKE_CURRENT_LIST_DIR}/vcpkg-cmake-wrapper.cmake.in" "${CURRENT_PACKAGES_DIR}/share/${PORT}/vcpkg-cmake-wrapper.cmake" @ONLY) - -if ("tools" IN_LIST FEATURES) - vcpkg_copy_tools(TOOL_NAMES - tiffcp - tiffdump - tiffinfo - tiffset - tiffsplit - AUTO_CLEAN - ) -endif() - -vcpkg_copy_pdbs() -file(COPY "${CURRENT_PORT_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}") -vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE.md") diff --git a/dependencies/vcpkg_overlay_ports_mac/tiff/usage b/dependencies/vcpkg_overlay_ports_mac/tiff/usage deleted file mode 100644 index d47265b1..00000000 --- a/dependencies/vcpkg_overlay_ports_mac/tiff/usage +++ /dev/null @@ -1,9 +0,0 @@ -tiff is compatible with built-in CMake targets: - - find_package(TIFF REQUIRED) - target_link_libraries(main PRIVATE TIFF::TIFF) - -tiff provides pkg-config modules: - - # Tag Image File Format (TIFF) library. - libtiff-4 diff --git a/dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg-cmake-wrapper.cmake.in b/dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg-cmake-wrapper.cmake.in deleted file mode 100644 index 1d04ec7a..00000000 --- a/dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg-cmake-wrapper.cmake.in +++ /dev/null @@ -1,104 +0,0 @@ -cmake_policy(PUSH) -cmake_policy(SET CMP0012 NEW) -cmake_policy(SET CMP0057 NEW) -set(z_vcpkg_tiff_find_options "") -if("REQUIRED" IN_LIST ARGS) - list(APPEND z_vcpkg_tiff_find_options "REQUIRED") -endif() -if("QUIET" IN_LIST ARGS) - list(APPEND z_vcpkg_tiff_find_options "QUIET") -endif() - -_find_package(${ARGS}) - -if(TIFF_FOUND AND "@VCPKG_LIBRARY_LINKAGE@" STREQUAL "static") - include(SelectLibraryConfigurations) - set(z_vcpkg_tiff_link_libraries "") - set(z_vcpkg_tiff_libraries "") - if("@webp@") - find_package(WebP CONFIG ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:WebP::WebP>") - list(APPEND z_vcpkg_tiff_libraries ${WebP_LIBRARIES}) - endif() - if("@lzma@") - find_package(LibLZMA ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:LibLZMA::LibLZMA>") - list(APPEND z_vcpkg_tiff_libraries ${LIBLZMA_LIBRARIES}) - endif() - if("@jpeg@") - find_package(JPEG ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:JPEG::JPEG>") - list(APPEND z_vcpkg_tiff_libraries ${JPEG_LIBRARIES}) - endif() - if("@zstd@") - find_package(zstd CONFIG ${z_vcpkg_tiff_find_options}) - set(z_vcpkg_tiff_zstd_target_property "IMPORTED_LOCATION_") - if(TARGET zstd::libzstd_shared) - set(z_vcpkg_tiff_zstd "\$<LINK_ONLY:zstd::libzstd_shared>") - set(z_vcpkg_tiff_zstd_target zstd::libzstd_shared) - if(WIN32) - set(z_vcpkg_tiff_zstd_target_property "IMPORTED_IMPLIB_") - endif() - else() - set(z_vcpkg_tiff_zstd "\$<LINK_ONLY:zstd::libzstd_static>") - set(z_vcpkg_tiff_zstd_target zstd::libzstd_static) - endif() - get_target_property(z_vcpkg_tiff_zstd_configs "${z_vcpkg_tiff_zstd_target}" IMPORTED_CONFIGURATIONS) - foreach(z_vcpkg_config IN LISTS z_vcpkg_tiff_zstd_configs) - get_target_property(ZSTD_LIBRARY_${z_vcpkg_config} "${z_vcpkg_tiff_zstd_target}" "${z_vcpkg_tiff_zstd_target_property}${z_vcpkg_config}") - endforeach() - select_library_configurations(ZSTD) - if(NOT TARGET ZSTD::ZSTD) - add_library(ZSTD::ZSTD INTERFACE IMPORTED) - set_property(TARGET ZSTD::ZSTD APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${z_vcpkg_tiff_zstd}) - endif() - list(APPEND z_vcpkg_tiff_link_libraries ${z_vcpkg_tiff_zstd}) - list(APPEND z_vcpkg_tiff_libraries ${ZSTD_LIBRARIES}) - unset(z_vcpkg_tiff_zstd) - unset(z_vcpkg_tiff_zstd_configs) - unset(z_vcpkg_config) - unset(z_vcpkg_tiff_zstd_target) - endif() - if("@libdeflate@") - find_package(libdeflate ${z_vcpkg_tiff_find_options}) - set(z_vcpkg_property "IMPORTED_LOCATION_") - if(TARGET libdeflate::libdeflate_shared) - set(z_vcpkg_libdeflate_target libdeflate::libdeflate_shared) - if(WIN32) - set(z_vcpkg_property "IMPORTED_IMPLIB_") - endif() - else() - set(z_vcpkg_libdeflate_target libdeflate::libdeflate_static) - endif() - get_target_property(z_vcpkg_libdeflate_configs "${z_vcpkg_libdeflate_target}" IMPORTED_CONFIGURATIONS) - foreach(z_vcpkg_config IN LISTS z_vcpkg_libdeflate_configs) - get_target_property(Z_VCPKG_DEFLATE_LIBRARY_${z_vcpkg_config} "${z_vcpkg_libdeflate_target}" "${z_vcpkg_property}${z_vcpkg_config}") - endforeach() - select_library_configurations(Z_VCPKG_DEFLATE) - list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:${z_vcpkg_libdeflate_target}>") - list(APPEND z_vcpkg_tiff_libraries ${Z_VCPKG_DEFLATE_LIBRARIES}) - unset(z_vcpkg_config) - unset(z_vcpkg_libdeflate_configs) - unset(z_vcpkg_libdeflate_target) - unset(z_vcpkg_property) - unset(Z_VCPKG_DEFLATE_FOUND) - endif() - if("@zlib@") - find_package(ZLIB ${z_vcpkg_tiff_find_options}) - list(APPEND z_vcpkg_tiff_link_libraries "\$<LINK_ONLY:ZLIB::ZLIB>") - list(APPEND z_vcpkg_tiff_libraries ${ZLIB_LIBRARIES}) - endif() - if(UNIX) - list(APPEND z_vcpkg_tiff_link_libraries m) - list(APPEND z_vcpkg_tiff_libraries m) - endif() - - if(TARGET TIFF::TIFF) - set_property(TARGET TIFF::TIFF APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${z_vcpkg_tiff_link_libraries}) - endif() - list(APPEND TIFF_LIBRARIES ${z_vcpkg_tiff_libraries}) - unset(z_vcpkg_tiff_link_libraries) - unset(z_vcpkg_tiff_libraries) -endif() -unset(z_vcpkg_tiff_find_options) -cmake_policy(POP) diff --git a/dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg.json b/dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg.json deleted file mode 100644 index 9b36e1a8..00000000 --- a/dependencies/vcpkg_overlay_ports_mac/tiff/vcpkg.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "name": "tiff", - "version": "4.6.0", - "port-version": 2, - "description": "A library that supports the manipulation of TIFF image files", - "homepage": "https://libtiff.gitlab.io/libtiff/", - "license": "libtiff", - "dependencies": [ - { - "name": "vcpkg-cmake", - "host": true - }, - { - "name": "vcpkg-cmake-config", - "host": true - } - ], - "default-features": [ - "jpeg", - "zip" - ], - "features": { - "cxx": { - "description": "Build C++ libtiffxx library" - }, - "jpeg": { - "description": "Support JPEG compression in TIFF image files", - "dependencies": [ - "libjpeg-turbo" - ] - }, - "libdeflate": { - "description": "Use libdeflate for faster ZIP support", - "dependencies": [ - "libdeflate", - { - "name": "tiff", - "default-features": false, - "features": [ - "zip" - ] - } - ] - }, - "tools": { - "description": "Build tools" - }, - "webp": { - "description": "Support WEBP compression in TIFF image files", - "dependencies": [ - "libwebp" - ] - }, - "zip": { - "description": "Support ZIP/deflate compression in TIFF image files", - "dependencies": [ - "zlib" - ] - }, - "zstd": { - "description": "Support ZSTD compression in TIFF image files", - "dependencies": [ - "zstd" - ] - } - } -} diff --git a/vcpkg.json b/vcpkg.json index b27a7095..0a46e32e 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,7 +1,7 @@ { "name": "cemu", "version-string": "1.0", - "builtin-baseline": "cbf4a6641528cee6f172328984576f51698de726", + "builtin-baseline": "a4275b7eee79fb24ec2e135481ef5fce8b41c339", "dependencies": [ "pugixml", "zlib", @@ -44,6 +44,22 @@ "default-features": false, "features": [ "openssl" ] }, + { + "name": "dbus", + "default-features": false, + "platform": "linux" + }, + { + "name": "tiff", + "default-features": false, + "features": ["jpeg", "zip"] + }, "libusb" + ], + "overrides": [ + { + "name": "sdl2", + "version": "2.30.3" + } ] } From 1672f969bbc4a683e4a852aa2e145c1e6f9f68e6 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 9 Jun 2024 17:38:59 +0200 Subject: [PATCH 133/299] Latte: Add support for vertex format used by Rabbids Land --- .../LatteDecompilerEmitGLSLAttrDecoder.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLAttrDecoder.cpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLAttrDecoder.cpp index ea2b9491..76d76322 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLAttrDecoder.cpp +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSLAttrDecoder.cpp @@ -241,6 +241,16 @@ void LatteDecompiler_emitAttributeDecodeGLSL(LatteDecompilerShader* shaderContex src->add("attrDecoder.z = floatBitsToUint(max(float(int(attrDecoder.z))/32767.0,-1.0));" _CRLF); src->add("attrDecoder.w = floatBitsToUint(max(float(int(attrDecoder.w))/32767.0,-1.0));" _CRLF); } + else if( attrib->format == FMT_16_16_16_16 && attrib->nfa == 2 && attrib->isSigned == 1 ) + { + // seen in Rabbids Land + _readLittleEndianAttributeU16x4(shaderContext, src, attributeInputIndex); + src->add("if( (attrDecoder.x&0x8000) != 0 ) attrDecoder.x |= 0xFFFF0000;" _CRLF); + src->add("if( (attrDecoder.y&0x8000) != 0 ) attrDecoder.y |= 0xFFFF0000;" _CRLF); + src->add("if( (attrDecoder.z&0x8000) != 0 ) attrDecoder.z |= 0xFFFF0000;" _CRLF); + src->add("if( (attrDecoder.w&0x8000) != 0 ) attrDecoder.w |= 0xFFFF0000;" _CRLF); + src->add("attrDecoder.xyzw = floatBitsToUint(vec4(ivec4(attrDecoder)));" _CRLF); + } else if (attrib->format == FMT_16_16_16_16_FLOAT && attrib->nfa == 2) { // seen in Giana Sisters: Twisted Dreams @@ -496,3 +506,5 @@ void LatteDecompiler_emitAttributeDecodeGLSL(LatteDecompilerShader* shaderContex cemu_assert_debug(false); } } + + From d4c2c3d2098616b3596b743f33fcc37629282ec0 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Tue, 25 Jun 2024 15:50:06 +0200 Subject: [PATCH 134/299] nsyskbd: Stub KBDGetKey Fixes MSX VC games freezing on boot --- src/Cafe/OS/libs/nsyskbd/nsyskbd.cpp | 35 ++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/Cafe/OS/libs/nsyskbd/nsyskbd.cpp b/src/Cafe/OS/libs/nsyskbd/nsyskbd.cpp index 72cb9bd0..f1571cc0 100644 --- a/src/Cafe/OS/libs/nsyskbd/nsyskbd.cpp +++ b/src/Cafe/OS/libs/nsyskbd/nsyskbd.cpp @@ -3,6 +3,11 @@ namespace nsyskbd { + bool IsValidChannel(uint32 channel) + { + return channel >= 0 && channel < 4; + } + uint32 KBDGetChannelStatus(uint32 channel, uint32be* status) { static bool loggedError = false; @@ -16,8 +21,38 @@ namespace nsyskbd return 0; } +#pragma pack(push, 1) + struct KeyState + { + uint8be channel; + uint8be ukn1; + uint8be _padding[2]; + uint32be ukn4; + uint32be ukn8; + uint16be uknC; + }; +#pragma pack(pop) + static_assert(sizeof(KeyState) == 0xE); // actual size might be padded to 0x10? + + uint32 KBDGetKey(uint32 channel, KeyState* keyState) + { + // used by MSX VC + if(!IsValidChannel(channel) || !keyState) + { + cemuLog_log(LogType::APIErrors, "KBDGetKey(): Invalid parameter"); + return 0; + } + keyState->channel = channel; + keyState->ukn1 = 0; + keyState->ukn4 = 0; + keyState->ukn8 = 0; + keyState->uknC = 0; + return 0; + } + void nsyskbd_load() { cafeExportRegister("nsyskbd", KBDGetChannelStatus, LogType::Placeholder); + cafeExportRegister("nsyskbd", KBDGetKey, LogType::Placeholder); } } From f3d20832c191f90fe4ad6f9b64a3ff21eb477b02 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Tue, 25 Jun 2024 19:28:21 +0200 Subject: [PATCH 135/299] Avoid an unhandled exception when mlc path is invalid --- src/gui/CemuApp.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/CemuApp.cpp b/src/gui/CemuApp.cpp index 505a09c6..86d81e43 100644 --- a/src/gui/CemuApp.cpp +++ b/src/gui/CemuApp.cpp @@ -266,10 +266,10 @@ std::vector<const wxLanguageInfo*> CemuApp::GetAvailableTranslationLanguages(wxT void CemuApp::CreateDefaultFiles(bool first_start) { + std::error_code ec; fs::path mlc = ActiveSettings::GetMlcPath(); - // check for mlc01 folder missing if custom path has been set - if (!fs::exists(mlc) && !first_start) + if (!fs::exists(mlc, ec) && !first_start) { const wxString message = formatWxString(_("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?"), _pathToUtf8(mlc)); From 93b58ae6f7315bf17126d49314e0132eeb356ef9 Mon Sep 17 00:00:00 2001 From: Joshua de Reeper <joshua@dereeper.co.nz> Date: Thu, 27 Jun 2024 23:55:20 +0100 Subject: [PATCH 136/299] nsyshid: Add infrastructure and support for emulating Skylander Portal (#971) --- src/Cafe/CMakeLists.txt | 4 + .../OS/libs/nsyshid/AttachDefaultBackends.cpp | 9 + src/Cafe/OS/libs/nsyshid/Backend.h | 57 +- src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp | 29 + src/Cafe/OS/libs/nsyshid/BackendEmulated.h | 16 + src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp | 36 +- src/Cafe/OS/libs/nsyshid/BackendLibusb.h | 6 +- .../OS/libs/nsyshid/BackendWindowsHID.cpp | 34 +- src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h | 6 +- src/Cafe/OS/libs/nsyshid/Skylander.cpp | 939 ++++++++++++++++++ src/Cafe/OS/libs/nsyshid/Skylander.h | 98 ++ src/Cafe/OS/libs/nsyshid/nsyshid.cpp | 41 +- src/config/CemuConfig.cpp | 8 + src/config/CemuConfig.h | 6 + src/gui/CMakeLists.txt | 2 + .../EmulatedUSBDeviceFrame.cpp | 354 +++++++ .../EmulatedUSBDeviceFrame.h | 42 + src/gui/MainWindow.cpp | 27 + src/gui/MainWindow.h | 2 + 19 files changed, 1658 insertions(+), 58 deletions(-) create mode 100644 src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp create mode 100644 src/Cafe/OS/libs/nsyshid/BackendEmulated.h create mode 100644 src/Cafe/OS/libs/nsyshid/Skylander.cpp create mode 100644 src/Cafe/OS/libs/nsyshid/Skylander.h create mode 100644 src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp create mode 100644 src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index b5090dcf..1583bdd7 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -457,10 +457,14 @@ add_library(CemuCafe OS/libs/nsyshid/AttachDefaultBackends.cpp OS/libs/nsyshid/Whitelist.cpp OS/libs/nsyshid/Whitelist.h + OS/libs/nsyshid/BackendEmulated.cpp + OS/libs/nsyshid/BackendEmulated.h OS/libs/nsyshid/BackendLibusb.cpp OS/libs/nsyshid/BackendLibusb.h OS/libs/nsyshid/BackendWindowsHID.cpp OS/libs/nsyshid/BackendWindowsHID.h + OS/libs/nsyshid/Skylander.cpp + OS/libs/nsyshid/Skylander.h OS/libs/nsyskbd/nsyskbd.cpp OS/libs/nsyskbd/nsyskbd.h OS/libs/nsysnet/nsysnet.cpp diff --git a/src/Cafe/OS/libs/nsyshid/AttachDefaultBackends.cpp b/src/Cafe/OS/libs/nsyshid/AttachDefaultBackends.cpp index 6e6cb123..fc8e496c 100644 --- a/src/Cafe/OS/libs/nsyshid/AttachDefaultBackends.cpp +++ b/src/Cafe/OS/libs/nsyshid/AttachDefaultBackends.cpp @@ -1,5 +1,6 @@ #include "nsyshid.h" #include "Backend.h" +#include "BackendEmulated.h" #if NSYSHID_ENABLE_BACKEND_LIBUSB @@ -37,5 +38,13 @@ namespace nsyshid::backend } } #endif // NSYSHID_ENABLE_BACKEND_WINDOWS_HID + // add emulated backend + { + auto backendEmulated = std::make_shared<backend::emulated::BackendEmulated>(); + if (backendEmulated->IsInitialisedOk()) + { + AttachBackend(backendEmulated); + } + } } } // namespace nsyshid::backend diff --git a/src/Cafe/OS/libs/nsyshid/Backend.h b/src/Cafe/OS/libs/nsyshid/Backend.h index 641104f5..03232736 100644 --- a/src/Cafe/OS/libs/nsyshid/Backend.h +++ b/src/Cafe/OS/libs/nsyshid/Backend.h @@ -23,6 +23,55 @@ namespace nsyshid /* +0x12 */ uint16be maxPacketSizeTX; } HID_t; + struct TransferCommand + { + uint8* data; + sint32 length; + + TransferCommand(uint8* data, sint32 length) + : data(data), length(length) + { + } + virtual ~TransferCommand() = default; + }; + + struct ReadMessage final : TransferCommand + { + sint32 bytesRead; + + ReadMessage(uint8* data, sint32 length, sint32 bytesRead) + : bytesRead(bytesRead), TransferCommand(data, length) + { + } + using TransferCommand::TransferCommand; + }; + + struct WriteMessage final : TransferCommand + { + sint32 bytesWritten; + + WriteMessage(uint8* data, sint32 length, sint32 bytesWritten) + : bytesWritten(bytesWritten), TransferCommand(data, length) + { + } + using TransferCommand::TransferCommand; + }; + + struct ReportMessage final : TransferCommand + { + uint8* reportData; + sint32 length; + uint8* originalData; + sint32 originalLength; + + ReportMessage(uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength) + : reportData(reportData), length(length), originalData(originalData), + originalLength(originalLength), TransferCommand(reportData, length) + { + } + using TransferCommand::TransferCommand; + }; + static_assert(offsetof(HID_t, vendorId) == 0x8, ""); static_assert(offsetof(HID_t, productId) == 0xA, ""); static_assert(offsetof(HID_t, ifIndex) == 0xC, ""); @@ -69,7 +118,7 @@ namespace nsyshid ErrorTimeout, }; - virtual ReadResult Read(uint8* data, sint32 length, sint32& bytesRead) = 0; + virtual ReadResult Read(ReadMessage* message) = 0; enum class WriteResult { @@ -78,7 +127,7 @@ namespace nsyshid ErrorTimeout, }; - virtual WriteResult Write(uint8* data, sint32 length, sint32& bytesWritten) = 0; + virtual WriteResult Write(WriteMessage* message) = 0; virtual bool GetDescriptor(uint8 descType, uint8 descIndex, @@ -88,7 +137,7 @@ namespace nsyshid virtual bool SetProtocol(uint32 ifIndef, uint32 protocol) = 0; - virtual bool SetReport(uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength) = 0; + virtual bool SetReport(ReportMessage* message) = 0; }; class Backend { @@ -121,6 +170,8 @@ namespace nsyshid std::shared_ptr<Device> FindDevice(std::function<bool(const std::shared_ptr<Device>&)> isWantedDevice); + bool FindDeviceById(uint16 vendorId, uint16 productId); + bool IsDeviceWhitelisted(uint16 vendorId, uint16 productId); // called from OnAttach() - attach devices that your backend can see here diff --git a/src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp b/src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp new file mode 100644 index 00000000..11a299ed --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp @@ -0,0 +1,29 @@ +#include "BackendEmulated.h" +#include "Skylander.h" +#include "config/CemuConfig.h" + +namespace nsyshid::backend::emulated +{ + BackendEmulated::BackendEmulated() + { + cemuLog_logDebug(LogType::Force, "nsyshid::BackendEmulated: emulated backend initialised"); + } + + BackendEmulated::~BackendEmulated() = default; + + bool BackendEmulated::IsInitialisedOk() + { + return true; + } + + void BackendEmulated::AttachVisibleDevices() + { + if (GetConfig().emulated_usb_devices.emulate_skylander_portal && !FindDeviceById(0x1430, 0x0150)) + { + cemuLog_logDebug(LogType::Force, "Attaching Emulated Portal"); + // Add Skylander Portal + auto device = std::make_shared<SkylanderPortalDevice>(); + AttachDevice(device); + } + } +} // namespace nsyshid::backend::emulated \ No newline at end of file diff --git a/src/Cafe/OS/libs/nsyshid/BackendEmulated.h b/src/Cafe/OS/libs/nsyshid/BackendEmulated.h new file mode 100644 index 00000000..cf38a8b7 --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/BackendEmulated.h @@ -0,0 +1,16 @@ +#include "nsyshid.h" +#include "Backend.h" + +namespace nsyshid::backend::emulated +{ + class BackendEmulated : public nsyshid::Backend { + public: + BackendEmulated(); + ~BackendEmulated(); + + bool IsInitialisedOk() override; + + protected: + void AttachVisibleDevices() override; + }; +} // namespace nsyshid::backend::emulated diff --git a/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp b/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp index 4f88b7ed..6701d780 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp +++ b/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp @@ -241,11 +241,6 @@ namespace nsyshid::backend::libusb ret); return nullptr; } - if (desc.idVendor == 0x0e6f && desc.idProduct == 0x0241) - { - cemuLog_logDebug(LogType::Force, - "nsyshid::BackendLibusb::CheckAndCreateDevice(): lego dimensions portal detected"); - } auto device = std::make_shared<DeviceLibusb>(m_ctx, desc.idVendor, desc.idProduct, @@ -471,7 +466,7 @@ namespace nsyshid::backend::libusb return m_libusbHandle != nullptr && m_handleInUseCounter >= 0; } - Device::ReadResult DeviceLibusb::Read(uint8* data, sint32 length, sint32& bytesRead) + Device::ReadResult DeviceLibusb::Read(ReadMessage* message) { auto handleLock = AquireHandleLock(); if (!handleLock->IsValid()) @@ -488,8 +483,8 @@ namespace nsyshid::backend::libusb { ret = libusb_bulk_transfer(handleLock->GetHandle(), this->m_libusbEndpointIn, - data, - length, + message->data, + message->length, &actualLength, timeout); } @@ -500,8 +495,8 @@ namespace nsyshid::backend::libusb // success cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::read(): read {} of {} bytes", actualLength, - length); - bytesRead = actualLength; + message->length); + message->bytesRead = actualLength; return ReadResult::Success; } cemuLog_logDebug(LogType::Force, @@ -510,7 +505,7 @@ namespace nsyshid::backend::libusb return ReadResult::Error; } - Device::WriteResult DeviceLibusb::Write(uint8* data, sint32 length, sint32& bytesWritten) + Device::WriteResult DeviceLibusb::Write(WriteMessage* message) { auto handleLock = AquireHandleLock(); if (!handleLock->IsValid()) @@ -520,23 +515,23 @@ namespace nsyshid::backend::libusb return WriteResult::Error; } - bytesWritten = 0; + message->bytesWritten = 0; int actualLength = 0; int ret = libusb_bulk_transfer(handleLock->GetHandle(), this->m_libusbEndpointOut, - data, - length, + message->data, + message->length, &actualLength, 0); if (ret == 0) { // success - bytesWritten = actualLength; + message->bytesWritten = actualLength; cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::write(): wrote {} of {} bytes", - bytesWritten, - length); + message->bytesWritten, + message->length); return WriteResult::Success; } cemuLog_logDebug(LogType::Force, @@ -713,8 +708,7 @@ namespace nsyshid::backend::libusb return true; } - bool DeviceLibusb::SetReport(uint8* reportData, sint32 length, uint8* originalData, - sint32 originalLength) + bool DeviceLibusb::SetReport(ReportMessage* message) { auto handleLock = AquireHandleLock(); if (!handleLock->IsValid()) @@ -731,8 +725,8 @@ namespace nsyshid::backend::libusb bRequest, wValue, wIndex, - reportData, - length, + message->reportData, + message->length, timeout); #endif diff --git a/src/Cafe/OS/libs/nsyshid/BackendLibusb.h b/src/Cafe/OS/libs/nsyshid/BackendLibusb.h index 216be6ce..a8122aff 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendLibusb.h +++ b/src/Cafe/OS/libs/nsyshid/BackendLibusb.h @@ -63,9 +63,9 @@ namespace nsyshid::backend::libusb bool IsOpened() override; - ReadResult Read(uint8* data, sint32 length, sint32& bytesRead) override; + ReadResult Read(ReadMessage* message) override; - WriteResult Write(uint8* data, sint32 length, sint32& bytesWritten) override; + WriteResult Write(WriteMessage* message) override; bool GetDescriptor(uint8 descType, uint8 descIndex, @@ -75,7 +75,7 @@ namespace nsyshid::backend::libusb bool SetProtocol(uint32 ifIndex, uint32 protocol) override; - bool SetReport(uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength) override; + bool SetReport(ReportMessage* message) override; uint8 m_libusbBusNumber; uint8 m_libusbDeviceAddress; diff --git a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp index 23da5798..3cfba26a 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp +++ b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp @@ -196,20 +196,20 @@ namespace nsyshid::backend::windows return m_hFile != INVALID_HANDLE_VALUE; } - Device::ReadResult DeviceWindowsHID::Read(uint8* data, sint32 length, sint32& bytesRead) + Device::ReadResult DeviceWindowsHID::Read(ReadMessage* message) { - bytesRead = 0; + message->bytesRead = 0; DWORD bt; OVERLAPPED ovlp = {0}; ovlp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - uint8* tempBuffer = (uint8*)malloc(length + 1); + uint8* tempBuffer = (uint8*)malloc(message->length + 1); sint32 transferLength = 0; // minus report byte - _debugPrintHex("HID_READ_BEFORE", data, length); + _debugPrintHex("HID_READ_BEFORE", message->data, message->length); - cemuLog_logDebug(LogType::Force, "HidRead Begin (Length 0x{:08x})", length); - BOOL readResult = ReadFile(this->m_hFile, tempBuffer, length + 1, &bt, &ovlp); + cemuLog_logDebug(LogType::Force, "HidRead Begin (Length 0x{:08x})", message->length); + BOOL readResult = ReadFile(this->m_hFile, tempBuffer, message->length + 1, &bt, &ovlp); if (readResult != FALSE) { // sometimes we get the result immediately @@ -247,7 +247,7 @@ namespace nsyshid::backend::windows ReadResult result = ReadResult::Success; if (bt != 0) { - memcpy(data, tempBuffer + 1, transferLength); + memcpy(message->data, tempBuffer + 1, transferLength); sint32 hidReadLength = transferLength; char debugOutput[1024] = {0}; @@ -257,7 +257,7 @@ namespace nsyshid::backend::windows } cemuLog_logDebug(LogType::Force, "HIDRead data: {}", debugOutput); - bytesRead = transferLength; + message->bytesRead = transferLength; result = ReadResult::Success; } else @@ -270,19 +270,19 @@ namespace nsyshid::backend::windows return result; } - Device::WriteResult DeviceWindowsHID::Write(uint8* data, sint32 length, sint32& bytesWritten) + Device::WriteResult DeviceWindowsHID::Write(WriteMessage* message) { - bytesWritten = 0; + message->bytesWritten = 0; DWORD bt; OVERLAPPED ovlp = {0}; ovlp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - uint8* tempBuffer = (uint8*)malloc(length + 1); - memcpy(tempBuffer + 1, data, length); + uint8* tempBuffer = (uint8*)malloc(message->length + 1); + memcpy(tempBuffer + 1, message->data, message->length); tempBuffer[0] = 0; // report byte? - cemuLog_logDebug(LogType::Force, "HidWrite Begin (Length 0x{:08x})", length); - BOOL writeResult = WriteFile(this->m_hFile, tempBuffer, length + 1, &bt, &ovlp); + cemuLog_logDebug(LogType::Force, "HidWrite Begin (Length 0x{:08x})", message->length); + BOOL writeResult = WriteFile(this->m_hFile, tempBuffer, message->length + 1, &bt, &ovlp); if (writeResult != FALSE) { // sometimes we get the result immediately @@ -314,7 +314,7 @@ namespace nsyshid::backend::windows if (bt != 0) { - bytesWritten = length; + message->bytesWritten = message->length; return WriteResult::Success; } return WriteResult::Error; @@ -407,12 +407,12 @@ namespace nsyshid::backend::windows return true; } - bool DeviceWindowsHID::SetReport(uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength) + bool DeviceWindowsHID::SetReport(ReportMessage* message) { sint32 retryCount = 0; while (true) { - BOOL r = HidD_SetOutputReport(this->m_hFile, reportData, length); + BOOL r = HidD_SetOutputReport(this->m_hFile, message->reportData, message->length); if (r != FALSE) break; Sleep(20); // retry diff --git a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h index 049b33e4..84fe7bda 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h +++ b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h @@ -41,15 +41,15 @@ namespace nsyshid::backend::windows bool IsOpened() override; - ReadResult Read(uint8* data, sint32 length, sint32& bytesRead) override; + ReadResult Read(ReadMessage* message) override; - WriteResult Write(uint8* data, sint32 length, sint32& bytesWritten) override; + WriteResult Write(WriteMessage* message) override; bool GetDescriptor(uint8 descType, uint8 descIndex, uint8 lang, uint8* output, uint32 outputMaxLength) override; bool SetProtocol(uint32 ifIndef, uint32 protocol) override; - bool SetReport(uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength) override; + bool SetReport(ReportMessage* message) override; private: wchar_t* m_devicePath; diff --git a/src/Cafe/OS/libs/nsyshid/Skylander.cpp b/src/Cafe/OS/libs/nsyshid/Skylander.cpp new file mode 100644 index 00000000..3123d14d --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/Skylander.cpp @@ -0,0 +1,939 @@ +#include "Skylander.h" + +#include "nsyshid.h" +#include "Backend.h" + +#include "Common/FileStream.h" + +namespace nsyshid +{ + SkylanderUSB g_skyportal; + + const std::map<const std::pair<const uint16, const uint16>, const std::string> + listSkylanders = { + {{0, 0x0000}, "Whirlwind"}, + {{0, 0x1801}, "Series 2 Whirlwind"}, + {{0, 0x1C02}, "Polar Whirlwind"}, + {{0, 0x2805}, "Horn Blast Whirlwind"}, + {{0, 0x3810}, "Eon's Elite Whirlwind"}, + {{1, 0x0000}, "Sonic Boom"}, + {{1, 0x1801}, "Series 2 Sonic Boom"}, + {{2, 0x0000}, "Warnado"}, + {{2, 0x2206}, "LightCore Warnado"}, + {{3, 0x0000}, "Lightning Rod"}, + {{3, 0x1801}, "Series 2 Lightning Rod"}, + {{4, 0x0000}, "Bash"}, + {{4, 0x1801}, "Series 2 Bash"}, + {{5, 0x0000}, "Terrafin"}, + {{5, 0x1801}, "Series 2 Terrafin"}, + {{5, 0x2805}, "Knockout Terrafin"}, + {{5, 0x3810}, "Eon's Elite Terrafin"}, + {{6, 0x0000}, "Dino Rang"}, + {{6, 0x4810}, "Eon's Elite Dino Rang"}, + {{7, 0x0000}, "Prism Break"}, + {{7, 0x1801}, "Series 2 Prism Break"}, + {{7, 0x2805}, "Hyper Beam Prism Break"}, + {{7, 0x1206}, "LightCore Prism Break"}, + {{8, 0x0000}, "Sunburn"}, + {{9, 0x0000}, "Eruptor"}, + {{9, 0x1801}, "Series 2 Eruptor"}, + {{9, 0x2C02}, "Volcanic Eruptor"}, + {{9, 0x2805}, "Lava Barf Eruptor"}, + {{9, 0x1206}, "LightCore Eruptor"}, + {{9, 0x3810}, "Eon's Elite Eruptor"}, + {{10, 0x0000}, "Ignitor"}, + {{10, 0x1801}, "Series 2 Ignitor"}, + {{10, 0x1C03}, "Legendary Ignitor"}, + {{11, 0x0000}, "Flameslinger"}, + {{11, 0x1801}, "Series 2 Flameslinger"}, + {{12, 0x0000}, "Zap"}, + {{12, 0x1801}, "Series 2 Zap"}, + {{13, 0x0000}, "Wham Shell"}, + {{13, 0x2206}, "LightCore Wham Shell"}, + {{14, 0x0000}, "Gill Grunt"}, + {{14, 0x1801}, "Series 2 Gill Grunt"}, + {{14, 0x2805}, "Anchors Away Gill Grunt"}, + {{14, 0x3805}, "Tidal Wave Gill Grunt"}, + {{14, 0x3810}, "Eon's Elite Gill Grunt"}, + {{15, 0x0000}, "Slam Bam"}, + {{15, 0x1801}, "Series 2 Slam Bam"}, + {{15, 0x1C03}, "Legendary Slam Bam"}, + {{15, 0x4810}, "Eon's Elite Slam Bam"}, + {{16, 0x0000}, "Spyro"}, + {{16, 0x1801}, "Series 2 Spyro"}, + {{16, 0x2C02}, "Dark Mega Ram Spyro"}, + {{16, 0x2805}, "Mega Ram Spyro"}, + {{16, 0x3810}, "Eon's Elite Spyro"}, + {{17, 0x0000}, "Voodood"}, + {{17, 0x4810}, "Eon's Elite Voodood"}, + {{18, 0x0000}, "Double Trouble"}, + {{18, 0x1801}, "Series 2 Double Trouble"}, + {{18, 0x1C02}, "Royal Double Trouble"}, + {{19, 0x0000}, "Trigger Happy"}, + {{19, 0x1801}, "Series 2 Trigger Happy"}, + {{19, 0x2C02}, "Springtime Trigger Happy"}, + {{19, 0x2805}, "Big Bang Trigger Happy"}, + {{19, 0x3810}, "Eon's Elite Trigger Happy"}, + {{20, 0x0000}, "Drobot"}, + {{20, 0x1801}, "Series 2 Drobot"}, + {{20, 0x1206}, "LightCore Drobot"}, + {{21, 0x0000}, "Drill Seargeant"}, + {{21, 0x1801}, "Series 2 Drill Seargeant"}, + {{22, 0x0000}, "Boomer"}, + {{22, 0x4810}, "Eon's Elite Boomer"}, + {{23, 0x0000}, "Wrecking Ball"}, + {{23, 0x1801}, "Series 2 Wrecking Ball"}, + {{24, 0x0000}, "Camo"}, + {{24, 0x2805}, "Thorn Horn Camo"}, + {{25, 0x0000}, "Zook"}, + {{25, 0x1801}, "Series 2 Zook"}, + {{25, 0x4810}, "Eon's Elite Zook"}, + {{26, 0x0000}, "Stealth Elf"}, + {{26, 0x1801}, "Series 2 Stealth Elf"}, + {{26, 0x2C02}, "Dark Stealth Elf"}, + {{26, 0x1C03}, "Legendary Stealth Elf"}, + {{26, 0x2805}, "Ninja Stealth Elf"}, + {{26, 0x3810}, "Eon's Elite Stealth Elf"}, + {{27, 0x0000}, "Stump Smash"}, + {{27, 0x1801}, "Series 2 Stump Smash"}, + {{28, 0x0000}, "Dark Spyro"}, + {{29, 0x0000}, "Hex"}, + {{29, 0x1801}, "Series 2 Hex"}, + {{29, 0x1206}, "LightCore Hex"}, + {{30, 0x0000}, "Chop Chop"}, + {{30, 0x1801}, "Series 2 Chop Chop"}, + {{30, 0x2805}, "Twin Blade Chop Chop"}, + {{30, 0x3810}, "Eon's Elite Chop Chop"}, + {{31, 0x0000}, "Ghost Roaster"}, + {{31, 0x4810}, "Eon's Elite Ghost Roaster"}, + {{32, 0x0000}, "Cynder"}, + {{32, 0x1801}, "Series 2 Cynder"}, + {{32, 0x2805}, "Phantom Cynder"}, + {{100, 0x0000}, "Jet Vac"}, + {{100, 0x1403}, "Legendary Jet Vac"}, + {{100, 0x2805}, "Turbo Jet Vac"}, + {{100, 0x3805}, "Full Blast Jet Vac"}, + {{100, 0x1206}, "LightCore Jet Vac"}, + {{101, 0x0000}, "Swarm"}, + {{102, 0x0000}, "Crusher"}, + {{102, 0x1602}, "Granite Crusher"}, + {{103, 0x0000}, "Flashwing"}, + {{103, 0x1402}, "Jade Flash Wing"}, + {{103, 0x2206}, "LightCore Flashwing"}, + {{104, 0x0000}, "Hot Head"}, + {{105, 0x0000}, "Hot Dog"}, + {{105, 0x1402}, "Molten Hot Dog"}, + {{105, 0x2805}, "Fire Bone Hot Dog"}, + {{106, 0x0000}, "Chill"}, + {{106, 0x1603}, "Legendary Chill"}, + {{106, 0x2805}, "Blizzard Chill"}, + {{106, 0x1206}, "LightCore Chill"}, + {{107, 0x0000}, "Thumpback"}, + {{108, 0x0000}, "Pop Fizz"}, + {{108, 0x1402}, "Punch Pop Fizz"}, + {{108, 0x3C02}, "Love Potion Pop Fizz"}, + {{108, 0x2805}, "Super Gulp Pop Fizz"}, + {{108, 0x3805}, "Fizzy Frenzy Pop Fizz"}, + {{108, 0x1206}, "LightCore Pop Fizz"}, + {{109, 0x0000}, "Ninjini"}, + {{109, 0x1602}, "Scarlet Ninjini"}, + {{110, 0x0000}, "Bouncer"}, + {{110, 0x1603}, "Legendary Bouncer"}, + {{111, 0x0000}, "Sprocket"}, + {{111, 0x2805}, "Heavy Duty Sprocket"}, + {{112, 0x0000}, "Tree Rex"}, + {{112, 0x1602}, "Gnarly Tree Rex"}, + {{113, 0x0000}, "Shroomboom"}, + {{113, 0x3805}, "Sure Shot Shroomboom"}, + {{113, 0x1206}, "LightCore Shroomboom"}, + {{114, 0x0000}, "Eye Brawl"}, + {{115, 0x0000}, "Fright Rider"}, + {{200, 0x0000}, "Anvil Rain"}, + {{201, 0x0000}, "Hidden Treasure"}, + {{201, 0x2000}, "Platinum Hidden Treasure"}, + {{202, 0x0000}, "Healing Elixir"}, + {{203, 0x0000}, "Ghost Pirate Swords"}, + {{204, 0x0000}, "Time Twist Hourglass"}, + {{205, 0x0000}, "Sky Iron Shield"}, + {{206, 0x0000}, "Winged Boots"}, + {{207, 0x0000}, "Sparx the Dragonfly"}, + {{208, 0x0000}, "Dragonfire Cannon"}, + {{208, 0x1602}, "Golden Dragonfire Cannon"}, + {{209, 0x0000}, "Scorpion Striker"}, + {{210, 0x3002}, "Biter's Bane"}, + {{210, 0x3008}, "Sorcerous Skull"}, + {{210, 0x300B}, "Axe of Illusion"}, + {{210, 0x300E}, "Arcane Hourglass"}, + {{210, 0x3012}, "Spell Slapper"}, + {{210, 0x3014}, "Rune Rocket"}, + {{211, 0x3001}, "Tidal Tiki"}, + {{211, 0x3002}, "Wet Walter"}, + {{211, 0x3006}, "Flood Flask"}, + {{211, 0x3406}, "Legendary Flood Flask"}, + {{211, 0x3007}, "Soaking Staff"}, + {{211, 0x300B}, "Aqua Axe"}, + {{211, 0x3016}, "Frost Helm"}, + {{212, 0x3003}, "Breezy Bird"}, + {{212, 0x3006}, "Drafty Decanter"}, + {{212, 0x300D}, "Tempest Timer"}, + {{212, 0x3010}, "Cloudy Cobra"}, + {{212, 0x3011}, "Storm Warning"}, + {{212, 0x3018}, "Cyclone Saber"}, + {{213, 0x3004}, "Spirit Sphere"}, + {{213, 0x3404}, "Legendary Spirit Sphere"}, + {{213, 0x3008}, "Spectral Skull"}, + {{213, 0x3408}, "Legendary Spectral Skull"}, + {{213, 0x300B}, "Haunted Hatchet"}, + {{213, 0x300C}, "Grim Gripper"}, + {{213, 0x3010}, "Spooky Snake"}, + {{213, 0x3017}, "Dream Piercer"}, + {{214, 0x3000}, "Tech Totem"}, + {{214, 0x3007}, "Automatic Angel"}, + {{214, 0x3009}, "Factory Flower"}, + {{214, 0x300C}, "Grabbing Gadget"}, + {{214, 0x3016}, "Makers Mana"}, + {{214, 0x301A}, "Topsy Techy"}, + {{215, 0x3005}, "Eternal Flame"}, + {{215, 0x3009}, "Fire Flower"}, + {{215, 0x3011}, "Scorching Stopper"}, + {{215, 0x3012}, "Searing Spinner"}, + {{215, 0x3017}, "Spark Spear"}, + {{215, 0x301B}, "Blazing Belch"}, + {{216, 0x3000}, "Banded Boulder"}, + {{216, 0x3003}, "Rock Hawk"}, + {{216, 0x300A}, "Slag Hammer"}, + {{216, 0x300E}, "Dust Of Time"}, + {{216, 0x3013}, "Spinning Sandstorm"}, + {{216, 0x301A}, "Rubble Trouble"}, + {{217, 0x3003}, "Oak Eagle"}, + {{217, 0x3005}, "Emerald Energy"}, + {{217, 0x300A}, "Weed Whacker"}, + {{217, 0x3010}, "Seed Serpent"}, + {{217, 0x3018}, "Jade Blade"}, + {{217, 0x301B}, "Shrub Shrieker"}, + {{218, 0x3000}, "Dark Dagger"}, + {{218, 0x3014}, "Shadow Spider"}, + {{218, 0x301A}, "Ghastly Grimace"}, + {{219, 0x3000}, "Shining Ship"}, + {{219, 0x300F}, "Heavenly Hawk"}, + {{219, 0x301B}, "Beam Scream"}, + {{220, 0x301E}, "Kaos Trap"}, + {{220, 0x351F}, "Ultimate Kaos Trap"}, + {{230, 0x0000}, "Hand of Fate"}, + {{230, 0x3403}, "Legendary Hand of Fate"}, + {{231, 0x0000}, "Piggy Bank"}, + {{232, 0x0000}, "Rocket Ram"}, + {{233, 0x0000}, "Tiki Speaky"}, + {{300, 0x0000}, "Dragon’s Peak"}, + {{301, 0x0000}, "Empire of Ice"}, + {{302, 0x0000}, "Pirate Seas"}, + {{303, 0x0000}, "Darklight Crypt"}, + {{304, 0x0000}, "Volcanic Vault"}, + {{305, 0x0000}, "Mirror of Mystery"}, + {{306, 0x0000}, "Nightmare Express"}, + {{307, 0x0000}, "Sunscraper Spire"}, + {{308, 0x0000}, "Midnight Museum"}, + {{404, 0x0000}, "Legendary Bash"}, + {{416, 0x0000}, "Legendary Spyro"}, + {{419, 0x0000}, "Legendary Trigger Happy"}, + {{430, 0x0000}, "Legendary Chop Chop"}, + {{450, 0x0000}, "Gusto"}, + {{451, 0x0000}, "Thunderbolt"}, + {{452, 0x0000}, "Fling Kong"}, + {{453, 0x0000}, "Blades"}, + {{453, 0x3403}, "Legendary Blades"}, + {{454, 0x0000}, "Wallop"}, + {{455, 0x0000}, "Head Rush"}, + {{455, 0x3402}, "Nitro Head Rush"}, + {{456, 0x0000}, "Fist Bump"}, + {{457, 0x0000}, "Rocky Roll"}, + {{458, 0x0000}, "Wildfire"}, + {{458, 0x3402}, "Dark Wildfire"}, + {{459, 0x0000}, "Ka Boom"}, + {{460, 0x0000}, "Trail Blazer"}, + {{461, 0x0000}, "Torch"}, + {{462, 0x3000}, "Snap Shot"}, + {{462, 0x3402}, "Dark Snap Shot"}, + {{463, 0x0000}, "Lob Star"}, + {{463, 0x3402}, "Winterfest Lob-Star"}, + {{464, 0x0000}, "Flip Wreck"}, + {{465, 0x0000}, "Echo"}, + {{466, 0x0000}, "Blastermind"}, + {{467, 0x0000}, "Enigma"}, + {{468, 0x0000}, "Deja Vu"}, + {{468, 0x3403}, "Legendary Deja Vu"}, + {{469, 0x0000}, "Cobra Candabra"}, + {{469, 0x3402}, "King Cobra Cadabra"}, + {{470, 0x0000}, "Jawbreaker"}, + {{470, 0x3403}, "Legendary Jawbreaker"}, + {{471, 0x0000}, "Gearshift"}, + {{472, 0x0000}, "Chopper"}, + {{473, 0x0000}, "Tread Head"}, + {{474, 0x0000}, "Bushwack"}, + {{474, 0x3403}, "Legendary Bushwack"}, + {{475, 0x0000}, "Tuff Luck"}, + {{476, 0x0000}, "Food Fight"}, + {{476, 0x3402}, "Dark Food Fight"}, + {{477, 0x0000}, "High Five"}, + {{478, 0x0000}, "Krypt King"}, + {{478, 0x3402}, "Nitro Krypt King"}, + {{479, 0x0000}, "Short Cut"}, + {{480, 0x0000}, "Bat Spin"}, + {{481, 0x0000}, "Funny Bone"}, + {{482, 0x0000}, "Knight Light"}, + {{483, 0x0000}, "Spotlight"}, + {{484, 0x0000}, "Knight Mare"}, + {{485, 0x0000}, "Blackout"}, + {{502, 0x0000}, "Bop"}, + {{505, 0x0000}, "Terrabite"}, + {{506, 0x0000}, "Breeze"}, + {{508, 0x0000}, "Pet Vac"}, + {{508, 0x3402}, "Power Punch Pet Vac"}, + {{507, 0x0000}, "Weeruptor"}, + {{507, 0x3402}, "Eggcellent Weeruptor"}, + {{509, 0x0000}, "Small Fry"}, + {{510, 0x0000}, "Drobit"}, + {{519, 0x0000}, "Trigger Snappy"}, + {{526, 0x0000}, "Whisper Elf"}, + {{540, 0x0000}, "Barkley"}, + {{540, 0x3402}, "Gnarly Barkley"}, + {{541, 0x0000}, "Thumpling"}, + {{514, 0x0000}, "Gill Runt"}, + {{542, 0x0000}, "Mini-Jini"}, + {{503, 0x0000}, "Spry"}, + {{504, 0x0000}, "Hijinx"}, + {{543, 0x0000}, "Eye Small"}, + {{601, 0x0000}, "King Pen"}, + {{602, 0x0000}, "Tri-Tip"}, + {{603, 0x0000}, "Chopscotch"}, + {{604, 0x0000}, "Boom Bloom"}, + {{605, 0x0000}, "Pit Boss"}, + {{606, 0x0000}, "Barbella"}, + {{607, 0x0000}, "Air Strike"}, + {{608, 0x0000}, "Ember"}, + {{609, 0x0000}, "Ambush"}, + {{610, 0x0000}, "Dr. Krankcase"}, + {{611, 0x0000}, "Hood Sickle"}, + {{612, 0x0000}, "Tae Kwon Crow"}, + {{613, 0x0000}, "Golden Queen"}, + {{614, 0x0000}, "Wolfgang"}, + {{615, 0x0000}, "Pain-Yatta"}, + {{616, 0x0000}, "Mysticat"}, + {{617, 0x0000}, "Starcast"}, + {{618, 0x0000}, "Buckshot"}, + {{619, 0x0000}, "Aurora"}, + {{620, 0x0000}, "Flare Wolf"}, + {{621, 0x0000}, "Chompy Mage"}, + {{622, 0x0000}, "Bad Juju"}, + {{623, 0x0000}, "Grave Clobber"}, + {{624, 0x0000}, "Blaster-Tron"}, + {{625, 0x0000}, "Ro-Bow"}, + {{626, 0x0000}, "Chain Reaction"}, + {{627, 0x0000}, "Kaos"}, + {{628, 0x0000}, "Wild Storm"}, + {{629, 0x0000}, "Tidepool"}, + {{630, 0x0000}, "Crash Bandicoot"}, + {{631, 0x0000}, "Dr. Neo Cortex"}, + {{1000, 0x0000}, "Boom Jet (Bottom)"}, + {{1001, 0x0000}, "Free Ranger (Bottom)"}, + {{1001, 0x2403}, "Legendary Free Ranger (Bottom)"}, + {{1002, 0x0000}, "Rubble Rouser (Bottom)"}, + {{1003, 0x0000}, "Doom Stone (Bottom)"}, + {{1004, 0x0000}, "Blast Zone (Bottom)"}, + {{1004, 0x2402}, "Dark Blast Zone (Bottom)"}, + {{1005, 0x0000}, "Fire Kraken (Bottom)"}, + {{1005, 0x2402}, "Jade Fire Kraken (Bottom)"}, + {{1006, 0x0000}, "Stink Bomb (Bottom)"}, + {{1007, 0x0000}, "Grilla Drilla (Bottom)"}, + {{1008, 0x0000}, "Hoot Loop (Bottom)"}, + {{1008, 0x2402}, "Enchanted Hoot Loop (Bottom)"}, + {{1009, 0x0000}, "Trap Shadow (Bottom)"}, + {{1010, 0x0000}, "Magna Charge (Bottom)"}, + {{1010, 0x2402}, "Nitro Magna Charge (Bottom)"}, + {{1011, 0x0000}, "Spy Rise (Bottom)"}, + {{1012, 0x0000}, "Night Shift (Bottom)"}, + {{1012, 0x2403}, "Legendary Night Shift (Bottom)"}, + {{1013, 0x0000}, "Rattle Shake (Bottom)"}, + {{1013, 0x2402}, "Quick Draw Rattle Shake (Bottom)"}, + {{1014, 0x0000}, "Freeze Blade (Bottom)"}, + {{1014, 0x2402}, "Nitro Freeze Blade (Bottom)"}, + {{1015, 0x0000}, "Wash Buckler (Bottom)"}, + {{1015, 0x2402}, "Dark Wash Buckler (Bottom)"}, + {{2000, 0x0000}, "Boom Jet (Top)"}, + {{2001, 0x0000}, "Free Ranger (Top)"}, + {{2001, 0x2403}, "Legendary Free Ranger (Top)"}, + {{2002, 0x0000}, "Rubble Rouser (Top)"}, + {{2003, 0x0000}, "Doom Stone (Top)"}, + {{2004, 0x0000}, "Blast Zone (Top)"}, + {{2004, 0x2402}, "Dark Blast Zone (Top)"}, + {{2005, 0x0000}, "Fire Kraken (Top)"}, + {{2005, 0x2402}, "Jade Fire Kraken (Top)"}, + {{2006, 0x0000}, "Stink Bomb (Top)"}, + {{2007, 0x0000}, "Grilla Drilla (Top)"}, + {{2008, 0x0000}, "Hoot Loop (Top)"}, + {{2008, 0x2402}, "Enchanted Hoot Loop (Top)"}, + {{2009, 0x0000}, "Trap Shadow (Top)"}, + {{2010, 0x0000}, "Magna Charge (Top)"}, + {{2010, 0x2402}, "Nitro Magna Charge (Top)"}, + {{2011, 0x0000}, "Spy Rise (Top)"}, + {{2012, 0x0000}, "Night Shift (Top)"}, + {{2012, 0x2403}, "Legendary Night Shift (Top)"}, + {{2013, 0x0000}, "Rattle Shake (Top)"}, + {{2013, 0x2402}, "Quick Draw Rattle Shake (Top)"}, + {{2014, 0x0000}, "Freeze Blade (Top)"}, + {{2014, 0x2402}, "Nitro Freeze Blade (Top)"}, + {{2015, 0x0000}, "Wash Buckler (Top)"}, + {{2015, 0x2402}, "Dark Wash Buckler (Top)"}, + {{3000, 0x0000}, "Scratch"}, + {{3001, 0x0000}, "Pop Thorn"}, + {{3002, 0x0000}, "Slobber Tooth"}, + {{3002, 0x2402}, "Dark Slobber Tooth"}, + {{3003, 0x0000}, "Scorp"}, + {{3004, 0x0000}, "Fryno"}, + {{3004, 0x3805}, "Hog Wild Fryno"}, + {{3005, 0x0000}, "Smolderdash"}, + {{3005, 0x2206}, "LightCore Smolderdash"}, + {{3006, 0x0000}, "Bumble Blast"}, + {{3006, 0x2402}, "Jolly Bumble Blast"}, + {{3006, 0x2206}, "LightCore Bumble Blast"}, + {{3007, 0x0000}, "Zoo Lou"}, + {{3007, 0x2403}, "Legendary Zoo Lou"}, + {{3008, 0x0000}, "Dune Bug"}, + {{3009, 0x0000}, "Star Strike"}, + {{3009, 0x2602}, "Enchanted Star Strike"}, + {{3009, 0x2206}, "LightCore Star Strike"}, + {{3010, 0x0000}, "Countdown"}, + {{3010, 0x2402}, "Kickoff Countdown"}, + {{3010, 0x2206}, "LightCore Countdown"}, + {{3011, 0x0000}, "Wind Up"}, + {{3011, 0x2404}, "Gear Head VVind Up"}, + {{3012, 0x0000}, "Roller Brawl"}, + {{3013, 0x0000}, "Grim Creeper"}, + {{3013, 0x2603}, "Legendary Grim Creeper"}, + {{3013, 0x2206}, "LightCore Grim Creeper"}, + {{3014, 0x0000}, "Rip Tide"}, + {{3015, 0x0000}, "Punk Shock"}, + {{3200, 0x0000}, "Battle Hammer"}, + {{3201, 0x0000}, "Sky Diamond"}, + {{3202, 0x0000}, "Platinum Sheep"}, + {{3203, 0x0000}, "Groove Machine"}, + {{3204, 0x0000}, "UFO Hat"}, + {{3300, 0x0000}, "Sheep Wreck Island"}, + {{3301, 0x0000}, "Tower of Time"}, + {{3302, 0x0000}, "Fiery Forge"}, + {{3303, 0x0000}, "Arkeyan Crossbow"}, + {{3220, 0x0000}, "Jet Stream"}, + {{3221, 0x0000}, "Tomb Buggy"}, + {{3222, 0x0000}, "Reef Ripper"}, + {{3223, 0x0000}, "Burn Cycle"}, + {{3224, 0x0000}, "Hot Streak"}, + {{3224, 0x4402}, "Dark Hot Streak"}, + {{3224, 0x4004}, "E3 Hot Streak"}, + {{3224, 0x441E}, "Golden Hot Streak"}, + {{3225, 0x0000}, "Shark Tank"}, + {{3226, 0x0000}, "Thump Truck"}, + {{3227, 0x0000}, "Crypt Crusher"}, + {{3228, 0x0000}, "Stealth Stinger"}, + {{3228, 0x4402}, "Nitro Stealth Stinger"}, + {{3231, 0x0000}, "Dive Bomber"}, + {{3231, 0x4402}, "Spring Ahead Dive Bomber"}, + {{3232, 0x0000}, "Sky Slicer"}, + {{3233, 0x0000}, "Clown Cruiser (Nintendo Only)"}, + {{3233, 0x4402}, "Dark Clown Cruiser (Nintendo Only)"}, + {{3234, 0x0000}, "Gold Rusher"}, + {{3234, 0x4402}, "Power Blue Gold Rusher"}, + {{3235, 0x0000}, "Shield Striker"}, + {{3236, 0x0000}, "Sun Runner"}, + {{3236, 0x4403}, "Legendary Sun Runner"}, + {{3237, 0x0000}, "Sea Shadow"}, + {{3237, 0x4402}, "Dark Sea Shadow"}, + {{3238, 0x0000}, "Splatter Splasher"}, + {{3238, 0x4402}, "Power Blue Splatter Splasher"}, + {{3239, 0x0000}, "Soda Skimmer"}, + {{3239, 0x4402}, "Nitro Soda Skimmer"}, + {{3240, 0x0000}, "Barrel Blaster (Nintendo Only)"}, + {{3240, 0x4402}, "Dark Barrel Blaster (Nintendo Only)"}, + {{3241, 0x0000}, "Buzz Wing"}, + {{3400, 0x0000}, "Fiesta"}, + {{3400, 0x4515}, "Frightful Fiesta"}, + {{3401, 0x0000}, "High Volt"}, + {{3402, 0x0000}, "Splat"}, + {{3402, 0x4502}, "Power Blue Splat"}, + {{3406, 0x0000}, "Stormblade"}, + {{3411, 0x0000}, "Smash Hit"}, + {{3411, 0x4502}, "Steel Plated Smash Hit"}, + {{3412, 0x0000}, "Spitfire"}, + {{3412, 0x4502}, "Dark Spitfire"}, + {{3413, 0x0000}, "Hurricane Jet Vac"}, + {{3413, 0x4503}, "Legendary Hurricane Jet Vac"}, + {{3414, 0x0000}, "Double Dare Trigger Happy"}, + {{3414, 0x4502}, "Power Blue Double Dare Trigger Happy"}, + {{3415, 0x0000}, "Super Shot Stealth Elf"}, + {{3415, 0x4502}, "Dark Super Shot Stealth Elf"}, + {{3416, 0x0000}, "Shark Shooter Terrafin"}, + {{3417, 0x0000}, "Bone Bash Roller Brawl"}, + {{3417, 0x4503}, "Legendary Bone Bash Roller Brawl"}, + {{3420, 0x0000}, "Big Bubble Pop Fizz"}, + {{3420, 0x450E}, "Birthday Bash Big Bubble Pop Fizz"}, + {{3421, 0x0000}, "Lava Lance Eruptor"}, + {{3422, 0x0000}, "Deep Dive Gill Grunt"}, + {{3423, 0x0000}, "Turbo Charge Donkey Kong (Nintendo Only)"}, + {{3423, 0x4502}, "Dark Turbo Charge Donkey Kong (Nintendo Only)"}, + {{3424, 0x0000}, "Hammer Slam Bowser (Nintendo Only)"}, + {{3424, 0x4502}, "Dark Hammer Slam Bowser (Nintendo Only)"}, + {{3425, 0x0000}, "Dive-Clops"}, + {{3425, 0x450E}, "Missile-Tow Dive-Clops"}, + {{3426, 0x0000}, "Astroblast"}, + {{3426, 0x4503}, "Legendary Astroblast"}, + {{3427, 0x0000}, "Nightfall"}, + {{3428, 0x0000}, "Thrillipede"}, + {{3428, 0x450D}, "Eggcited Thrillipede"}, + {{3500, 0x0000}, "Sky Trophy"}, + {{3501, 0x0000}, "Land Trophy"}, + {{3502, 0x0000}, "Sea Trophy"}, + {{3503, 0x0000}, "Kaos Trophy"}, + }; + + uint16 SkylanderUSB::SkylanderCRC16(uint16 initValue, const uint8* buffer, uint32 size) + { + const unsigned short CRC_CCITT_TABLE[256] = {0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210, 0x3273, + 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, + 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 0x48C4, 0x58E5, 0x6886, + 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF, + 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, + 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2, + 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, 0x95A8, + 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, + 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, + 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64, + 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0}; + + uint16 crc = initValue; + + for (uint32 i = 0; i < size; i++) + { + const uint16 tmp = (crc >> 8) ^ buffer[i]; + crc = (crc << 8) ^ CRC_CCITT_TABLE[tmp]; + } + + return crc; + } + SkylanderPortalDevice::SkylanderPortalDevice() + : Device(0x1430, 0x0150, 1, 2, 0) + { + m_IsOpened = false; + } + + bool SkylanderPortalDevice::Open() + { + if (!IsOpened()) + { + m_IsOpened = true; + } + return true; + } + + void SkylanderPortalDevice::Close() + { + if (IsOpened()) + { + m_IsOpened = false; + } + } + + bool SkylanderPortalDevice::IsOpened() + { + return m_IsOpened; + } + + Device::ReadResult SkylanderPortalDevice::Read(ReadMessage* message) + { + memcpy(message->data, g_skyportal.GetStatus().data(), message->length); + message->bytesRead = message->length; + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + return Device::ReadResult::Success; + } + + Device::WriteResult SkylanderPortalDevice::Write(WriteMessage* message) + { + message->bytesWritten = message->length; + return Device::WriteResult::Success; + } + + bool SkylanderPortalDevice::GetDescriptor(uint8 descType, + uint8 descIndex, + uint8 lang, + uint8* output, + uint32 outputMaxLength) + { + uint8 configurationDescriptor[0x29]; + + uint8* currentWritePtr; + + // configuration descriptor + currentWritePtr = configurationDescriptor + 0; + *(uint8*)(currentWritePtr + 0) = 9; // bLength + *(uint8*)(currentWritePtr + 1) = 2; // bDescriptorType + *(uint16be*)(currentWritePtr + 2) = 0x0029; // wTotalLength + *(uint8*)(currentWritePtr + 4) = 1; // bNumInterfaces + *(uint8*)(currentWritePtr + 5) = 1; // bConfigurationValue + *(uint8*)(currentWritePtr + 6) = 0; // iConfiguration + *(uint8*)(currentWritePtr + 7) = 0x80; // bmAttributes + *(uint8*)(currentWritePtr + 8) = 0xFA; // MaxPower + currentWritePtr = currentWritePtr + 9; + // configuration descriptor + *(uint8*)(currentWritePtr + 0) = 9; // bLength + *(uint8*)(currentWritePtr + 1) = 0x04; // bDescriptorType + *(uint8*)(currentWritePtr + 2) = 0; // bInterfaceNumber + *(uint8*)(currentWritePtr + 3) = 0; // bAlternateSetting + *(uint8*)(currentWritePtr + 4) = 2; // bNumEndpoints + *(uint8*)(currentWritePtr + 5) = 3; // bInterfaceClass + *(uint8*)(currentWritePtr + 6) = 0; // bInterfaceSubClass + *(uint8*)(currentWritePtr + 7) = 0; // bInterfaceProtocol + *(uint8*)(currentWritePtr + 8) = 0; // iInterface + currentWritePtr = currentWritePtr + 9; + // configuration descriptor + *(uint8*)(currentWritePtr + 0) = 9; // bLength + *(uint8*)(currentWritePtr + 1) = 0x21; // bDescriptorType + *(uint16be*)(currentWritePtr + 2) = 0x0111; // bcdHID + *(uint8*)(currentWritePtr + 4) = 0x00; // bCountryCode + *(uint8*)(currentWritePtr + 5) = 0x01; // bNumDescriptors + *(uint8*)(currentWritePtr + 6) = 0x22; // bDescriptorType + *(uint16be*)(currentWritePtr + 7) = 0x001D; // wDescriptorLength + currentWritePtr = currentWritePtr + 9; + // endpoint descriptor 1 + *(uint8*)(currentWritePtr + 0) = 7; // bLength + *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType + *(uint8*)(currentWritePtr + 2) = 0x81; // bEndpointAddress + *(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes + *(uint16be*)(currentWritePtr + 4) = 0x40; // wMaxPacketSize + *(uint8*)(currentWritePtr + 6) = 0x01; // bInterval + currentWritePtr = currentWritePtr + 7; + // endpoint descriptor 2 + *(uint8*)(currentWritePtr + 0) = 7; // bLength + *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType + *(uint8*)(currentWritePtr + 2) = 0x02; // bEndpointAddress + *(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes + *(uint16be*)(currentWritePtr + 4) = 0x40; // wMaxPacketSize + *(uint8*)(currentWritePtr + 6) = 0x01; // bInterval + currentWritePtr = currentWritePtr + 7; + + cemu_assert_debug((currentWritePtr - configurationDescriptor) == 0x29); + + memcpy(output, configurationDescriptor, + std::min<uint32>(outputMaxLength, sizeof(configurationDescriptor))); + return true; + } + + bool SkylanderPortalDevice::SetProtocol(uint32 ifIndex, uint32 protocol) + { + return true; + } + + bool SkylanderPortalDevice::SetReport(ReportMessage* message) + { + g_skyportal.ControlTransfer(message->originalData, message->originalLength); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + return true; + } + + void SkylanderUSB::ControlTransfer(uint8* buf, sint32 originalLength) + { + std::array<uint8, 64> interruptResponse = {}; + switch (buf[0]) + { + case 'A': + { + interruptResponse = {buf[0], buf[1], 0xFF, 0x77}; + g_skyportal.Activate(); + break; + } + case 'C': + { + g_skyportal.SetLeds(0x01, buf[1], buf[2], buf[3]); + break; + } + case 'J': + { + g_skyportal.SetLeds(buf[1], buf[2], buf[3], buf[4]); + interruptResponse = {buf[0]}; + break; + } + case 'L': + { + uint8 side = buf[1]; + if (side == 0x02) + { + side = 0x04; + } + g_skyportal.SetLeds(side, buf[2], buf[3], buf[4]); + break; + } + case 'M': + { + interruptResponse = {buf[0], buf[1], 0x00, 0x19}; + break; + } + case 'Q': + { + const uint8 skyNum = buf[1] & 0xF; + const uint8 block = buf[2]; + g_skyportal.QueryBlock(skyNum, block, interruptResponse.data()); + break; + } + case 'R': + { + interruptResponse = {buf[0], 0x02, 0x1b}; + break; + } + case 'S': + case 'V': + { + // No response needed + break; + } + case 'W': + { + const uint8 skyNum = buf[1] & 0xF; + const uint8 block = buf[2]; + g_skyportal.WriteBlock(skyNum, block, &buf[3], interruptResponse.data()); + break; + } + default: + cemu_assert_error(); + break; + } + if (interruptResponse[0] != 0) + { + std::lock_guard lock(m_queryMutex); + m_queries.push(interruptResponse); + } + } + + void SkylanderUSB::Activate() + { + std::lock_guard lock(m_skyMutex); + if (m_activated) + { + // If the portal was already active no change is needed + return; + } + + // If not we need to advertise change to all the figures present on the portal + for (auto& s : m_skylanders) + { + if (s.status & 1) + { + s.queuedStatus.push(3); + s.queuedStatus.push(1); + } + } + + m_activated = true; + } + + void SkylanderUSB::Deactivate() + { + std::lock_guard lock(m_skyMutex); + + for (auto& s : m_skylanders) + { + // check if at the end of the updates there would be a figure on the portal + if (!s.queuedStatus.empty()) + { + s.status = s.queuedStatus.back(); + s.queuedStatus = std::queue<uint8>(); + } + + s.status &= 1; + } + + m_activated = false; + } + + void SkylanderUSB::SetLeds(uint8 side, uint8 r, uint8 g, uint8 b) + { + std::lock_guard lock(m_skyMutex); + if (side == 0x00) + { + m_colorRight.red = r; + m_colorRight.green = g; + m_colorRight.blue = b; + } + else if (side == 0x01) + { + m_colorRight.red = r; + m_colorRight.green = g; + m_colorRight.blue = b; + + m_colorLeft.red = r; + m_colorLeft.green = g; + m_colorLeft.blue = b; + } + else if (side == 0x02) + { + m_colorLeft.red = r; + m_colorLeft.green = g; + m_colorLeft.blue = b; + } + else if (side == 0x03) + { + m_colorTrap.red = r; + m_colorTrap.green = g; + m_colorTrap.blue = b; + } + } + + uint8 SkylanderUSB::LoadSkylander(uint8* buf, std::unique_ptr<FileStream> file) + { + std::lock_guard lock(m_skyMutex); + + uint32 skySerial = 0; + for (int i = 3; i > -1; i--) + { + skySerial <<= 8; + skySerial |= buf[i]; + } + uint8 foundSlot = 0xFF; + + // mimics spot retaining on the portal + for (auto i = 0; i < 16; i++) + { + if ((m_skylanders[i].status & 1) == 0) + { + if (m_skylanders[i].lastId == skySerial) + { + foundSlot = i; + break; + } + + if (i < foundSlot) + { + foundSlot = i; + } + } + } + + if (foundSlot != 0xFF) + { + auto& skylander = m_skylanders[foundSlot]; + memcpy(skylander.data.data(), buf, skylander.data.size()); + skylander.skyFile = std::move(file); + skylander.status = Skylander::ADDED; + skylander.queuedStatus.push(Skylander::ADDED); + skylander.queuedStatus.push(Skylander::READY); + skylander.lastId = skySerial; + } + return foundSlot; + } + + bool SkylanderUSB::RemoveSkylander(uint8 skyNum) + { + std::lock_guard lock(m_skyMutex); + auto& thesky = m_skylanders[skyNum]; + + if (thesky.status & 1) + { + thesky.status = 2; + thesky.queuedStatus.push(2); + thesky.queuedStatus.push(0); + thesky.Save(); + thesky.skyFile.reset(); + return true; + } + + return false; + } + + void SkylanderUSB::QueryBlock(uint8 skyNum, uint8 block, uint8* replyBuf) + { + std::lock_guard lock(m_skyMutex); + + const auto& skylander = m_skylanders[skyNum]; + + replyBuf[0] = 'Q'; + replyBuf[2] = block; + if (skylander.status & 1) + { + replyBuf[1] = (0x10 | skyNum); + memcpy(replyBuf + 3, skylander.data.data() + (16 * block), 16); + } + else + { + replyBuf[1] = skyNum; + } + } + + void SkylanderUSB::WriteBlock(uint8 skyNum, uint8 block, + const uint8* toWriteBuf, uint8* replyBuf) + { + std::lock_guard lock(m_skyMutex); + + auto& skylander = m_skylanders[skyNum]; + + replyBuf[0] = 'W'; + replyBuf[2] = block; + + if (skylander.status & 1) + { + replyBuf[1] = (0x10 | skyNum); + memcpy(skylander.data.data() + (block * 16), toWriteBuf, 16); + skylander.Save(); + } + else + { + replyBuf[1] = skyNum; + } + } + + std::array<uint8, 64> SkylanderUSB::GetStatus() + { + std::lock_guard lock(m_queryMutex); + std::array<uint8, 64> interruptResponse = {}; + + if (!m_queries.empty()) + { + interruptResponse = m_queries.front(); + m_queries.pop(); + // This needs to happen after ~22 milliseconds + } + else + { + uint32 status = 0; + uint8 active = 0x00; + if (m_activated) + { + active = 0x01; + } + + for (int i = 16 - 1; i >= 0; i--) + { + auto& s = m_skylanders[i]; + + if (!s.queuedStatus.empty()) + { + s.status = s.queuedStatus.front(); + s.queuedStatus.pop(); + } + status <<= 2; + status |= s.status; + } + interruptResponse = {0x53, 0x00, 0x00, 0x00, 0x00, m_interruptCounter++, + active, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00}; + memcpy(&interruptResponse[1], &status, sizeof(status)); + } + return interruptResponse; + } + + void SkylanderUSB::Skylander::Save() + { + if (!skyFile) + return; + + skyFile->writeData(data.data(), data.size()); + } +} // namespace nsyshid \ No newline at end of file diff --git a/src/Cafe/OS/libs/nsyshid/Skylander.h b/src/Cafe/OS/libs/nsyshid/Skylander.h new file mode 100644 index 00000000..dfa370dc --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/Skylander.h @@ -0,0 +1,98 @@ +#include <mutex> + +#include "nsyshid.h" +#include "Backend.h" + +#include "Common/FileStream.h" + +namespace nsyshid +{ + class SkylanderPortalDevice final : public Device { + public: + SkylanderPortalDevice(); + ~SkylanderPortalDevice() = default; + + bool Open() override; + + void Close() override; + + bool IsOpened() override; + + ReadResult Read(ReadMessage* message) override; + + WriteResult Write(WriteMessage* message) override; + + bool GetDescriptor(uint8 descType, + uint8 descIndex, + uint8 lang, + uint8* output, + uint32 outputMaxLength) override; + + bool SetProtocol(uint32 ifIndex, uint32 protocol) override; + + bool SetReport(ReportMessage* message) override; + + private: + bool m_IsOpened; + }; + + extern const std::map<const std::pair<const uint16, const uint16>, const std::string> listSkylanders; + + class SkylanderUSB { + public: + struct Skylander final + { + std::unique_ptr<FileStream> skyFile; + uint8 status = 0; + std::queue<uint8> queuedStatus; + std::array<uint8, 0x40 * 0x10> data{}; + uint32 lastId = 0; + void Save(); + + enum : uint8 + { + REMOVED = 0, + READY = 1, + REMOVING = 2, + ADDED = 3 + }; + }; + + struct SkylanderLEDColor final + { + uint8 red = 0; + uint8 green = 0; + uint8 blue = 0; + }; + + void ControlTransfer(uint8* buf, sint32 originalLength); + + void Activate(); + void Deactivate(); + void SetLeds(uint8 side, uint8 r, uint8 g, uint8 b); + + std::array<uint8, 64> GetStatus(); + void QueryBlock(uint8 skyNum, uint8 block, uint8* replyBuf); + void WriteBlock(uint8 skyNum, uint8 block, const uint8* toWriteBuf, + uint8* replyBuf); + + uint8 LoadSkylander(uint8* buf, std::unique_ptr<FileStream> file); + bool RemoveSkylander(uint8 skyNum); + uint16 SkylanderCRC16(uint16 initValue, const uint8* buffer, uint32 size); + + protected: + std::mutex m_skyMutex; + std::mutex m_queryMutex; + std::array<Skylander, 16> m_skylanders; + + private: + std::queue<std::array<uint8, 64>> m_queries; + bool m_activated = true; + uint8 m_interruptCounter = 0; + SkylanderLEDColor m_colorRight = {}; + SkylanderLEDColor m_colorLeft = {}; + SkylanderLEDColor m_colorTrap = {}; + + }; + extern SkylanderUSB g_skyportal; +} // namespace nsyshid \ No newline at end of file diff --git a/src/Cafe/OS/libs/nsyshid/nsyshid.cpp b/src/Cafe/OS/libs/nsyshid/nsyshid.cpp index ff5c4f45..c674b844 100644 --- a/src/Cafe/OS/libs/nsyshid/nsyshid.cpp +++ b/src/Cafe/OS/libs/nsyshid/nsyshid.cpp @@ -256,6 +256,19 @@ namespace nsyshid device->m_productId); } + bool FindDeviceById(uint16 vendorId, uint16 productId) + { + std::lock_guard<std::recursive_mutex> lock(hidMutex); + for (const auto& device : deviceList) + { + if (device->m_vendorId == vendorId && device->m_productId == productId) + { + return true; + } + } + return false; + } + void export_HIDAddClient(PPCInterpreter_t* hCPU) { ppcDefineParamTypePtr(hidClient, HIDClient_t, 0); @@ -406,7 +419,8 @@ namespace nsyshid sint32 originalLength, MPTR callbackFuncMPTR, MPTR callbackParamMPTR) { cemuLog_logDebug(LogType::Force, "_hidSetReportAsync begin"); - if (device->SetReport(reportData, length, originalData, originalLength)) + ReportMessage message(reportData, length, originalData, originalLength); + if (device->SetReport(&message)) { DoHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, @@ -433,7 +447,8 @@ namespace nsyshid { _debugPrintHex("_hidSetReportSync Begin", reportData, length); sint32 returnCode = 0; - if (device->SetReport(reportData, length, originalData, originalLength)) + ReportMessage message(reportData, length, originalData, originalLength); + if (device->SetReport(&message)) { returnCode = originalLength; } @@ -511,17 +526,16 @@ namespace nsyshid return -1; } memset(data, 0, maxLength); - - sint32 bytesRead = 0; - Device::ReadResult readResult = device->Read(data, maxLength, bytesRead); + ReadMessage message(data, maxLength, 0); + Device::ReadResult readResult = device->Read(&message); switch (readResult) { case Device::ReadResult::Success: { cemuLog_logDebug(LogType::Force, "nsyshid.hidReadInternalSync(): read {} of {} bytes", - bytesRead, + message.bytesRead, maxLength); - return bytesRead; + return message.bytesRead; } break; case Device::ReadResult::Error: @@ -609,15 +623,15 @@ namespace nsyshid cemuLog_logDebug(LogType::Force, "nsyshid.hidWriteInternalSync(): cannot write to a non-opened device"); return -1; } - sint32 bytesWritten = 0; - Device::WriteResult writeResult = device->Write(data, maxLength, bytesWritten); + WriteMessage message(data, maxLength, 0); + Device::WriteResult writeResult = device->Write(&message); switch (writeResult) { case Device::WriteResult::Success: { - cemuLog_logDebug(LogType::Force, "nsyshid.hidWriteInternalSync(): wrote {} of {} bytes", bytesWritten, + cemuLog_logDebug(LogType::Force, "nsyshid.hidWriteInternalSync(): wrote {} of {} bytes", message.bytesWritten, maxLength); - return bytesWritten; + return message.bytesWritten; } break; case Device::WriteResult::Error: @@ -758,6 +772,11 @@ namespace nsyshid return nullptr; } + bool Backend::FindDeviceById(uint16 vendorId, uint16 productId) + { + return nsyshid::FindDeviceById(vendorId, productId); + } + bool Backend::IsDeviceWhitelisted(uint16 vendorId, uint16 productId) { return Whitelist::GetInstance().IsDeviceWhitelisted(vendorId, productId); diff --git a/src/config/CemuConfig.cpp b/src/config/CemuConfig.cpp index 4f1736e2..8e7cf398 100644 --- a/src/config/CemuConfig.cpp +++ b/src/config/CemuConfig.cpp @@ -358,6 +358,10 @@ void CemuConfig::Load(XMLConfigParser& parser) auto dsuc = input.get("DSUC"); dsu_client.host = dsuc.get_attribute("host", dsu_client.host); dsu_client.port = dsuc.get_attribute("port", dsu_client.port); + + // emulatedusbdevices + auto usbdevices = parser.get("EmulatedUsbDevices"); + emulated_usb_devices.emulate_skylander_portal = usbdevices.get("EmulateSkylanderPortal", emulated_usb_devices.emulate_skylander_portal); } void CemuConfig::Save(XMLConfigParser& parser) @@ -551,6 +555,10 @@ void CemuConfig::Save(XMLConfigParser& parser) auto dsuc = input.set("DSUC"); dsuc.set_attribute("host", dsu_client.host); dsuc.set_attribute("port", dsu_client.port); + + // emulated usb devices + auto usbdevices = config.set("EmulatedUsbDevices"); + usbdevices.set("EmulateSkylanderPortal", emulated_usb_devices.emulate_skylander_portal.GetValue()); } GameEntry* CemuConfig::GetGameEntryByTitleId(uint64 titleId) diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index cab7a1af..d0776d2e 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -514,6 +514,12 @@ struct CemuConfig NetworkService GetAccountNetworkService(uint32 persistentId); void SetAccountSelectedService(uint32 persistentId, NetworkService serviceIndex); + + // emulated usb devices + struct + { + ConfigValue<bool> emulate_skylander_portal{false}; + }emulated_usb_devices{}; private: GameEntry* GetGameEntryByTitleId(uint64 titleId); diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 19ce95dc..02f96a9c 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -101,6 +101,8 @@ add_library(CemuGui PairingDialog.h TitleManager.cpp TitleManager.h + EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp + EmulatedUSBDevices/EmulatedUSBDeviceFrame.h windows/PPCThreadsViewer windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp windows/PPCThreadsViewer/DebugPPCThreadsWindow.h diff --git a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp new file mode 100644 index 00000000..58c1823c --- /dev/null +++ b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp @@ -0,0 +1,354 @@ +#include "gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h" + +#include <algorithm> +#include <random> + +#include "config/CemuConfig.h" +#include "gui/helpers/wxHelpers.h" +#include "gui/wxHelper.h" +#include "util/helpers/helpers.h" + +#include "Cafe/OS/libs/nsyshid/nsyshid.h" +#include "Cafe/OS/libs/nsyshid/Skylander.h" + +#include "Common/FileStream.h" + +#include <wx/arrstr.h> +#include <wx/button.h> +#include <wx/checkbox.h> +#include <wx/combobox.h> +#include <wx/filedlg.h> +#include <wx/msgdlg.h> +#include <wx/notebook.h> +#include <wx/panel.h> +#include <wx/sizer.h> +#include <wx/statbox.h> +#include <wx/stattext.h> +#include <wx/stream.h> +#include <wx/textctrl.h> +#include <wx/textentry.h> +#include <wx/valnum.h> +#include <wx/wfstream.h> + +#include "resource/embedded/resources.h" +#include "EmulatedUSBDeviceFrame.h" + +EmulatedUSBDeviceFrame::EmulatedUSBDeviceFrame(wxWindow* parent) + : wxFrame(parent, wxID_ANY, _("Emulated USB Devices"), wxDefaultPosition, + wxDefaultSize, wxDEFAULT_FRAME_STYLE | wxTAB_TRAVERSAL) +{ + SetIcon(wxICON(X_BOX)); + + auto& config = GetConfig(); + + auto* sizer = new wxBoxSizer(wxVERTICAL); + auto* notebook = new wxNotebook(this, wxID_ANY); + + notebook->AddPage(AddSkylanderPage(notebook), _("Skylanders Portal")); + + sizer->Add(notebook, 1, wxEXPAND | wxALL, 2); + + SetSizerAndFit(sizer); + Layout(); + Centre(wxBOTH); +} + +EmulatedUSBDeviceFrame::~EmulatedUSBDeviceFrame() {} + +wxPanel* EmulatedUSBDeviceFrame::AddSkylanderPage(wxNotebook* notebook) +{ + auto* panel = new wxPanel(notebook); + auto* panelSizer = new wxBoxSizer(wxVERTICAL); + auto* box = new wxStaticBox(panel, wxID_ANY, _("Skylanders Manager")); + auto* boxSizer = new wxStaticBoxSizer(box, wxVERTICAL); + + auto* row = new wxBoxSizer(wxHORIZONTAL); + + m_emulatePortal = + new wxCheckBox(box, wxID_ANY, _("Emulate Skylander Portal")); + m_emulatePortal->SetValue( + GetConfig().emulated_usb_devices.emulate_skylander_portal); + m_emulatePortal->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) { + GetConfig().emulated_usb_devices.emulate_skylander_portal = + m_emulatePortal->IsChecked(); + g_config.Save(); + }); + row->Add(m_emulatePortal, 1, wxEXPAND | wxALL, 2); + boxSizer->Add(row, 1, wxEXPAND | wxALL, 2); + for (int i = 0; i < 16; i++) + { + boxSizer->Add(AddSkylanderRow(i, box), 1, wxEXPAND | wxALL, 2); + } + panelSizer->Add(boxSizer, 1, wxEXPAND | wxALL, 2); + panel->SetSizerAndFit(panelSizer); + + return panel; +} + +wxBoxSizer* EmulatedUSBDeviceFrame::AddSkylanderRow(uint8 row_number, + wxStaticBox* box) +{ + auto* row = new wxBoxSizer(wxHORIZONTAL); + + row->Add(new wxStaticText(box, wxID_ANY, + fmt::format("{} {}", _("Skylander").ToStdString(), + (row_number + 1))), + 1, wxEXPAND | wxALL, 2); + m_skylanderSlots[row_number] = + new wxTextCtrl(box, wxID_ANY, _("None"), wxDefaultPosition, wxDefaultSize, + wxTE_READONLY); + m_skylanderSlots[row_number]->SetMinSize(wxSize(150, -1)); + m_skylanderSlots[row_number]->Disable(); + row->Add(m_skylanderSlots[row_number], 1, wxEXPAND | wxALL, 2); + auto* loadButton = new wxButton(box, wxID_ANY, _("Load")); + loadButton->Bind(wxEVT_BUTTON, [row_number, this](wxCommandEvent&) { + LoadSkylander(row_number); + }); + auto* createButton = new wxButton(box, wxID_ANY, _("Create")); + createButton->Bind(wxEVT_BUTTON, [row_number, this](wxCommandEvent&) { + CreateSkylander(row_number); + }); + auto* clearButton = new wxButton(box, wxID_ANY, _("Clear")); + clearButton->Bind(wxEVT_BUTTON, [row_number, this](wxCommandEvent&) { + ClearSkylander(row_number); + }); + row->Add(loadButton, 1, wxEXPAND | wxALL, 2); + row->Add(createButton, 1, wxEXPAND | wxALL, 2); + row->Add(clearButton, 1, wxEXPAND | wxALL, 2); + + return row; +} + +void EmulatedUSBDeviceFrame::LoadSkylander(uint8 slot) +{ + wxFileDialog openFileDialog(this, _("Open Skylander dump"), "", "", + "Skylander files (*.sky;*.bin;*.dump;*.dmp)|*.sky;*.bin;*.dump;*.dmp", + wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (openFileDialog.ShowModal() != wxID_OK || openFileDialog.GetPath().empty()) + return; + + LoadSkylanderPath(slot, openFileDialog.GetPath()); +} + +void EmulatedUSBDeviceFrame::LoadSkylanderPath(uint8 slot, wxString path) +{ + std::unique_ptr<FileStream> skyFile(FileStream::openFile2(_utf8ToPath(path.utf8_string()), true)); + if (!skyFile) + { + wxMessageDialog open_error(this, "Error Opening File: " + path.c_str()); + open_error.ShowModal(); + return; + } + + std::array<uint8, 0x40 * 0x10> fileData; + if (skyFile->readData(fileData.data(), fileData.size()) != fileData.size()) + { + wxMessageDialog open_error(this, "Failed to read file! File was too small"); + open_error.ShowModal(); + return; + } + ClearSkylander(slot); + + uint16 skyId = uint16(fileData[0x11]) << 8 | uint16(fileData[0x10]); + uint16 skyVar = uint16(fileData[0x1D]) << 8 | uint16(fileData[0x1C]); + + uint8 portalSlot = nsyshid::g_skyportal.LoadSkylander(fileData.data(), + std::move(skyFile)); + m_skySlots[slot] = std::tuple(portalSlot, skyId, skyVar); + UpdateSkylanderEdits(); +} + +void EmulatedUSBDeviceFrame::CreateSkylander(uint8 slot) +{ + CreateSkylanderDialog create_dlg(this, slot); + create_dlg.ShowModal(); + if (create_dlg.GetReturnCode() == 1) + { + LoadSkylanderPath(slot, create_dlg.GetFilePath()); + } +} + +void EmulatedUSBDeviceFrame::ClearSkylander(uint8 slot) +{ + if (auto slotInfos = m_skySlots[slot]) + { + auto [curSlot, id, var] = slotInfos.value(); + nsyshid::g_skyportal.RemoveSkylander(curSlot); + m_skySlots[slot] = {}; + UpdateSkylanderEdits(); + } +} + +CreateSkylanderDialog::CreateSkylanderDialog(wxWindow* parent, uint8 slot) + : wxDialog(parent, wxID_ANY, _("Skylander Figure Creator"), wxDefaultPosition, wxSize(500, 150)) +{ + auto* sizer = new wxBoxSizer(wxVERTICAL); + + auto* comboRow = new wxBoxSizer(wxHORIZONTAL); + + auto* comboBox = new wxComboBox(this, wxID_ANY); + comboBox->Append("---Select---", reinterpret_cast<void*>(0xFFFFFFFF)); + wxArrayString filterlist; + for (auto it = nsyshid::listSkylanders.begin(); it != nsyshid::listSkylanders.end(); it++) + { + const uint32 variant = uint32(uint32(it->first.first) << 16) | uint32(it->first.second); + comboBox->Append(it->second, reinterpret_cast<void*>(variant)); + filterlist.Add(it->second); + } + comboBox->SetSelection(0); + bool enabled = comboBox->AutoComplete(filterlist); + comboRow->Add(comboBox, 1, wxEXPAND | wxALL, 2); + + auto* idVarRow = new wxBoxSizer(wxHORIZONTAL); + + wxIntegerValidator<uint32> validator; + + auto* labelId = new wxStaticText(this, wxID_ANY, "ID:"); + auto* labelVar = new wxStaticText(this, wxID_ANY, "Variant:"); + auto* editId = new wxTextCtrl(this, wxID_ANY, _("0"), wxDefaultPosition, wxDefaultSize, 0, validator); + auto* editVar = new wxTextCtrl(this, wxID_ANY, _("0"), wxDefaultPosition, wxDefaultSize, 0, validator); + + idVarRow->Add(labelId, 1, wxALL, 5); + idVarRow->Add(editId, 1, wxALL, 5); + idVarRow->Add(labelVar, 1, wxALL, 5); + idVarRow->Add(editVar, 1, wxALL, 5); + + auto* buttonRow = new wxBoxSizer(wxHORIZONTAL); + + auto* createButton = new wxButton(this, wxID_ANY, _("Create")); + createButton->Bind(wxEVT_BUTTON, [editId, editVar, this](wxCommandEvent&) { + long longSkyId; + if (!editId->GetValue().ToLong(&longSkyId) || longSkyId > 0xFFFF) + { + wxMessageDialog id_error(this, "Error Converting ID!", "ID Entered is Invalid"); + id_error.ShowModal(); + return; + } + long longSkyVar; + if (!editVar->GetValue().ToLong(&longSkyVar) || longSkyVar > 0xFFFF) + { + wxMessageDialog id_error(this, "Error Converting Variant!", "Variant Entered is Invalid"); + id_error.ShowModal(); + return; + } + uint16 skyId = longSkyId & 0xFFFF; + uint16 skyVar = longSkyVar & 0xFFFF; + const auto foundSky = nsyshid::listSkylanders.find(std::make_pair(skyId, skyVar)); + wxString predefName; + if (foundSky != nsyshid::listSkylanders.end()) + { + predefName = foundSky->second + ".sky"; + } + else + { + predefName = wxString::Format(_("Unknown(%i %i).sky"), skyId, skyVar); + } + wxFileDialog + saveFileDialog(this, _("Create Skylander file"), "", predefName, + "SKY files (*.sky)|*.sky", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + + if (saveFileDialog.ShowModal() == wxID_CANCEL) + return; + + m_filePath = saveFileDialog.GetPath(); + + wxFileOutputStream output_stream(saveFileDialog.GetPath()); + if (!output_stream.IsOk()) + { + wxMessageDialog saveError(this, "Error Creating Skylander File"); + return; + } + + std::array<uint8, 0x40 * 0x10> data{}; + + uint32 first_block = 0x690F0F0F; + uint32 other_blocks = 0x69080F7F; + memcpy(&data[0x36], &first_block, sizeof(first_block)); + for (size_t index = 1; index < 0x10; index++) + { + memcpy(&data[(index * 0x40) + 0x36], &other_blocks, sizeof(other_blocks)); + } + std::random_device rd; + std::mt19937 mt(rd()); + std::uniform_int_distribution<int> dist(0, 255); + data[0] = dist(mt); + data[1] = dist(mt); + data[2] = dist(mt); + data[3] = dist(mt); + data[4] = data[0] ^ data[1] ^ data[2] ^ data[3]; + data[5] = 0x81; + data[6] = 0x01; + data[7] = 0x0F; + + memcpy(&data[0x10], &skyId, sizeof(skyId)); + memcpy(&data[0x1C], &skyVar, sizeof(skyVar)); + + uint16 crc = nsyshid::g_skyportal.SkylanderCRC16(0xFFFF, data.data(), 0x1E); + + memcpy(&data[0x1E], &crc, sizeof(crc)); + + output_stream.SeekO(0); + output_stream.WriteAll(data.data(), data.size()); + output_stream.Close(); + + this->EndModal(1); + }); + auto* cancelButton = new wxButton(this, wxID_ANY, _("Cancel")); + cancelButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { + this->EndModal(0); + }); + + comboBox->Bind(wxEVT_COMBOBOX, [comboBox, editId, editVar, this](wxCommandEvent&) { + const uint64 sky_info = reinterpret_cast<uint64>(comboBox->GetClientData(comboBox->GetSelection())); + if (sky_info != 0xFFFFFFFF) + { + const uint16 skyId = sky_info >> 16; + const uint16 skyVar = sky_info & 0xFFFF; + + editId->SetValue(wxString::Format(wxT("%i"), skyId)); + editVar->SetValue(wxString::Format(wxT("%i"), skyVar)); + } + }); + + buttonRow->Add(createButton, 1, wxALL, 5); + buttonRow->Add(cancelButton, 1, wxALL, 5); + + sizer->Add(comboRow, 1, wxEXPAND | wxALL, 2); + sizer->Add(idVarRow, 1, wxEXPAND | wxALL, 2); + sizer->Add(buttonRow, 1, wxEXPAND | wxALL, 2); + + this->SetSizer(sizer); + this->Centre(wxBOTH); +} + +wxString CreateSkylanderDialog::GetFilePath() const +{ + return m_filePath; +} + +void EmulatedUSBDeviceFrame::UpdateSkylanderEdits() +{ + for (auto i = 0; i < 16; i++) + { + std::string displayString; + if (auto sd = m_skySlots[i]) + { + auto [portalSlot, skyId, skyVar] = sd.value(); + auto foundSky = nsyshid::listSkylanders.find(std::make_pair(skyId, skyVar)); + if (foundSky != nsyshid::listSkylanders.end()) + { + displayString = foundSky->second; + } + else + { + displayString = fmt::format("Unknown (Id:{} Var:{})", skyId, skyVar); + } + } + else + { + displayString = "None"; + } + + m_skylanderSlots[i]->ChangeValue(displayString); + } +} \ No newline at end of file diff --git a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h new file mode 100644 index 00000000..6acb7da8 --- /dev/null +++ b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h @@ -0,0 +1,42 @@ +#pragma once + +#include <array> + +#include <wx/dialog.h> +#include <wx/frame.h> + +class wxBoxSizer; +class wxCheckBox; +class wxFlexGridSizer; +class wxNotebook; +class wxPanel; +class wxStaticBox; +class wxString; +class wxTextCtrl; + +class EmulatedUSBDeviceFrame : public wxFrame { + public: + EmulatedUSBDeviceFrame(wxWindow* parent); + ~EmulatedUSBDeviceFrame(); + + private: + wxCheckBox* m_emulatePortal; + std::array<wxTextCtrl*, 16> m_skylanderSlots; + std::array<std::optional<std::tuple<uint8, uint16, uint16>>, 16> m_skySlots; + + wxPanel* AddSkylanderPage(wxNotebook* notebook); + wxBoxSizer* AddSkylanderRow(uint8 row_number, wxStaticBox* box); + void LoadSkylander(uint8 slot); + void LoadSkylanderPath(uint8 slot, wxString path); + void CreateSkylander(uint8 slot); + void ClearSkylander(uint8 slot); + void UpdateSkylanderEdits(); +}; +class CreateSkylanderDialog : public wxDialog { + public: + explicit CreateSkylanderDialog(wxWindow* parent, uint8 slot); + wxString GetFilePath() const; + + protected: + wxString m_filePath; +}; \ No newline at end of file diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 03c69a7f..7a4f3174 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -30,6 +30,7 @@ #include "Cafe/Filesystem/FST/FST.h" #include "gui/TitleManager.h" +#include "gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h" #include "Cafe/CafeSystem.h" @@ -110,6 +111,7 @@ enum MAINFRAME_MENU_ID_TOOLS_MEMORY_SEARCHER = 20600, MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER, MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER, + MAINFRAME_MENU_ID_TOOLS_EMULATED_USB_DEVICES, // cpu // cpu->timer speed MAINFRAME_MENU_ID_TIMER_SPEED_1X = 20700, @@ -188,6 +190,7 @@ EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_INPUT, MainWindow::OnOptionsInput) EVT_MENU(MAINFRAME_MENU_ID_TOOLS_MEMORY_SEARCHER, MainWindow::OnToolsInput) EVT_MENU(MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER, MainWindow::OnToolsInput) EVT_MENU(MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER, MainWindow::OnToolsInput) +EVT_MENU(MAINFRAME_MENU_ID_TOOLS_EMULATED_USB_DEVICES, MainWindow::OnToolsInput) // cpu menu EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_8X, MainWindow::OnDebugSetting) EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_4X, MainWindow::OnDebugSetting) @@ -1515,6 +1518,29 @@ void MainWindow::OnToolsInput(wxCommandEvent& event) }); m_title_manager->Show(); } + break; + } + case MAINFRAME_MENU_ID_TOOLS_EMULATED_USB_DEVICES: + { + if (m_usb_devices) + { + m_usb_devices->Show(true); + m_usb_devices->Raise(); + m_usb_devices->SetFocus(); + } + else + { + m_usb_devices = new EmulatedUSBDeviceFrame(this); + m_usb_devices->Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& event) + { + if (event.CanVeto()) { + m_usb_devices->Show(false); + event.Veto(); + } + }); + m_usb_devices->Show(true); + } + break; } break; } @@ -2166,6 +2192,7 @@ void MainWindow::RecreateMenu() m_memorySearcherMenuItem->Enable(false); toolsMenu->Append(MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER, _("&Title Manager")); toolsMenu->Append(MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER, _("&Download Manager")); + toolsMenu->Append(MAINFRAME_MENU_ID_TOOLS_EMULATED_USB_DEVICES, _("&Emulated USB Devices")); m_menuBar->Append(toolsMenu, _("&Tools")); diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index 7191df12..dd4d0d0d 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -22,6 +22,7 @@ struct GameEntry; class DiscordPresence; class TitleManager; class GraphicPacksWindow2; +class EmulatedUSBDeviceFrame; class wxLaunchGameEvent; wxDECLARE_EVENT(wxEVT_LAUNCH_GAME, wxLaunchGameEvent); @@ -164,6 +165,7 @@ private: MemorySearcherTool* m_toolWindow = nullptr; TitleManager* m_title_manager = nullptr; + EmulatedUSBDeviceFrame* m_usb_devices = nullptr; PadViewFrame* m_padView = nullptr; GraphicPacksWindow2* m_graphic_pack_window = nullptr; From aefbb918beb8718af8f190a73018ff63bf801d95 Mon Sep 17 00:00:00 2001 From: Joshua de Reeper <joshua@dereeper.co.nz> Date: Fri, 28 Jun 2024 14:44:49 +0100 Subject: [PATCH 137/299] nsyshid: Skylander emulation fixes and code cleanup (#1244) --- src/Cafe/OS/libs/nsyshid/Skylander.cpp | 79 +++++++++++++++++-- src/Cafe/OS/libs/nsyshid/Skylander.h | 17 ++-- .../EmulatedUSBDeviceFrame.cpp | 78 ++++-------------- .../EmulatedUSBDeviceFrame.h | 6 +- 4 files changed, 101 insertions(+), 79 deletions(-) diff --git a/src/Cafe/OS/libs/nsyshid/Skylander.cpp b/src/Cafe/OS/libs/nsyshid/Skylander.cpp index 3123d14d..241e1969 100644 --- a/src/Cafe/OS/libs/nsyshid/Skylander.cpp +++ b/src/Cafe/OS/libs/nsyshid/Skylander.cpp @@ -1,5 +1,7 @@ #include "Skylander.h" +#include <random> + #include "nsyshid.h" #include "Backend.h" @@ -9,8 +11,8 @@ namespace nsyshid { SkylanderUSB g_skyportal; - const std::map<const std::pair<const uint16, const uint16>, const std::string> - listSkylanders = { + const std::map<const std::pair<const uint16, const uint16>, const char*> + s_listSkylanders = { {{0, 0x0000}, "Whirlwind"}, {{0, 0x1801}, "Series 2 Whirlwind"}, {{0, 0x1C02}, "Polar Whirlwind"}, @@ -845,6 +847,49 @@ namespace nsyshid return false; } + bool SkylanderUSB::CreateSkylander(fs::path pathName, uint16 skyId, uint16 skyVar) + { + FileStream* skyFile(FileStream::createFile2(pathName)); + if (!skyFile) + { + return false; + } + + std::array<uint8, BLOCK_COUNT * BLOCK_SIZE> data{}; + + uint32 first_block = 0x690F0F0F; + uint32 other_blocks = 0x69080F7F; + memcpy(&data[0x36], &first_block, sizeof(first_block)); + for (size_t index = 1; index < 0x10; index++) + { + memcpy(&data[(index * 0x40) + 0x36], &other_blocks, sizeof(other_blocks)); + } + std::random_device rd; + std::mt19937 mt(rd()); + std::uniform_int_distribution<int> dist(0, 255); + data[0] = dist(mt); + data[1] = dist(mt); + data[2] = dist(mt); + data[3] = dist(mt); + data[4] = data[0] ^ data[1] ^ data[2] ^ data[3]; + data[5] = 0x81; + data[6] = 0x01; + data[7] = 0x0F; + + memcpy(&data[0x10], &skyId, sizeof(skyId)); + memcpy(&data[0x1C], &skyVar, sizeof(skyVar)); + + uint16 crc = nsyshid::g_skyportal.SkylanderCRC16(0xFFFF, data.data(), 0x1E); + + memcpy(&data[0x1E], &crc, sizeof(crc)); + + skyFile->writeData(data.data(), data.size()); + + delete skyFile; + + return true; + } + void SkylanderUSB::QueryBlock(uint8 skyNum, uint8 block, uint8* replyBuf) { std::lock_guard lock(m_skyMutex); @@ -865,7 +910,7 @@ namespace nsyshid } void SkylanderUSB::WriteBlock(uint8 skyNum, uint8 block, - const uint8* toWriteBuf, uint8* replyBuf) + const uint8* toWriteBuf, uint8* replyBuf) { std::lock_guard lock(m_skyMutex); @@ -919,21 +964,39 @@ namespace nsyshid status |= s.status; } interruptResponse = {0x53, 0x00, 0x00, 0x00, 0x00, m_interruptCounter++, - active, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00}; + active, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00}; memcpy(&interruptResponse[1], &status, sizeof(status)); } return interruptResponse; } + std::string SkylanderUSB::FindSkylander(uint16 skyId, uint16 skyVar) + { + for (const auto& it : GetListSkylanders()) + { + if(it.first.first == skyId && it.first.second == skyVar) + { + return it.second; + } + } + return fmt::format("Unknown ({} {})", skyId, skyVar); + } + + std::map<const std::pair<const uint16, const uint16>, const char*> SkylanderUSB::GetListSkylanders() + { + return s_listSkylanders; + } + void SkylanderUSB::Skylander::Save() { if (!skyFile) return; + skyFile->SetPosition(0); skyFile->writeData(data.data(), data.size()); } } // namespace nsyshid \ No newline at end of file diff --git a/src/Cafe/OS/libs/nsyshid/Skylander.h b/src/Cafe/OS/libs/nsyshid/Skylander.h index dfa370dc..a1ca7f8f 100644 --- a/src/Cafe/OS/libs/nsyshid/Skylander.h +++ b/src/Cafe/OS/libs/nsyshid/Skylander.h @@ -1,3 +1,5 @@ +#pragma once + #include <mutex> #include "nsyshid.h" @@ -36,7 +38,10 @@ namespace nsyshid bool m_IsOpened; }; - extern const std::map<const std::pair<const uint16, const uint16>, const std::string> listSkylanders; + constexpr uint16 BLOCK_COUNT = 0x40; + constexpr uint16 BLOCK_SIZE = 0x10; + constexpr uint16 FIGURE_SIZE = BLOCK_COUNT * BLOCK_SIZE; + constexpr uint8 MAX_SKYLANDERS = 16; class SkylanderUSB { public: @@ -45,7 +50,7 @@ namespace nsyshid std::unique_ptr<FileStream> skyFile; uint8 status = 0; std::queue<uint8> queuedStatus; - std::array<uint8, 0x40 * 0x10> data{}; + std::array<uint8, BLOCK_COUNT * BLOCK_SIZE> data{}; uint32 lastId = 0; void Save(); @@ -74,16 +79,19 @@ namespace nsyshid std::array<uint8, 64> GetStatus(); void QueryBlock(uint8 skyNum, uint8 block, uint8* replyBuf); void WriteBlock(uint8 skyNum, uint8 block, const uint8* toWriteBuf, - uint8* replyBuf); + uint8* replyBuf); uint8 LoadSkylander(uint8* buf, std::unique_ptr<FileStream> file); bool RemoveSkylander(uint8 skyNum); + bool CreateSkylander(fs::path pathName, uint16 skyId, uint16 skyVar); uint16 SkylanderCRC16(uint16 initValue, const uint8* buffer, uint32 size); + static std::map<const std::pair<const uint16, const uint16>, const char*> GetListSkylanders(); + std::string FindSkylander(uint16 skyId, uint16 skyVar); protected: std::mutex m_skyMutex; std::mutex m_queryMutex; - std::array<Skylander, 16> m_skylanders; + std::array<Skylander, MAX_SKYLANDERS> m_skylanders; private: std::queue<std::array<uint8, 64>> m_queries; @@ -92,7 +100,6 @@ namespace nsyshid SkylanderLEDColor m_colorRight = {}; SkylanderLEDColor m_colorLeft = {}; SkylanderLEDColor m_colorTrap = {}; - }; extern SkylanderUSB g_skyportal; } // namespace nsyshid \ No newline at end of file diff --git a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp index 58c1823c..f43c3690 100644 --- a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp +++ b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp @@ -1,7 +1,6 @@ #include "gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h" #include <algorithm> -#include <random> #include "config/CemuConfig.h" #include "gui/helpers/wxHelpers.h" @@ -9,7 +8,6 @@ #include "util/helpers/helpers.h" #include "Cafe/OS/libs/nsyshid/nsyshid.h" -#include "Cafe/OS/libs/nsyshid/Skylander.h" #include "Common/FileStream.h" @@ -75,7 +73,7 @@ wxPanel* EmulatedUSBDeviceFrame::AddSkylanderPage(wxNotebook* notebook) }); row->Add(m_emulatePortal, 1, wxEXPAND | wxALL, 2); boxSizer->Add(row, 1, wxEXPAND | wxALL, 2); - for (int i = 0; i < 16; i++) + for (int i = 0; i < nsyshid::MAX_SKYLANDERS; i++) { boxSizer->Add(AddSkylanderRow(i, box), 1, wxEXPAND | wxALL, 2); } @@ -153,7 +151,7 @@ void EmulatedUSBDeviceFrame::LoadSkylanderPath(uint8 slot, wxString path) uint16 skyVar = uint16(fileData[0x1D]) << 8 | uint16(fileData[0x1C]); uint8 portalSlot = nsyshid::g_skyportal.LoadSkylander(fileData.data(), - std::move(skyFile)); + std::move(skyFile)); m_skySlots[slot] = std::tuple(portalSlot, skyId, skyVar); UpdateSkylanderEdits(); } @@ -189,11 +187,11 @@ CreateSkylanderDialog::CreateSkylanderDialog(wxWindow* parent, uint8 slot) auto* comboBox = new wxComboBox(this, wxID_ANY); comboBox->Append("---Select---", reinterpret_cast<void*>(0xFFFFFFFF)); wxArrayString filterlist; - for (auto it = nsyshid::listSkylanders.begin(); it != nsyshid::listSkylanders.end(); it++) + for (const auto& it : nsyshid::g_skyportal.GetListSkylanders()) { - const uint32 variant = uint32(uint32(it->first.first) << 16) | uint32(it->first.second); - comboBox->Append(it->second, reinterpret_cast<void*>(variant)); - filterlist.Add(it->second); + const uint32 variant = uint32(uint32(it.first.first) << 16) | uint32(it.first.second); + comboBox->Append(it.second, reinterpret_cast<void*>(variant)); + filterlist.Add(it.second); } comboBox->SetSelection(0); bool enabled = comboBox->AutoComplete(filterlist); @@ -233,16 +231,7 @@ CreateSkylanderDialog::CreateSkylanderDialog(wxWindow* parent, uint8 slot) } uint16 skyId = longSkyId & 0xFFFF; uint16 skyVar = longSkyVar & 0xFFFF; - const auto foundSky = nsyshid::listSkylanders.find(std::make_pair(skyId, skyVar)); - wxString predefName; - if (foundSky != nsyshid::listSkylanders.end()) - { - predefName = foundSky->second + ".sky"; - } - else - { - predefName = wxString::Format(_("Unknown(%i %i).sky"), skyId, skyVar); - } + wxString predefName = nsyshid::g_skyportal.FindSkylander(skyId, skyVar) + ".sky"; wxFileDialog saveFileDialog(this, _("Create Skylander file"), "", predefName, "SKY files (*.sky)|*.sky", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); @@ -251,46 +240,15 @@ CreateSkylanderDialog::CreateSkylanderDialog(wxWindow* parent, uint8 slot) return; m_filePath = saveFileDialog.GetPath(); - - wxFileOutputStream output_stream(saveFileDialog.GetPath()); - if (!output_stream.IsOk()) + + if(!nsyshid::g_skyportal.CreateSkylander(_utf8ToPath(m_filePath.utf8_string()), skyId, skyVar)) { - wxMessageDialog saveError(this, "Error Creating Skylander File"); + wxMessageDialog errorMessage(this, "Failed to create file"); + errorMessage.ShowModal(); + this->EndModal(0); return; } - std::array<uint8, 0x40 * 0x10> data{}; - - uint32 first_block = 0x690F0F0F; - uint32 other_blocks = 0x69080F7F; - memcpy(&data[0x36], &first_block, sizeof(first_block)); - for (size_t index = 1; index < 0x10; index++) - { - memcpy(&data[(index * 0x40) + 0x36], &other_blocks, sizeof(other_blocks)); - } - std::random_device rd; - std::mt19937 mt(rd()); - std::uniform_int_distribution<int> dist(0, 255); - data[0] = dist(mt); - data[1] = dist(mt); - data[2] = dist(mt); - data[3] = dist(mt); - data[4] = data[0] ^ data[1] ^ data[2] ^ data[3]; - data[5] = 0x81; - data[6] = 0x01; - data[7] = 0x0F; - - memcpy(&data[0x10], &skyId, sizeof(skyId)); - memcpy(&data[0x1C], &skyVar, sizeof(skyVar)); - - uint16 crc = nsyshid::g_skyportal.SkylanderCRC16(0xFFFF, data.data(), 0x1E); - - memcpy(&data[0x1E], &crc, sizeof(crc)); - - output_stream.SeekO(0); - output_stream.WriteAll(data.data(), data.size()); - output_stream.Close(); - this->EndModal(1); }); auto* cancelButton = new wxButton(this, wxID_ANY, _("Cancel")); @@ -328,21 +286,13 @@ wxString CreateSkylanderDialog::GetFilePath() const void EmulatedUSBDeviceFrame::UpdateSkylanderEdits() { - for (auto i = 0; i < 16; i++) + for (auto i = 0; i < nsyshid::MAX_SKYLANDERS; i++) { std::string displayString; if (auto sd = m_skySlots[i]) { auto [portalSlot, skyId, skyVar] = sd.value(); - auto foundSky = nsyshid::listSkylanders.find(std::make_pair(skyId, skyVar)); - if (foundSky != nsyshid::listSkylanders.end()) - { - displayString = foundSky->second; - } - else - { - displayString = fmt::format("Unknown (Id:{} Var:{})", skyId, skyVar); - } + displayString = nsyshid::g_skyportal.FindSkylander(skyId, skyVar); } else { diff --git a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h index 6acb7da8..8988cb8a 100644 --- a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h +++ b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h @@ -5,6 +5,8 @@ #include <wx/dialog.h> #include <wx/frame.h> +#include "Cafe/OS/libs/nsyshid/Skylander.h" + class wxBoxSizer; class wxCheckBox; class wxFlexGridSizer; @@ -21,8 +23,8 @@ class EmulatedUSBDeviceFrame : public wxFrame { private: wxCheckBox* m_emulatePortal; - std::array<wxTextCtrl*, 16> m_skylanderSlots; - std::array<std::optional<std::tuple<uint8, uint16, uint16>>, 16> m_skySlots; + std::array<wxTextCtrl*, nsyshid::MAX_SKYLANDERS> m_skylanderSlots; + std::array<std::optional<std::tuple<uint8, uint16, uint16>>, nsyshid::MAX_SKYLANDERS> m_skySlots; wxPanel* AddSkylanderPage(wxNotebook* notebook); wxBoxSizer* AddSkylanderRow(uint8 row_number, wxStaticBox* box); From 64b0b85ed5fe15d17cfa6b5fce0044000b013a07 Mon Sep 17 00:00:00 2001 From: Colin Kinloch <colin@kinlo.ch> Date: Sat, 29 Jun 2024 21:31:47 +0100 Subject: [PATCH 138/299] Create GamePad window at correct size (#1247) Don't change the size on canvas initialization --- src/gui/PadViewFrame.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/gui/PadViewFrame.cpp b/src/gui/PadViewFrame.cpp index f2da2ca7..e7cc5c18 100644 --- a/src/gui/PadViewFrame.cpp +++ b/src/gui/PadViewFrame.cpp @@ -20,18 +20,24 @@ extern WindowInfo g_window_info; +#define PAD_MIN_WIDTH 320 +#define PAD_MIN_HEIGHT 180 + 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) + : wxFrame(nullptr, wxID_ANY, _("GamePad View"), wxDefaultPosition, wxDefaultSize, 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 }); + SetMinClientSize({ PAD_MIN_WIDTH, PAD_MIN_HEIGHT }); 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.restored_pad_width >= PAD_MIN_WIDTH && g_window_info.restored_pad_height >= PAD_MIN_HEIGHT) + SetClientSize({ g_window_info.restored_pad_width, g_window_info.restored_pad_height }); + else + SetClientSize(wxSize(854, 480)); if (g_window_info.pad_maximized) Maximize(); @@ -72,7 +78,7 @@ void PadViewFrame::InitializeRenderCanvas() m_render_canvas = GLCanvas_Create(this, wxSize(854, 480), false); sizer->Add(m_render_canvas, 1, wxEXPAND, 0, nullptr); } - SetSizerAndFit(sizer); + SetSizer(sizer); Layout(); m_render_canvas->Bind(wxEVT_KEY_UP, &PadViewFrame::OnKeyUp, this); From 5209677f2fd661949778c071287f5005b57bc8fe Mon Sep 17 00:00:00 2001 From: Joshua de Reeper <joshua@dereeper.co.nz> Date: Tue, 2 Jul 2024 02:32:37 +0100 Subject: [PATCH 139/299] nsyshid: Add SetProtocol and SetReport support for libusb backend (#1243) --- src/Cafe/OS/libs/nsyshid/Backend.h | 2 +- src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp | 161 +++++++++++++----- src/Cafe/OS/libs/nsyshid/BackendLibusb.h | 15 +- .../OS/libs/nsyshid/BackendWindowsHID.cpp | 2 +- src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h | 2 +- src/Cafe/OS/libs/nsyshid/Skylander.cpp | 2 +- src/Cafe/OS/libs/nsyshid/Skylander.h | 2 +- src/Cafe/OS/libs/nsyshid/nsyshid.cpp | 4 +- 8 files changed, 137 insertions(+), 53 deletions(-) diff --git a/src/Cafe/OS/libs/nsyshid/Backend.h b/src/Cafe/OS/libs/nsyshid/Backend.h index 03232736..12362773 100644 --- a/src/Cafe/OS/libs/nsyshid/Backend.h +++ b/src/Cafe/OS/libs/nsyshid/Backend.h @@ -135,7 +135,7 @@ namespace nsyshid uint8* output, uint32 outputMaxLength) = 0; - virtual bool SetProtocol(uint32 ifIndef, uint32 protocol) = 0; + virtual bool SetProtocol(uint8 ifIndex, uint8 protocol) = 0; virtual bool SetReport(ReportMessage* message) = 0; }; diff --git a/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp b/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp index 6701d780..7548c998 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp +++ b/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp @@ -230,6 +230,17 @@ namespace nsyshid::backend::libusb return nullptr; } + std::pair<int, ConfigDescriptor> MakeConfigDescriptor(libusb_device* device, uint8 config_num) + { + libusb_config_descriptor* descriptor = nullptr; + const int ret = libusb_get_config_descriptor(device, config_num, &descriptor); + if (ret == LIBUSB_SUCCESS) + return {ret, ConfigDescriptor{descriptor, libusb_free_config_descriptor}}; + + return {ret, ConfigDescriptor{nullptr, [](auto) { + }}}; + } + std::shared_ptr<Device> BackendLibusb::CheckAndCreateDevice(libusb_device* dev) { struct libusb_device_descriptor desc; @@ -241,6 +252,25 @@ namespace nsyshid::backend::libusb ret); return nullptr; } + std::vector<ConfigDescriptor> config_descriptors{}; + for (uint8 i = 0; i < desc.bNumConfigurations; ++i) + { + auto [ret, config_descriptor] = MakeConfigDescriptor(dev, i); + if (ret != LIBUSB_SUCCESS || !config_descriptor) + { + cemuLog_log(LogType::Force, "Failed to make config descriptor {} for {:04x}:{:04x}: {}", + i, desc.idVendor, desc.idProduct, libusb_error_name(ret)); + } + else + { + config_descriptors.emplace_back(std::move(config_descriptor)); + } + } + if (desc.idVendor == 0x0e6f && desc.idProduct == 0x0241) + { + cemuLog_logDebug(LogType::Force, + "nsyshid::BackendLibusb::CheckAndCreateDevice(): lego dimensions portal detected"); + } auto device = std::make_shared<DeviceLibusb>(m_ctx, desc.idVendor, desc.idProduct, @@ -248,7 +278,8 @@ namespace nsyshid::backend::libusb 2, 0, libusb_get_bus_number(dev), - libusb_get_device_address(dev)); + libusb_get_device_address(dev), + std::move(config_descriptors)); // figure out device endpoints if (!FindDefaultDeviceEndpoints(dev, device->m_libusbHasEndpointIn, @@ -330,7 +361,8 @@ namespace nsyshid::backend::libusb uint8 interfaceSubClass, uint8 protocol, uint8 libusbBusNumber, - uint8 libusbDeviceAddress) + uint8 libusbDeviceAddress, + std::vector<ConfigDescriptor> configs) : Device(vendorId, productId, interfaceIndex, @@ -346,6 +378,7 @@ namespace nsyshid::backend::libusb m_libusbHasEndpointOut(false), m_libusbEndpointOut(0) { + m_config_descriptors = std::move(configs); } DeviceLibusb::~DeviceLibusb() @@ -413,20 +446,8 @@ namespace nsyshid::backend::libusb } this->m_handleInUseCounter = 0; } - if (libusb_kernel_driver_active(this->m_libusbHandle, 0) == 1) { - cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::open(): kernel driver active"); - if (libusb_detach_kernel_driver(this->m_libusbHandle, 0) == 0) - { - cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::open(): kernel driver detached"); - } - else - { - cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::open(): failed to detach kernel driver"); - } - } - { - int ret = libusb_claim_interface(this->m_libusbHandle, 0); + int ret = ClaimAllInterfaces(0); if (ret != 0) { cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::open(): cannot claim interface"); @@ -680,7 +701,65 @@ namespace nsyshid::backend::libusb return false; } - bool DeviceLibusb::SetProtocol(uint32 ifIndex, uint32 protocol) + template<typename Configs, typename Function> + static int DoForEachInterface(const Configs& configs, uint8 config_num, Function action) + { + int ret = LIBUSB_ERROR_NOT_FOUND; + if (configs.size() <= config_num || !configs[config_num]) + return ret; + for (uint8 i = 0; i < configs[config_num]->bNumInterfaces; ++i) + { + ret = action(i); + if (ret < LIBUSB_SUCCESS) + break; + } + return ret; + } + + int DeviceLibusb::ClaimAllInterfaces(uint8 config_num) + { + const int ret = DoForEachInterface(m_config_descriptors, config_num, [this](uint8 i) { + if (libusb_kernel_driver_active(this->m_libusbHandle, i)) + { + const int ret2 = libusb_detach_kernel_driver(this->m_libusbHandle, i); + if (ret2 < LIBUSB_SUCCESS && ret2 != LIBUSB_ERROR_NOT_FOUND && + ret2 != LIBUSB_ERROR_NOT_SUPPORTED) + { + cemuLog_log(LogType::Force, "Failed to detach kernel driver {}", libusb_error_name(ret2)); + return ret2; + } + } + return libusb_claim_interface(this->m_libusbHandle, i); + }); + if (ret < LIBUSB_SUCCESS) + { + cemuLog_log(LogType::Force, "Failed to release all interfaces for config {}", config_num); + } + return ret; + } + + int DeviceLibusb::ReleaseAllInterfaces(uint8 config_num) + { + const int ret = DoForEachInterface(m_config_descriptors, config_num, [this](uint8 i) { + return libusb_release_interface(AquireHandleLock()->GetHandle(), i); + }); + if (ret < LIBUSB_SUCCESS && ret != LIBUSB_ERROR_NO_DEVICE && ret != LIBUSB_ERROR_NOT_FOUND) + { + cemuLog_log(LogType::Force, "Failed to release all interfaces for config {}", config_num); + } + return ret; + } + + int DeviceLibusb::ReleaseAllInterfacesForCurrentConfig() + { + int config_num; + const int get_config_ret = libusb_get_configuration(AquireHandleLock()->GetHandle(), &config_num); + if (get_config_ret < LIBUSB_SUCCESS) + return get_config_ret; + return ReleaseAllInterfaces(config_num); + } + + bool DeviceLibusb::SetProtocol(uint8 ifIndex, uint8 protocol) { auto handleLock = AquireHandleLock(); if (!handleLock->IsValid()) @@ -688,24 +767,18 @@ namespace nsyshid::backend::libusb cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetProtocol(): device is not opened"); return false; } + if (m_interfaceIndex != ifIndex) + m_interfaceIndex = ifIndex; - // ToDo: implement this -#if 0 - // is this correct? Discarding "ifIndex" seems like a bad idea - int ret = libusb_set_configuration(handleLock->getHandle(), protocol); - if (ret == 0) { - cemuLog_logDebug(LogType::Force, - "nsyshid::DeviceLibusb::setProtocol(): success"); + ReleaseAllInterfacesForCurrentConfig(); + int ret = libusb_set_configuration(AquireHandleLock()->GetHandle(), protocol); + if (ret == LIBUSB_SUCCESS) + ret = ClaimAllInterfaces(protocol); + + if (ret == LIBUSB_SUCCESS) return true; - } - cemuLog_logDebug(LogType::Force, - "nsyshid::DeviceLibusb::setProtocol(): failed with error code: {}", - ret); - return false; -#endif - // pretend that everything is fine - return true; + return false; } bool DeviceLibusb::SetReport(ReportMessage* message) @@ -717,20 +790,20 @@ namespace nsyshid::backend::libusb return false; } - // ToDo: implement this -#if 0 - // not sure if libusb_control_transfer() is the right candidate for this - int ret = libusb_control_transfer(handleLock->getHandle(), - bmRequestType, - bRequest, - wValue, - wIndex, - message->reportData, - message->length, - timeout); -#endif + int ret = libusb_control_transfer(handleLock->GetHandle(), + LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT, + LIBUSB_REQUEST_SET_CONFIGURATION, + 512, + 0, + message->originalData, + message->originalLength, + 0); - // pretend that everything is fine + if (ret != message->originalLength) + { + cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetReport(): Control Transfer Failed: {}", libusb_error_name(ret)); + return false; + } return true; } diff --git a/src/Cafe/OS/libs/nsyshid/BackendLibusb.h b/src/Cafe/OS/libs/nsyshid/BackendLibusb.h index a8122aff..a7b23769 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendLibusb.h +++ b/src/Cafe/OS/libs/nsyshid/BackendLibusb.h @@ -44,6 +44,11 @@ namespace nsyshid::backend::libusb bool& endpointOutFound, uint8& endpointOut, uint16& endpointOutMaxPacketSize); }; + template<typename T> + using UniquePtr = std::unique_ptr<T, void (*)(T*)>; + + using ConfigDescriptor = UniquePtr<libusb_config_descriptor>; + class DeviceLibusb : public nsyshid::Device { public: DeviceLibusb(libusb_context* ctx, @@ -53,7 +58,8 @@ namespace nsyshid::backend::libusb uint8 interfaceSubClass, uint8 protocol, uint8 libusbBusNumber, - uint8 libusbDeviceAddress); + uint8 libusbDeviceAddress, + std::vector<ConfigDescriptor> configs); ~DeviceLibusb() override; @@ -73,7 +79,11 @@ namespace nsyshid::backend::libusb uint8* output, uint32 outputMaxLength) override; - bool SetProtocol(uint32 ifIndex, uint32 protocol) override; + bool SetProtocol(uint8 ifIndex, uint8 protocol) override; + + int ClaimAllInterfaces(uint8 config_num); + int ReleaseAllInterfaces(uint8 config_num); + int ReleaseAllInterfacesForCurrentConfig(); bool SetReport(ReportMessage* message) override; @@ -92,6 +102,7 @@ namespace nsyshid::backend::libusb std::atomic<sint32> m_handleInUseCounter; std::condition_variable m_handleInUseCounterDecremented; libusb_device_handle* m_libusbHandle; + std::vector<ConfigDescriptor> m_config_descriptors; class HandleLock { public: diff --git a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp index 3cfba26a..44e01399 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp +++ b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp @@ -400,7 +400,7 @@ namespace nsyshid::backend::windows return false; } - bool DeviceWindowsHID::SetProtocol(uint32 ifIndef, uint32 protocol) + bool DeviceWindowsHID::SetProtocol(uint8 ifIndex, uint8 protocol) { // ToDo: implement this // pretend that everything is fine diff --git a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h index 84fe7bda..9a8a78e9 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h +++ b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h @@ -47,7 +47,7 @@ namespace nsyshid::backend::windows bool GetDescriptor(uint8 descType, uint8 descIndex, uint8 lang, uint8* output, uint32 outputMaxLength) override; - bool SetProtocol(uint32 ifIndef, uint32 protocol) override; + bool SetProtocol(uint8 ifIndex, uint8 protocol) override; bool SetReport(ReportMessage* message) override; diff --git a/src/Cafe/OS/libs/nsyshid/Skylander.cpp b/src/Cafe/OS/libs/nsyshid/Skylander.cpp index 241e1969..7f17f8a3 100644 --- a/src/Cafe/OS/libs/nsyshid/Skylander.cpp +++ b/src/Cafe/OS/libs/nsyshid/Skylander.cpp @@ -627,7 +627,7 @@ namespace nsyshid return true; } - bool SkylanderPortalDevice::SetProtocol(uint32 ifIndex, uint32 protocol) + bool SkylanderPortalDevice::SetProtocol(uint8 ifIndex, uint8 protocol) { return true; } diff --git a/src/Cafe/OS/libs/nsyshid/Skylander.h b/src/Cafe/OS/libs/nsyshid/Skylander.h index a1ca7f8f..ae8b5d92 100644 --- a/src/Cafe/OS/libs/nsyshid/Skylander.h +++ b/src/Cafe/OS/libs/nsyshid/Skylander.h @@ -30,7 +30,7 @@ namespace nsyshid uint8* output, uint32 outputMaxLength) override; - bool SetProtocol(uint32 ifIndex, uint32 protocol) override; + bool SetProtocol(uint8 ifIndex, uint8 protocol) override; bool SetReport(ReportMessage* message) override; diff --git a/src/Cafe/OS/libs/nsyshid/nsyshid.cpp b/src/Cafe/OS/libs/nsyshid/nsyshid.cpp index c674b844..99a736d9 100644 --- a/src/Cafe/OS/libs/nsyshid/nsyshid.cpp +++ b/src/Cafe/OS/libs/nsyshid/nsyshid.cpp @@ -379,8 +379,8 @@ namespace nsyshid void export_HIDSetProtocol(PPCInterpreter_t* hCPU) { ppcDefineParamU32(hidHandle, 0); // r3 - ppcDefineParamU32(ifIndex, 1); // r4 - ppcDefineParamU32(protocol, 2); // r5 + ppcDefineParamU8(ifIndex, 1); // r4 + ppcDefineParamU8(protocol, 2); // r5 ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6 ppcDefineParamMPTR(callbackParamMPTR, 4); // r7 cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetProtocol(...)"); From 9d366937cd1c5908e073ecd726a0b12763838ef7 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 7 Jul 2024 08:55:26 +0200 Subject: [PATCH 140/299] Workaround for compiler issue with Visual Studio 17.10 --- src/CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d5843c37..7d64d91b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -56,6 +56,12 @@ add_executable(CemuBin mainLLE.cpp ) +if(MSVC AND MSVC_VERSION EQUAL 1940) + # workaround for an msvc issue on VS 17.10 where generated ILK files are too large + # see https://developercommunity.visualstudio.com/t/After-updating-to-VS-1710-the-size-of-/10665511 + set_target_properties(CemuBin PROPERTIES LINK_FLAGS "/INCREMENTAL:NO") +endif() + if(WIN32) target_sources(CemuBin PRIVATE resource/cemu.rc From 7522c8470ee27d50a68ba662ae721b69018f3a8f Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Fri, 19 Jul 2024 14:24:46 +0200 Subject: [PATCH 141/299] resource: move fontawesome to .rodata (#1259) --- src/resource/embedded/fontawesome.S | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resource/embedded/fontawesome.S b/src/resource/embedded/fontawesome.S index 04da3b24..29b4f93a 100644 --- a/src/resource/embedded/fontawesome.S +++ b/src/resource/embedded/fontawesome.S @@ -1,4 +1,4 @@ -.section .text +.rodata .global g_fontawesome_data, g_fontawesome_size g_fontawesome_data: From 64232ffdbddd39cf14eed0ef457273af67277f3c Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Tue, 23 Jul 2024 03:13:36 +0200 Subject: [PATCH 142/299] Windows default to non-portable + Reworked MLC handling and related UI (#1252) --- src/config/ActiveSettings.cpp | 52 ++-- src/config/ActiveSettings.h | 23 +- src/config/CMakeLists.txt | 4 - src/config/CemuConfig.cpp | 18 -- src/config/CemuConfig.h | 2 +- src/config/PermanentConfig.cpp | 65 ----- src/config/PermanentConfig.h | 18 -- src/config/PermanentStorage.cpp | 76 ------ src/config/PermanentStorage.h | 27 -- src/gui/CemuApp.cpp | 407 +++++++++++++++++++------------ src/gui/CemuApp.h | 13 +- src/gui/GeneralSettings2.cpp | 154 ++++++------ src/gui/GeneralSettings2.h | 3 +- src/gui/GettingStartedDialog.cpp | 224 +++++++---------- src/gui/GettingStartedDialog.h | 34 +-- src/gui/MainWindow.cpp | 30 +-- src/gui/MainWindow.h | 2 - src/main.cpp | 14 +- 18 files changed, 515 insertions(+), 651 deletions(-) delete mode 100644 src/config/PermanentConfig.cpp delete mode 100644 src/config/PermanentConfig.h delete mode 100644 src/config/PermanentStorage.cpp delete mode 100644 src/config/PermanentStorage.h diff --git a/src/config/ActiveSettings.cpp b/src/config/ActiveSettings.cpp index 07e6f16d..560f2986 100644 --- a/src/config/ActiveSettings.cpp +++ b/src/config/ActiveSettings.cpp @@ -7,41 +7,47 @@ #include "config/LaunchSettings.h" #include "util/helpers/helpers.h" -std::set<fs::path> -ActiveSettings::LoadOnce( - const fs::path& executablePath, - const fs::path& userDataPath, - const fs::path& configPath, - const fs::path& cachePath, - const fs::path& dataPath) +void ActiveSettings::SetPaths(bool isPortableMode, + const fs::path& executablePath, + const fs::path& userDataPath, + const fs::path& configPath, + const fs::path& cachePath, + const fs::path& dataPath, + std::set<fs::path>& failedWriteAccess) { + cemu_assert_debug(!s_setPathsCalled); // can only change paths before loading + s_isPortableMode = isPortableMode; s_executable_path = executablePath; s_user_data_path = userDataPath; s_config_path = configPath; s_cache_path = cachePath; s_data_path = dataPath; - std::set<fs::path> failed_write_access; + failedWriteAccess.clear(); for (auto&& path : {userDataPath, configPath, cachePath}) { - if (!fs::exists(path)) - { - std::error_code ec; + std::error_code ec; + if (!fs::exists(path, ec)) fs::create_directories(path, ec); - } if (!TestWriteAccess(path)) { cemuLog_log(LogType::Force, "Failed to write to {}", _pathToUtf8(path)); - failed_write_access.insert(path); + failedWriteAccess.insert(path); } } - s_executable_filename = s_executable_path.filename(); + s_setPathsCalled = true; +} - g_config.SetFilename(GetConfigPath("settings.xml").generic_wstring()); - g_config.Load(); +[[nodiscard]] bool ActiveSettings::IsPortableMode() +{ + return s_isPortableMode; +} + +void ActiveSettings::Init() +{ + cemu_assert_debug(s_setPathsCalled); std::string additionalErrorInfo; s_has_required_online_files = iosuCrypt_checkRequirementsForOnlineMode(additionalErrorInfo) == IOS_CRYPTO_ONLINE_REQ_OK; - return failed_write_access; } bool ActiveSettings::LoadSharedLibrariesEnabled() @@ -229,6 +235,7 @@ bool ActiveSettings::ForceSamplerRoundToPrecision() fs::path ActiveSettings::GetMlcPath() { + cemu_assert_debug(s_setPathsCalled); if(const auto launch_mlc = LaunchSettings::GetMLCPath(); launch_mlc.has_value()) return launch_mlc.value(); @@ -238,6 +245,17 @@ fs::path ActiveSettings::GetMlcPath() return GetDefaultMLCPath(); } +bool ActiveSettings::IsCustomMlcPath() +{ + cemu_assert_debug(s_setPathsCalled); + return !GetConfig().mlc_path.GetValue().empty(); +} + +bool ActiveSettings::IsCommandLineMlcPath() +{ + return LaunchSettings::GetMLCPath().has_value(); +} + fs::path ActiveSettings::GetDefaultMLCPath() { return GetUserDataPath("mlc01"); diff --git a/src/config/ActiveSettings.h b/src/config/ActiveSettings.h index 54052741..e672fbee 100644 --- a/src/config/ActiveSettings.h +++ b/src/config/ActiveSettings.h @@ -34,12 +34,16 @@ private: public: // Set directories and return all directories that failed write access test - static std::set<fs::path> - LoadOnce(const fs::path& executablePath, - const fs::path& userDataPath, - const fs::path& configPath, - const fs::path& cachePath, - const fs::path& dataPath); + static void + SetPaths(bool isPortableMode, + const fs::path& executablePath, + const fs::path& userDataPath, + const fs::path& configPath, + const fs::path& cachePath, + const fs::path& dataPath, + std::set<fs::path>& failedWriteAccess); + + static void Init(); [[nodiscard]] static fs::path GetExecutablePath() { return s_executable_path; } [[nodiscard]] static fs::path GetExecutableFilename() { return s_executable_filename; } @@ -56,11 +60,14 @@ public: template <typename ...TArgs> [[nodiscard]] static fs::path GetMlcPath(TArgs&&... args){ return GetPath(GetMlcPath(), std::forward<TArgs>(args)...); }; + static bool IsCustomMlcPath(); + static bool IsCommandLineMlcPath(); // get mlc path to default cemu root dir/mlc01 [[nodiscard]] static fs::path GetDefaultMLCPath(); private: + inline static bool s_isPortableMode{false}; inline static fs::path s_executable_path; inline static fs::path s_user_data_path; inline static fs::path s_config_path; @@ -70,6 +77,9 @@ private: inline static fs::path s_mlc_path; public: + // can be called before Init + [[nodiscard]] static bool IsPortableMode(); + // general [[nodiscard]] static bool LoadSharedLibrariesEnabled(); [[nodiscard]] static bool DisplayDRCEnabled(); @@ -111,6 +121,7 @@ public: [[nodiscard]] static bool ForceSamplerRoundToPrecision(); private: + inline static bool s_setPathsCalled = false; // dump options inline static bool s_dump_shaders = false; inline static bool s_dump_textures = false; diff --git a/src/config/CMakeLists.txt b/src/config/CMakeLists.txt index f02b95d4..d53e8574 100644 --- a/src/config/CMakeLists.txt +++ b/src/config/CMakeLists.txt @@ -8,10 +8,6 @@ add_library(CemuConfig LaunchSettings.h NetworkSettings.cpp NetworkSettings.h - PermanentConfig.cpp - PermanentConfig.h - PermanentStorage.cpp - PermanentStorage.h XMLConfig.h ) diff --git a/src/config/CemuConfig.cpp b/src/config/CemuConfig.cpp index 8e7cf398..03b12731 100644 --- a/src/config/CemuConfig.cpp +++ b/src/config/CemuConfig.cpp @@ -5,7 +5,6 @@ #include <wx/language.h> -#include "PermanentConfig.h" #include "ActiveSettings.h" XMLCemuConfig_t g_config(L"settings.xml"); @@ -15,23 +14,6 @@ void CemuConfig::SetMLCPath(fs::path path, bool save) mlc_path.SetValue(_pathToUtf8(path)); if(save) g_config.Save(); - - // if custom mlc path has been selected, store it in permanent config - if (path != ActiveSettings::GetDefaultMLCPath()) - { - try - { - auto pconfig = PermanentConfig::Load(); - pconfig.custom_mlc_path = _pathToUtf8(path); - pconfig.Store(); - } - catch (const PSDisabledException&) {} - catch (const std::exception& ex) - { - cemuLog_log(LogType::Force, "can't store custom mlc path in permanent storage: {}", ex.what()); - } - } - Account::RefreshAccounts(); } diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index d0776d2e..3f3da953 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -417,7 +417,7 @@ struct CemuConfig ConfigValue<bool> save_screenshot{true}; ConfigValue<bool> did_show_vulkan_warning{false}; - ConfigValue<bool> did_show_graphic_pack_download{false}; + ConfigValue<bool> did_show_graphic_pack_download{false}; // no longer used but we keep the config value around in case people downgrade Cemu. Despite the name this was used for the Getting Started dialog ConfigValue<bool> did_show_macos_disclaimer{false}; ConfigValue<bool> show_icon_column{ true }; diff --git a/src/config/PermanentConfig.cpp b/src/config/PermanentConfig.cpp deleted file mode 100644 index 20a44d28..00000000 --- a/src/config/PermanentConfig.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "PermanentConfig.h" - -#include "pugixml.hpp" - -#include "PermanentStorage.h" - -struct xml_string_writer : pugi::xml_writer -{ - std::string result; - - void write(const void* data, size_t size) override - { - result.append(static_cast<const char*>(data), size); - } -}; - -std::string PermanentConfig::ToXMLString() const noexcept -{ - pugi::xml_document doc; - doc.append_child(pugi::node_declaration).append_attribute("encoding") = "UTF-8"; - auto root = doc.append_child("config"); - root.append_child("MlcPath").text().set(this->custom_mlc_path.c_str()); - - xml_string_writer writer; - doc.save(writer); - return writer.result; -} - -PermanentConfig PermanentConfig::FromXMLString(std::string_view str) noexcept -{ - PermanentConfig result{}; - - pugi::xml_document doc; - if(doc.load_buffer(str.data(), str.size())) - { - result.custom_mlc_path = doc.select_node("/config/MlcPath").node().text().as_string(); - } - - return result; -} - -PermanentConfig PermanentConfig::Load() -{ - PermanentStorage storage; - - const auto str = storage.ReadFile(kFileName); - if (!str.empty()) - return FromXMLString(str); - - return {}; -} - -bool PermanentConfig::Store() noexcept -{ - try - { - PermanentStorage storage; - storage.WriteStringToFile(kFileName, ToXMLString()); - } - catch (...) - { - return false; - } - return true; -} diff --git a/src/config/PermanentConfig.h b/src/config/PermanentConfig.h deleted file mode 100644 index 8c134747..00000000 --- a/src/config/PermanentConfig.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include "PermanentStorage.h" - -struct PermanentConfig -{ - static constexpr const char* kFileName = "perm_setting.xml"; - - std::string custom_mlc_path; - - [[nodiscard]] std::string ToXMLString() const noexcept; - static PermanentConfig FromXMLString(std::string_view str) noexcept; - - // gets from permanent storage - static PermanentConfig Load(); - // saves to permanent storage - bool Store() noexcept; -}; diff --git a/src/config/PermanentStorage.cpp b/src/config/PermanentStorage.cpp deleted file mode 100644 index e095ff4b..00000000 --- a/src/config/PermanentStorage.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#include "PermanentStorage.h" -#include "config/CemuConfig.h" -#include "util/helpers/SystemException.h" - -PermanentStorage::PermanentStorage() -{ - if (!GetConfig().permanent_storage) - throw PSDisabledException(); - - const char* appdata = std::getenv("LOCALAPPDATA"); - if (!appdata) - throw std::runtime_error("can't get LOCALAPPDATA"); - m_storage_path = appdata; - m_storage_path /= "Cemu"; - - fs::create_directories(m_storage_path); -} - -PermanentStorage::~PermanentStorage() -{ - if (m_remove_storage) - { - std::error_code ec; - fs::remove_all(m_storage_path, ec); - if (ec) - { - SystemException ex(ec); - cemuLog_log(LogType::Force, "can't remove permanent storage: {}", ex.what()); - } - } -} - -void PermanentStorage::ClearAllFiles() const -{ - fs::remove_all(m_storage_path); - fs::create_directories(m_storage_path); -} - -void PermanentStorage::RemoveStorage() -{ - m_remove_storage = true; -} - -void PermanentStorage::WriteStringToFile(std::string_view filename, std::string_view content) -{ - const auto name = m_storage_path.append(filename.data(), filename.data() + filename.size()); - std::ofstream file(name.string()); - file.write(content.data(), (uint32_t)content.size()); -} - -std::string PermanentStorage::ReadFile(std::string_view filename) noexcept -{ - try - { - const auto name = m_storage_path.append(filename.data(), filename.data() + filename.size()); - std::ifstream file(name, std::ios::in | std::ios::ate); - if (!file.is_open()) - return {}; - - const auto end = file.tellg(); - file.seekg(0, std::ios::beg); - const auto file_size = end - file.tellg(); - if (file_size == 0) - return {}; - - std::string result; - result.resize(file_size); - file.read(result.data(), file_size); - return result; - } - catch (...) - { - return {}; - } - -} diff --git a/src/config/PermanentStorage.h b/src/config/PermanentStorage.h deleted file mode 100644 index 3cda3d6d..00000000 --- a/src/config/PermanentStorage.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -// disabled by config -class PSDisabledException : public std::runtime_error -{ -public: - PSDisabledException() - : std::runtime_error("permanent storage is disabled by user") {} -}; - -class PermanentStorage -{ -public: - PermanentStorage(); - ~PermanentStorage(); - - void ClearAllFiles() const; - // flags storage to be removed on destruction - void RemoveStorage(); - - void WriteStringToFile(std::string_view filename, std::string_view content); - std::string ReadFile(std::string_view filename) noexcept; - -private: - fs::path m_storage_path; - bool m_remove_storage = false; -}; \ No newline at end of file diff --git a/src/gui/CemuApp.cpp b/src/gui/CemuApp.cpp index 86d81e43..baa83888 100644 --- a/src/gui/CemuApp.cpp +++ b/src/gui/CemuApp.cpp @@ -3,11 +3,11 @@ #include "gui/wxgui.h" #include "config/CemuConfig.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" +#include "Cafe/HW/Latte/Core/LatteOverlay.h" #include "gui/guiWrapper.h" #include "config/ActiveSettings.h" +#include "config/LaunchSettings.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" @@ -30,7 +30,10 @@ wxIMPLEMENT_APP_NO_MAIN(CemuApp); extern WindowInfo g_window_info; extern std::shared_mutex g_mutex; -int mainEmulatorHLE(); +// forward declarations from main.cpp +void UnitTests(); +void CemuCommonInit(); + void HandlePostUpdate(); // Translation strings to extract for gettext: void unused_translation_dummy() @@ -54,34 +57,86 @@ void unused_translation_dummy() void(_("unknown")); } -bool CemuApp::OnInit() +#if BOOST_OS_WINDOWS +#include <shlobj_core.h> +fs::path GetAppDataRoamingPath() { + PWSTR path = nullptr; + HRESULT result = SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &path); + if (result != S_OK || !path) + { + if (path) + CoTaskMemFree(path); + return {}; + } + std::string appDataPath = boost::nowide::narrow(path); + CoTaskMemFree(path); + return _utf8ToPath(appDataPath); +} +#endif + +#if BOOST_OS_WINDOWS +void CemuApp::DeterminePaths(std::set<fs::path>& failedWriteAccess) // for Windows +{ + std::error_code ec; + bool isPortable = false; fs::path user_data_path, config_path, cache_path, data_path; auto standardPaths = wxStandardPaths::Get(); fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath())); + fs::path portablePath = exePath.parent_path() / "portable"; + data_path = exePath.parent_path(); // the data path is always the same as the exe path + if (fs::exists(portablePath, ec)) + { + isPortable = true; + user_data_path = config_path = cache_path = portablePath; + } + else + { + fs::path roamingPath = GetAppDataRoamingPath() / "Cemu"; + user_data_path = config_path = cache_path = roamingPath; + } + // on Windows Cemu used to be portable by default prior to 2.0-89 + // to remain backwards compatible with old installations we check for settings.xml in the Cemu directory + // if it exists, we use the exe path as the portable directory + if(!isPortable) // lower priority than portable directory + { + if (fs::exists(exePath.parent_path() / "settings.xml", ec)) + { + isPortable = true; + user_data_path = config_path = cache_path = exePath.parent_path(); + } + } + ActiveSettings::SetPaths(isPortable, exePath, user_data_path, config_path, cache_path, data_path, failedWriteAccess); +} +#endif + #if BOOST_OS_LINUX +void CemuApp::DeterminePaths(std::set<fs::path>& failedWriteAccess) // for Linux +{ + std::error_code ec; + bool isPortable = false; + fs::path user_data_path, config_path, cache_path, data_path; + auto standardPaths = wxStandardPaths::Get(); + fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath())); + fs::path portablePath = exePath.parent_path() / "portable"; // GetExecutablePath returns the AppImage's temporary mount location wxString appImagePath; if (wxGetEnv(("APPIMAGE"), &appImagePath)) - exePath = wxHelper::MakeFSPath(appImagePath); -#endif - // Try a portable path first, if it exists. - user_data_path = config_path = cache_path = data_path = exePath.parent_path() / "portable"; -#if BOOST_OS_MACOS - // If run from an app bundle, use its parent directory. - fs::path appPath = exePath.parent_path().parent_path().parent_path(); - if (appPath.extension() == ".app") - user_data_path = config_path = cache_path = data_path = appPath.parent_path() / "portable"; -#endif - - if (!fs::exists(user_data_path)) { -#if BOOST_OS_WINDOWS - user_data_path = config_path = cache_path = data_path = exePath.parent_path(); -#else + exePath = wxHelper::MakeFSPath(appImagePath); + portablePath = exePath.parent_path() / "portable"; + } + if (fs::exists(portablePath, ec)) + { + isPortable = true; + user_data_path = config_path = cache_path = portablePath; + // in portable mode assume the data directories (resources, gameProfiles/default/) are next to the executable + data_path = exePath.parent_path(); + } + else + { SetAppName("Cemu"); - wxString appName=GetAppName(); -#if BOOST_OS_LINUX + wxString appName = GetAppName(); standardPaths.SetFileLayout(wxStandardPaths::FileLayout::FileLayout_XDG); auto getEnvDir = [&](const wxString& varName, const wxString& defaultValue) { @@ -90,33 +145,151 @@ bool CemuApp::OnInit() return defaultValue; return dir; }; - wxString homeDir=wxFileName::GetHomeDir(); + wxString homeDir = wxFileName::GetHomeDir(); user_data_path = (getEnvDir(wxS("XDG_DATA_HOME"), homeDir + wxS("/.local/share")) + "/" + appName).ToStdString(); config_path = (getEnvDir(wxS("XDG_CONFIG_HOME"), homeDir + wxS("/.config")) + "/" + appName).ToStdString(); -#else - user_data_path = config_path = standardPaths.GetUserDataDir().ToStdString(); -#endif data_path = standardPaths.GetDataDir().ToStdString(); cache_path = standardPaths.GetUserDir(wxStandardPaths::Dir::Dir_Cache).ToStdString(); cache_path /= appName.ToStdString(); -#endif } + ActiveSettings::SetPaths(isPortable, exePath, user_data_path, config_path, cache_path, data_path, failedWriteAccess); +} +#endif - auto failed_write_access = ActiveSettings::LoadOnce(exePath, user_data_path, config_path, cache_path, data_path); - for (auto&& path : failed_write_access) - wxMessageBox(formatWxString(_("Cemu can't write to {}!"), wxString::FromUTF8(_pathToUtf8(path))), - _("Warning"), wxOK | wxCENTRE | wxICON_EXCLAMATION, nullptr); +#if BOOST_OS_MACOS +void CemuApp::DeterminePaths(std::set<fs::path>& failedWriteAccess) // for MacOS +{ + std::error_code ec; + bool isPortable = false; + fs::path user_data_path, config_path, cache_path, data_path; + auto standardPaths = wxStandardPaths::Get(); + fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath())); + // If run from an app bundle, use its parent directory + fs::path appPath = exePath.parent_path().parent_path().parent_path(); + fs::path portablePath = appPath.extension() == ".app" ? appPath.parent_path() / "portable" : exePath.parent_path() / "portable"; + if (fs::exists(portablePath, ec)) + { + isPortable = true; + user_data_path = config_path = cache_path = portablePath; + data_path = exePath.parent_path(); + } + else + { + SetAppName("Cemu"); + wxString appName = GetAppName(); + user_data_path = config_path = standardPaths.GetUserDataDir().ToStdString(); + data_path = standardPaths.GetDataDir().ToStdString(); + cache_path = standardPaths.GetUserDir(wxStandardPaths::Dir::Dir_Cache).ToStdString(); + cache_path /= appName.ToStdString(); + } + ActiveSettings::SetPaths(isPortable, exePath, user_data_path, config_path, cache_path, data_path, failedWriteAccess); +} +#endif + +// create default MLC files or quit if it fails +void CemuApp::InitializeNewMLCOrFail(fs::path mlc) +{ + if( CemuApp::CreateDefaultMLCFiles(mlc) ) + return; // all good + cemu_assert_debug(!ActiveSettings::IsCustomMlcPath()); // should not be possible? + + if(ActiveSettings::IsCommandLineMlcPath() || ActiveSettings::IsCustomMlcPath()) + { + // tell user that the custom path is not writable + wxMessageBox(formatWxString(_("Cemu failed to write to the custom mlc directory.\nThe path is:\n{}"), wxHelper::FromPath(mlc)), _("Error"), wxOK | wxCENTRE | wxICON_ERROR); + exit(0); + } + wxMessageBox(formatWxString(_("Cemu failed to write to the mlc directory.\nThe path is:\n{}"), wxHelper::FromPath(mlc)), _("Error"), wxOK | wxCENTRE | wxICON_ERROR); + exit(0); +} + +void CemuApp::InitializeExistingMLCOrFail(fs::path mlc) +{ + if(CreateDefaultMLCFiles(mlc)) + return; // all good + // failed to write mlc files + if(ActiveSettings::IsCommandLineMlcPath() || ActiveSettings::IsCustomMlcPath()) + { + // tell user that the custom path is not writable + // if it's a command line path then just quit. Otherwise ask if user wants to reset the path + if(ActiveSettings::IsCommandLineMlcPath()) + { + wxMessageBox(formatWxString(_("Cemu failed to write to the custom mlc directory.\nThe path is:\n{}"), wxHelper::FromPath(mlc)), _("Error"), wxOK | wxCENTRE | wxICON_ERROR); + exit(0); + } + // ask user if they want to reset the path + const wxString message = formatWxString(_("Cemu failed to write to the custom mlc directory.\n\nThe path is:\n{}\n\nCemu cannot start without a valid mlc path. Do you want to reset the path? You can later change it again in the General Settings."), + _pathToUtf8(mlc)); + wxMessageDialog dialog(nullptr, message, _("Error"), wxCENTRE | wxYES_NO | wxICON_WARNING); + dialog.SetYesNoLabels(_("Reset path"), _("Exit")); + const auto dialogResult = dialog.ShowModal(); + if (dialogResult == wxID_NO) + exit(0); + else // reset path + { + GetConfig().mlc_path = ""; + g_config.Save(); + } + } +} + +bool CemuApp::OnInit() +{ + std::set<fs::path> failedWriteAccess; + DeterminePaths(failedWriteAccess); + // make sure default cemu directories exist + CreateDefaultCemuFiles(); + + g_config.SetFilename(ActiveSettings::GetConfigPath("settings.xml").generic_wstring()); + + std::error_code ec; + bool isFirstStart = !fs::exists(ActiveSettings::GetConfigPath("settings.xml"), ec); NetworkConfig::LoadOnce(); - g_config.Load(); + if(!isFirstStart) + { + g_config.Load(); + LocalizeUI(static_cast<wxLanguage>(GetConfig().language == wxLANGUAGE_DEFAULT ? wxLocale::GetSystemLanguage() : GetConfig().language.GetValue())); + } + else + { + LocalizeUI(static_cast<wxLanguage>(wxLocale::GetSystemLanguage())); + } + for (auto&& path : failedWriteAccess) + { + wxMessageBox(formatWxString(_("Cemu can't write to {}!"), wxString::FromUTF8(_pathToUtf8(path))), + _("Warning"), wxOK | wxCENTRE | wxICON_EXCLAMATION, nullptr); + } + + if (isFirstStart) + { + // show the getting started dialog + GettingStartedDialog dia(nullptr); + dia.ShowModal(); + // make sure config is created. Gfx pack UI and input UI may create it earlier already, but we still want to update it + g_config.Save(); + // create mlc, on failure the user can quit here. So do this after the Getting Started dialog + InitializeNewMLCOrFail(ActiveSettings::GetMlcPath()); + } + else + { + // check if mlc is valid and recreate default files + InitializeExistingMLCOrFail(ActiveSettings::GetMlcPath()); + } + + ActiveSettings::Init(); // this is a bit of a misnomer, right now this call only loads certs for online play. In the future we should move the logic to a more appropriate place HandlePostUpdate(); - mainEmulatorHLE(); + + LatteOverlay_init(); + // run a couple of tests if in non-release mode +#ifdef CEMU_DEBUG_ASSERT + UnitTests(); +#endif + CemuCommonInit(); wxInitAllImageHandlers(); - LocalizeUI(); - // fill colour db wxTheColourDatabase->AddColour("ERROR", wxColour(0xCC, 0, 0)); wxTheColourDatabase->AddColour("SUCCESS", wxColour(0, 0xbb, 0)); @@ -135,15 +308,8 @@ bool CemuApp::OnInit() Bind(wxEVT_ACTIVATE_APP, &CemuApp::ActivateApp, this); 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; @@ -230,22 +396,22 @@ std::vector<const wxLanguageInfo *> CemuApp::GetLanguages() const { return availableLanguages; } -void CemuApp::LocalizeUI() +void CemuApp::LocalizeUI(wxLanguage languageToUse) { std::unique_ptr<wxTranslations> translationsMgr(new wxTranslations()); m_availableTranslations = GetAvailableTranslationLanguages(translationsMgr.get()); - const sint32 configuredLanguage = GetConfig().language; bool isTranslationAvailable = std::any_of(m_availableTranslations.begin(), m_availableTranslations.end(), - [configuredLanguage](const wxLanguageInfo* info) { return info->Language == configuredLanguage; }); - if (configuredLanguage == wxLANGUAGE_DEFAULT || isTranslationAvailable) + [languageToUse](const wxLanguageInfo* info) { return info->Language == languageToUse; }); + if (languageToUse == wxLANGUAGE_DEFAULT || isTranslationAvailable) { - translationsMgr->SetLanguage(static_cast<wxLanguage>(configuredLanguage)); + translationsMgr->SetLanguage(static_cast<wxLanguage>(languageToUse)); translationsMgr->AddCatalog("cemu"); - if (translationsMgr->IsLoaded("cemu") && wxLocale::IsAvailable(configuredLanguage)) - m_locale.Init(configuredLanguage); - + if (translationsMgr->IsLoaded("cemu") && wxLocale::IsAvailable(languageToUse)) + { + m_locale.Init(languageToUse); + } // This must be run after wxLocale::Init, as the latter sets up its own wxTranslations instance which we want to override wxTranslations::Set(translationsMgr.release()); } @@ -264,55 +430,47 @@ std::vector<const wxLanguageInfo*> CemuApp::GetAvailableTranslationLanguages(wxT return languages; } -void CemuApp::CreateDefaultFiles(bool first_start) +bool CemuApp::CheckMLCPath(const fs::path& mlc) { std::error_code ec; - fs::path mlc = ActiveSettings::GetMlcPath(); - // check for mlc01 folder missing if custom path has been set - if (!fs::exists(mlc, ec) && !first_start) - { - const wxString message = formatWxString(_("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?"), - _pathToUtf8(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 = ActiveSettings::GetMlcPath(); - } - else - { - GetConfig().mlc_path = ""; - g_config.Save(); - } - } + if (!fs::exists(mlc, ec)) + return false; + if (!fs::exists(mlc / "usr", ec) || !fs::exists(mlc / "sys", ec)) + return false; + return true; +} +bool CemuApp::CreateDefaultMLCFiles(const fs::path& mlc) +{ + auto CreateDirectoriesIfNotExist = [](const fs::path& path) + { + std::error_code ec; + if (!fs::exists(path, ec)) + return fs::create_directories(path, ec); + return true; + }; + // list of directories to create + const fs::path directories[] = { + mlc, + mlc / "sys", + mlc / "usr", + mlc / "usr/title/00050000", // base + mlc / "usr/title/0005000c", // dlc + mlc / "usr/title/0005000e", // update + mlc / "usr/save/00050010/1004a000/user/common/db", // Mii Maker save folders {0x500101004A000, 0x500101004A100, 0x500101004A200} + mlc / "usr/save/00050010/1004a100/user/common/db", + mlc / "usr/save/00050010/1004a200/user/common/db", + mlc / "sys/title/0005001b/1005c000/content" // lang files + }; + for(auto& path : directories) + { + if(!CreateDirectoriesIfNotExist(path)) + return false; + } // create sys/usr folder in mlc01 try { - const auto sysFolder = fs::path(mlc).append("sys"); - fs::create_directories(sysFolder); - - const auto usrFolder = fs::path(mlc).append("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("usr/save/00050010/1004a000/user/common/db")); - fs::create_directories(fs::path(mlc).append("usr/save/00050010/1004a100/user/common/db")); - fs::create_directories(fs::path(mlc).append("usr/save/00050010/1004a200/user/common/db")); - - // lang files const auto langDir = fs::path(mlc).append("sys/title/0005001b/1005c000/content"); - fs::create_directories(langDir); - auto langFile = fs::path(langDir).append("language.txt"); if (!fs::exists(langFile)) { @@ -346,18 +504,13 @@ void CemuApp::CreateDefaultFiles(bool first_start) } catch (const std::exception& ex) { - wxString errorMsg = formatWxString(_("Couldn't create a required mlc01 subfolder or file!\n\nError: {0}\nTarget path:\n{1}"), ex.what(), _pathToUtf8(mlc)); - -#if BOOST_OS_WINDOWS - const DWORD lastError = GetLastError(); - if (lastError != ERROR_SUCCESS) - errorMsg << fmt::format("\n\n{}", GetSystemErrorMessage(lastError)); -#endif - - wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); - exit(0); + return false; } + return true; +} +void CemuApp::CreateDefaultCemuFiles() +{ // cemu directories try { @@ -384,58 +537,6 @@ void CemuApp::CreateDefaultFiles(bool first_start) } } - -bool CemuApp::TrySelectMLCPath(fs::path path) -{ - if (path.empty()) - path = ActiveSettings::GetDefaultMLCPath(); - - if (!TestWriteAccess(path)) - return false; - - GetConfig().SetMLCPath(path); - CemuApp::CreateDefaultFiles(); - - // update TitleList and SaveList scanner with new MLC path - CafeTitleList::SetMLCPath(path); - CafeTitleList::Refresh(); - CafeSaveList::SetMLCPath(path); - CafeSaveList::Refresh(); - return true; -} - -bool CemuApp::SelectMLCPath(wxWindow* parent) -{ - auto& config = GetConfig(); - - fs::path default_path; - if (fs::exists(_utf8ToPath(config.mlc_path.GetValue()))) - default_path = _utf8ToPath(config.mlc_path.GetValue()); - - // try until users selects a valid path or aborts - while(true) - { - wxDirDialog path_dialog(parent, _("Select a mlc directory"), wxHelper::FromPath(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 (!TrySelectMLCPath(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; - } - - return true; - } - - return false; -} - void CemuApp::ActivateApp(wxActivateEvent& event) { g_window_info.app_active = event.GetActive(); diff --git a/src/gui/CemuApp.h b/src/gui/CemuApp.h index cfdab0a2..b73d627d 100644 --- a/src/gui/CemuApp.h +++ b/src/gui/CemuApp.h @@ -15,13 +15,18 @@ public: std::vector<const wxLanguageInfo*> GetLanguages() const; - static void CreateDefaultFiles(bool first_start = false); - static bool TrySelectMLCPath(fs::path path); - static bool SelectMLCPath(wxWindow* parent = nullptr); + static bool CheckMLCPath(const fs::path& mlc); + static bool CreateDefaultMLCFiles(const fs::path& mlc); + static void CreateDefaultCemuFiles(); + static void InitializeNewMLCOrFail(fs::path mlc); + static void InitializeExistingMLCOrFail(fs::path mlc); private: + void LocalizeUI(wxLanguage languageToUse); + + void DeterminePaths(std::set<fs::path>& failedWriteAccess); + void ActivateApp(wxActivateEvent& event); - void LocalizeUI(); static std::vector<const wxLanguageInfo*> GetAvailableTranslationLanguages(wxTranslations* translationsMgr); MainWindow* m_mainFrame = nullptr; diff --git a/src/gui/GeneralSettings2.cpp b/src/gui/GeneralSettings2.cpp index c0b54949..08395cd3 100644 --- a/src/gui/GeneralSettings2.cpp +++ b/src/gui/GeneralSettings2.cpp @@ -32,7 +32,6 @@ #include <boost/tokenizer.hpp> #include "util/helpers/SystemException.h" #include "gui/dialogs/CreateAccount/wxCreateAccountDialog.h" -#include "config/PermanentStorage.h" #if BOOST_OS_WINDOWS #include <VersionHelpers.h> @@ -176,19 +175,15 @@ wxPanel* GeneralSettings2::AddGeneralPage(wxNotebook* notebook) m_save_screenshot->SetToolTip(_("Pressing the screenshot key (F12) will save a screenshot directly to the screenshots folder")); second_row->Add(m_save_screenshot, 0, botflag, 5); - m_permanent_storage = new wxCheckBox(box, wxID_ANY, _("Use permanent storage")); - m_permanent_storage->SetToolTip(_("Cemu will remember your custom mlc path in %LOCALAPPDATA%/Cemu for new installations.")); - second_row->Add(m_permanent_storage, 0, botflag, 5); - second_row->AddSpacer(10); m_disable_screensaver = new wxCheckBox(box, wxID_ANY, _("Disable screen saver")); m_disable_screensaver->SetToolTip(_("Prevents the system from activating the screen saver or going to sleep while running a game.")); second_row->Add(m_disable_screensaver, 0, botflag, 5); - // Enable/disable feral interactive gamemode + // Enable/disable feral interactive gamemode #if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE) - m_feral_gamemode = new wxCheckBox(box, wxID_ANY, _("Enable Feral GameMode")); - m_feral_gamemode->SetToolTip(_("Use FeralInteractive GameMode if installed.")); - second_row->Add(m_feral_gamemode, 0, botflag, 5); + m_feral_gamemode = new wxCheckBox(box, wxID_ANY, _("Enable Feral GameMode")); + m_feral_gamemode->SetToolTip(_("Use FeralInteractive GameMode if installed.")); + second_row->Add(m_feral_gamemode, 0, botflag, 5); #endif // temporary workaround because feature crashes on macOS @@ -203,23 +198,33 @@ wxPanel* GeneralSettings2::AddGeneralPage(wxNotebook* notebook) } { - auto* box = new wxStaticBox(panel, wxID_ANY, _("MLC Path")); - auto* box_sizer = new wxStaticBoxSizer(box, wxHORIZONTAL); + auto* outerMlcBox = new wxStaticBox(panel, wxID_ANY, _("Custom MLC path")); - m_mlc_path = new wxTextCtrl(box, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY); + auto* box_sizer_mlc = new wxStaticBoxSizer(outerMlcBox, wxVERTICAL); + box_sizer_mlc->Add(new wxStaticText(box_sizer_mlc->GetStaticBox(), wxID_ANY, _("You can configure a custom path for the emulated internal Wii U storage (MLC).\nThis is where Cemu stores saves, accounts and other Wii U system files."), wxDefaultPosition, wxDefaultSize, 0), 0, wxALL, 5); + + auto* mlcPathLineSizer = new wxBoxSizer(wxHORIZONTAL); + + m_mlc_path = new wxTextCtrl(outerMlcBox, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY); m_mlc_path->SetMinSize(wxSize(150, -1)); - m_mlc_path->Bind(wxEVT_CHAR, &GeneralSettings2::OnMLCPathChar, this); m_mlc_path->SetToolTip(_("The mlc directory contains your save games and installed game update/dlc data")); - box_sizer->Add(m_mlc_path, 1, wxALL | wxEXPAND, 5); + mlcPathLineSizer->Add(m_mlc_path, 1, wxALL | wxEXPAND, 5); - auto* change_path = new wxButton(box, wxID_ANY, "..."); - change_path->Bind(wxEVT_BUTTON, &GeneralSettings2::OnMLCPathSelect, this); - change_path->SetToolTip(_("Select a custom mlc path\nThe mlc path is used to store Wii U related files like save games, game updates and dlc data")); - box_sizer->Add(change_path, 0, wxALL, 5); + auto* changePath = new wxButton(outerMlcBox, wxID_ANY, "Change"); + changePath->Bind(wxEVT_BUTTON, &GeneralSettings2::OnMLCPathSelect, this); + mlcPathLineSizer->Add(changePath, 0, wxALL, 5); if (LaunchSettings::GetMLCPath().has_value()) - change_path->Disable(); - general_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); + changePath->Disable(); + + auto* clearPath = new wxButton(outerMlcBox, wxID_ANY, "Clear custom path"); + clearPath->Bind(wxEVT_BUTTON, &GeneralSettings2::OnMLCPathClear, this); + mlcPathLineSizer->Add(clearPath, 0, wxALL, 5); + if (LaunchSettings::GetMLCPath().has_value() || !ActiveSettings::IsCustomMlcPath()) + clearPath->Disable(); + + box_sizer_mlc->Add(mlcPathLineSizer, 0, wxEXPAND, 5); + general_panel_sizer->Add(box_sizer_mlc, 0, wxEXPAND | wxALL, 5); } { @@ -897,39 +902,12 @@ void GeneralSettings2::StoreConfig() #if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE) config.feral_gamemode = m_feral_gamemode->IsChecked(); #endif - const bool use_ps = m_permanent_storage->IsChecked(); - if(use_ps) - { - config.permanent_storage = use_ps; - try - { - - PermanentStorage storage; - storage.RemoveStorage(); - } - catch (...) {} - } - else - { - try - { - // delete permanent storage - PermanentStorage storage; - storage.RemoveStorage(); - } - catch (...) {} - config.permanent_storage = use_ps; - } - config.disable_screensaver = m_disable_screensaver->IsChecked(); // Toggle while a game is running if (CafeSystem::IsTitleRunning()) { ScreenSaver::SetInhibit(config.disable_screensaver); } - - if (!LaunchSettings::GetMLCPath().has_value()) - config.SetMLCPath(wxHelper::MakeFSPath(m_mlc_path->GetValue()), false); // -1 is default wx widget value -> set to dummy 0 so mainwindow and padwindow will update it config.window_position = m_save_window_position_size->IsChecked() ? Vector2i{ 0,0 } : Vector2i{-1,-1}; @@ -1560,7 +1538,6 @@ void GeneralSettings2::ApplyConfig() m_auto_update->SetValue(config.check_update); m_save_screenshot->SetValue(config.save_screenshot); - m_permanent_storage->SetValue(config.permanent_storage); m_disable_screensaver->SetValue(config.disable_screensaver); #if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE) m_feral_gamemode->SetValue(config.feral_gamemode); @@ -1570,6 +1547,7 @@ void GeneralSettings2::ApplyConfig() m_disable_screensaver->SetValue(false); #endif + m_game_paths->Clear(); for (auto& path : config.game_paths) { m_game_paths->Append(to_wxString(path)); @@ -1985,34 +1963,70 @@ void GeneralSettings2::OnAccountServiceChanged(wxCommandEvent& event) void GeneralSettings2::OnMLCPathSelect(wxCommandEvent& event) { - if (!CemuApp::SelectMLCPath(this)) + if(CafeSystem::IsTitleRunning()) + { + wxMessageBox(_("Can't change MLC path while a game is running!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); return; - - m_mlc_path->SetValue(wxHelper::FromPath(ActiveSettings::GetMlcPath())); - m_reload_gamelist = true; - m_mlc_modified = true; + } + // show directory dialog + wxDirDialog path_dialog(this, _("Select MLC directory"), wxEmptyString, wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST); + if (path_dialog.ShowModal() != wxID_OK || path_dialog.GetPath().empty()) + return; + // check if the choosen MLC path is an already initialized MLC location + fs::path newMlc = wxHelper::MakeFSPath(path_dialog.GetPath()); + if(CemuApp::CheckMLCPath(newMlc)) + { + // ask user if they are sure they want to use this folder and let them know that accounts and saves wont transfer + wxString message = _("Note that changing the MLC location will not transfer any accounts or save files. Are you sure you want to change the path?"); + wxMessageDialog dialog(this, message, _("Warning"), wxYES_NO | wxCENTRE | wxICON_WARNING); + if(dialog.ShowModal() == wxID_NO) + return; + if( !CemuApp::CreateDefaultMLCFiles(newMlc) ) // creating also acts as a check for read+write access + { + wxMessageBox(_("Failed to create default MLC files in the selected directory. The MLC path has not been changed"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + return; + } + } + else + { + // ask user if they want to create a new mlc structure at the choosen location + wxString message = _("The selected directory does not contain the expected MLC structure. Do you want to create a new MLC structure in this directory?\nNote that changing the MLC location will not transfer any accounts or save files."); + wxMessageDialog dialog(this, message, _("Warning"), wxYES_NO | wxCENTRE | wxICON_WARNING); + if( !CemuApp::CreateDefaultMLCFiles(newMlc) ) + { + wxMessageBox(_("Failed to create default MLC files in the selected directory. The MLC path has not been changed"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + return; + } + } + // update MLC path and store any other modified settings + GetConfig().SetMLCPath(newMlc); + StoreConfig(); + wxMessageBox(_("Cemu needs to be restarted for the changes to take effect."), _("Information"), wxOK | wxCENTRE | wxICON_INFORMATION, this); + // close settings and then cemu + wxCloseEvent closeEvent(wxEVT_CLOSE_WINDOW); + wxPostEvent(this, closeEvent); + wxPostEvent(GetParent(), closeEvent); } -void GeneralSettings2::OnMLCPathChar(wxKeyEvent& event) +void GeneralSettings2::OnMLCPathClear(wxCommandEvent& event) { - if (LaunchSettings::GetMLCPath().has_value()) - return; - - if(event.GetKeyCode() == WXK_DELETE || event.GetKeyCode() == WXK_BACK) + if(CafeSystem::IsTitleRunning()) { - fs::path newPath = ""; - if(!CemuApp::TrySelectMLCPath(newPath)) - { - const auto res = wxMessageBox(_("The default MLC path is inaccessible.\nDo you want to select a different path?"), _("Error"), wxYES_NO | wxCENTRE | wxICON_ERROR); - if (res == wxYES && CemuApp::SelectMLCPath(this)) - newPath = ActiveSettings::GetMlcPath(); - else - return; - } - m_mlc_path->SetValue(wxHelper::FromPath(newPath)); - m_reload_gamelist = true; - m_mlc_modified = true; + wxMessageBox(_("Can't change MLC path while a game is running!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this); + return; } + wxString message = _("Note that changing the MLC location will not transfer any accounts or save files. Are you sure you want to change the path?"); + wxMessageDialog dialog(this, message, _("Warning"), wxYES_NO | wxCENTRE | wxICON_WARNING); + if(dialog.ShowModal() == wxID_NO) + return; + GetConfig().SetMLCPath(""); + StoreConfig(); + g_config.Save(); + wxMessageBox(_("Cemu needs to be restarted for the changes to take effect."), _("Information"), wxOK | wxCENTRE | wxICON_INFORMATION, this); + // close settings and then cemu + wxCloseEvent closeEvent(wxEVT_CLOSE_WINDOW); + wxPostEvent(this, closeEvent); + wxPostEvent(GetParent(), closeEvent); } void GeneralSettings2::OnShowOnlineValidator(wxCommandEvent& event) diff --git a/src/gui/GeneralSettings2.h b/src/gui/GeneralSettings2.h index b34c9222..a3429fa1 100644 --- a/src/gui/GeneralSettings2.h +++ b/src/gui/GeneralSettings2.h @@ -42,7 +42,6 @@ private: wxCheckBox* m_save_padwindow_position_size; wxCheckBox* m_discord_presence, *m_fullscreen_menubar; wxCheckBox* m_auto_update, *m_save_screenshot; - wxCheckBox* m_permanent_storage; wxCheckBox* m_disable_screensaver; #if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE) wxCheckBox* m_feral_gamemode; @@ -96,7 +95,7 @@ private: void OnRemovePathClicked(wxCommandEvent& event); void OnActiveAccountChanged(wxCommandEvent& event); void OnMLCPathSelect(wxCommandEvent& event); - void OnMLCPathChar(wxKeyEvent& event); + void OnMLCPathClear(wxCommandEvent& event); void OnShowOnlineValidator(wxCommandEvent& event); void OnAccountServiceChanged(wxCommandEvent& event); static wxString GetOnlineAccountErrorMessage(OnlineAccountError error); diff --git a/src/gui/GettingStartedDialog.cpp b/src/gui/GettingStartedDialog.cpp index bfd206b1..22426cf2 100644 --- a/src/gui/GettingStartedDialog.cpp +++ b/src/gui/GettingStartedDialog.cpp @@ -4,6 +4,7 @@ #include <wx/filepicker.h> #include <wx/statbox.h> #include <wx/msgdlg.h> +#include <wx/radiobox.h> #include "config/ActiveSettings.h" #include "gui/CemuApp.h" @@ -11,7 +12,6 @@ #include "gui/GraphicPacksWindow2.h" #include "gui/input/InputSettings2.h" #include "config/CemuConfig.h" -#include "config/PermanentConfig.h" #include "Cafe/TitleList/TitleList.h" @@ -21,75 +21,100 @@ #include "wxHelper.h" +wxDEFINE_EVENT(EVT_REFRESH_FIRST_PAGE, wxCommandEvent); // used to refresh the first page after the language change + wxPanel* GettingStartedDialog::CreatePage1() { - auto* result = new wxPanel(m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + auto* mainPanel = 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); - + sizer->Add(new wxStaticBitmap(mainPanel, wxID_ANY, wxICON(M_WND_ICON128)), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + m_page1.staticText11 = new wxStaticText(mainPanel, 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_page1.staticText11->Wrap(-1); + sizer->Add(m_page1.staticText11, 0, wxALL, 5); page1_sizer->Add(sizer, 0, wxALL | wxEXPAND, 5); } + if(ActiveSettings::IsPortableMode()) { - 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_page1.portableModeInfoText = new wxStaticText(mainPanel, wxID_ANY, _("Cemu is running in portable mode")); + m_page1.portableModeInfoText->Show(true); + page1_sizer->Add(m_page1.portableModeInfoText, 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); } + // language selection +#if 0 { - auto* sizer = new wxStaticBoxSizer(wxVERTICAL, result, _("Game paths")); + m_page1.languageBoxSizer = new wxStaticBoxSizer(wxVERTICAL, mainPanel, _("Language")); + m_page1.languageText = new wxStaticText(m_page1.languageBoxSizer->GetStaticBox(), wxID_ANY, _("Select the language you want to use in Cemu")); + m_page1.languageBoxSizer->Add(m_page1.languageText, 0, wxALL, 5); - 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); + wxString language_choices[] = { _("Default") }; + wxChoice* m_language = new wxChoice(m_page1.languageBoxSizer->GetStaticBox(), wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(language_choices), language_choices); + m_language->SetSelection(0); + + for (const auto& language : wxGetApp().GetLanguages()) + { + m_language->Append(language->DescriptionNative); + } + + m_language->SetSelection(0); + m_page1.languageBoxSizer->Add(m_language, 0, wxALL | wxEXPAND, 5); + + page1_sizer->Add(m_page1.languageBoxSizer, 0, wxALL | wxEXPAND, 5); + + m_language->Bind(wxEVT_CHOICE, [this, m_language](const auto&) + { + const auto language = m_language->GetStringSelection(); + auto selection = m_language->GetSelection(); + if (selection == 0) + GetConfig().language = wxLANGUAGE_DEFAULT; + else + { + auto* app = (CemuApp*)wxTheApp; + const auto language = m_language->GetStringSelection(); + for (const auto& lang : app->GetLanguages()) + { + if (lang->DescriptionNative == language) + { + app->LocalizeUI(static_cast<wxLanguage>(lang->Language)); + wxCommandEvent event(EVT_REFRESH_FIRST_PAGE); + wxPostEvent(this, event); + break; + } + } + } + }); + } +#endif + + { + m_page1.gamePathBoxSizer = new wxStaticBoxSizer(wxVERTICAL, mainPanel, _("Game paths")); + m_page1.gamePathText = new wxStaticText(m_page1.gamePathBoxSizer->GetStaticBox(), wxID_ANY, _("The game path is scanned by Cemu to automatically locate your games, game updates and DLCs. We recommend creating a dedicated directory in which\nyou place all your Wii U game files. Additional paths can be set later in Cemu's general settings. All common Wii U game formats are supported by Cemu.")); + m_page1.gamePathBoxSizer->Add(m_page1.gamePathText, 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_page1.gamePathText2 = new wxStaticText(m_page1.gamePathBoxSizer->GetStaticBox(), wxID_ANY, _("Game path")); + game_path_sizer->Add(m_page1.gamePathText2, 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); + m_page1.gamePathPicker = new wxDirPickerCtrl(m_page1.gamePathBoxSizer->GetStaticBox(), wxID_ANY, wxEmptyString, _("Select a folder")); + game_path_sizer->Add(m_page1.gamePathPicker, 1, wxALL, 5); - sizer->Add(game_path_sizer, 0, wxEXPAND, 5); + m_page1.gamePathBoxSizer->Add(game_path_sizer, 0, wxEXPAND, 5); - page1_sizer->Add(sizer, 0, wxALL | wxEXPAND, 5); + page1_sizer->Add(m_page1.gamePathBoxSizer, 0, wxALL | wxEXPAND, 5); } { - auto* sizer = new wxStaticBoxSizer(wxVERTICAL, result, _("Graphic packs")); + auto* sizer = new wxStaticBoxSizer(wxVERTICAL, mainPanel, _("Graphic packs && mods")); - 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); + sizer->Add(new wxStaticText(sizer->GetStaticBox(), wxID_ANY, _("Graphic packs improve games by offering the ability to change resolution, increase FPS, tweak visuals or add gameplay modifications.\nGet started by opening the graphic packs configuration window.\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); + auto* download_gp = new wxButton(sizer->GetStaticBox(), wxID_ANY, _("Download and configure graphic packs")); + download_gp->Bind(wxEVT_BUTTON, &GettingStartedDialog::OnConfigureGPs, this); sizer->Add(download_gp, 0, wxALIGN_CENTER | wxALL, 5); page1_sizer->Add(sizer, 0, wxALL | wxEXPAND, 5); @@ -102,16 +127,15 @@ wxPanel* GettingStartedDialog::CreatePage1() sizer->SetFlexibleDirection(wxBOTH); sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_ALL); - auto* next = new wxButton(result, wxID_ANY, _("Next"), wxDefaultPosition, wxDefaultSize, 0); + auto* next = new wxButton(mainPanel, 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; + mainPanel->SetSizer(page1_sizer); + return mainPanel; } wxPanel* GettingStartedDialog::CreatePage2() @@ -138,17 +162,17 @@ wxPanel* GettingStartedDialog::CreatePage2() 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_page2.fullscreenCheckbox = new wxCheckBox(sizer->GetStaticBox(), wxID_ANY, _("Start games with fullscreen")); + option_sizer->Add(m_page2.fullscreenCheckbox, 0, wxALL, 5); - m_separate = new wxCheckBox(sizer->GetStaticBox(), wxID_ANY, _("Open separate pad screen")); - option_sizer->Add(m_separate, 0, wxALL, 5); + m_page2.separateCheckbox = new wxCheckBox(sizer->GetStaticBox(), wxID_ANY, _("Open separate pad screen")); + option_sizer->Add(m_page2.separateCheckbox, 0, wxALL, 5); - m_update = new wxCheckBox(sizer->GetStaticBox(), wxID_ANY, _("Automatically check for updates")); - option_sizer->Add(m_update, 0, wxALL, 5); + m_page2.updateCheckbox = new wxCheckBox(sizer->GetStaticBox(), wxID_ANY, _("Automatically check for updates")); + option_sizer->Add(m_page2.updateCheckbox, 0, wxALL, 5); #if BOOST_OS_LINUX if (!std::getenv("APPIMAGE")) { - m_update->Disable(); + m_page2.updateCheckbox->Disable(); } #endif sizer->Add(option_sizer, 1, wxEXPAND, 5); @@ -162,10 +186,6 @@ wxPanel* GettingStartedDialog::CreatePage2() 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); @@ -184,23 +204,9 @@ wxPanel* GettingStartedDialog::CreatePage2() 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 (...) {} + m_page2.fullscreenCheckbox->SetValue(config.fullscreen.GetValue()); + m_page2.updateCheckbox->SetValue(config.check_update.GetValue()); + m_page2.separateCheckbox->SetValue(config.pad_open.GetValue()); } void GettingStartedDialog::UpdateWindowSize() @@ -219,46 +225,25 @@ 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(); + config.fullscreen = m_page2.fullscreenCheckbox->GetValue(); + config.check_update = m_page2.updateCheckbox->GetValue(); + config.pad_open = m_page2.separateCheckbox->GetValue(); - const fs::path gamePath = wxHelper::MakeFSPath(m_game_path->GetPath()); - if (!gamePath.empty() && fs::exists(gamePath)) + const fs::path gamePath = wxHelper::MakeFSPath(m_page1.gamePathPicker->GetPath()); + std::error_code ec; + if (!gamePath.empty() && fs::exists(gamePath, ec)) { 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(_pathToUtf8(gamePath)); - 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, false); - m_mlc_changed = true; - } - - g_config.Save(); - - if(m_mlc_changed) - CemuApp::CreateDefaultFiles(); - - CafeTitleList::ClearScanPaths(); - for (auto& it : GetConfig().game_paths) - CafeTitleList::AddScanPath(_utf8ToPath(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); @@ -274,24 +259,18 @@ GettingStartedDialog::GettingStartedDialog(wxWindow* parent) this->SetSizer(sizer); this->Centre(wxBOTH); this->Bind(wxEVT_CLOSE_WINDOW, &GettingStartedDialog::OnClose, this); - + ApplySettings(); UpdateWindowSize(); } -void GettingStartedDialog::OnDownloadGPs(wxCommandEvent& event) +void GettingStartedDialog::OnConfigureGPs(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(); - } + GraphicPacksWindow2 window(this, 0); + window.ShowModal(); } void GettingStartedDialog::OnInputSettings(wxCommandEvent& event) @@ -299,20 +278,3 @@ 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(); - } - } -} - diff --git a/src/gui/GettingStartedDialog.h b/src/gui/GettingStartedDialog.h index ec122eab..9dfd69b4 100644 --- a/src/gui/GettingStartedDialog.h +++ b/src/gui/GettingStartedDialog.h @@ -13,9 +13,6 @@ 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(); @@ -23,22 +20,29 @@ private: void UpdateWindowSize(); void OnClose(wxCloseEvent& event); - void OnDownloadGPs(wxCommandEvent& event); + void OnConfigureGPs(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; + struct + { + // header + wxStaticText* staticText11{}; + wxStaticText* portableModeInfoText{}; - bool m_game_path_changed = false; - bool m_mlc_changed = false; + // game path box + wxStaticBoxSizer* gamePathBoxSizer{}; + wxStaticText* gamePathText{}; + wxStaticText* gamePathText2{}; + wxDirPickerCtrl* gamePathPicker{}; + }m_page1; + + struct + { + wxCheckBox* fullscreenCheckbox; + wxCheckBox* separateCheckbox; + wxCheckBox* updateCheckbox; + }m_page2; }; diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 7a4f3174..c83ab16b 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -149,8 +149,6 @@ enum // help MAINFRAME_MENU_ID_HELP_ABOUT = 21700, MAINFRAME_MENU_ID_HELP_UPDATE, - MAINFRAME_MENU_ID_HELP_GETTING_STARTED, - // custom MAINFRAME_ID_TIMER1 = 21800, }; @@ -225,7 +223,6 @@ EVT_MENU(MAINFRAME_MENU_ID_DEBUG_VIEW_TEXTURE_RELATIONS, MainWindow::OnDebugView // help menu EVT_MENU(MAINFRAME_MENU_ID_HELP_ABOUT, MainWindow::OnHelpAbout) EVT_MENU(MAINFRAME_MENU_ID_HELP_UPDATE, MainWindow::OnHelpUpdate) -EVT_MENU(MAINFRAME_MENU_ID_HELP_GETTING_STARTED, MainWindow::OnHelpGettingStarted) // misc EVT_COMMAND(wxID_ANY, wxEVT_REQUEST_GAMELIST_REFRESH, MainWindow::OnRequestGameListRefresh) @@ -418,25 +415,6 @@ wxString MainWindow::GetInitialWindowTitle() return BUILD_VERSION_WITH_NAME_STRING; } -void MainWindow::ShowGettingStartedDialog() -{ - GettingStartedDialog dia(this); - dia.ShowModal(); - if (dia.HasGamePathChanged() || dia.HasMLCChanged()) - m_game_list->ReloadGameEntries(); - - TogglePadView(); - - auto& config = GetConfig(); - m_padViewMenuItem->Check(config.pad_open.GetValue()); - m_fullscreenMenuItem->Check(config.fullscreen.GetValue()); -} - -namespace coreinit -{ - void OSSchedulerEnd(); -}; - void MainWindow::OnClose(wxCloseEvent& event) { wxTheClipboard->Flush(); @@ -2075,11 +2053,6 @@ void MainWindow::OnHelpUpdate(wxCommandEvent& event) test.ShowModal(); } -void MainWindow::OnHelpGettingStarted(wxCommandEvent& event) -{ - ShowGettingStartedDialog(); -} - void MainWindow::RecreateMenu() { if (m_menuBar) @@ -2303,8 +2276,7 @@ void MainWindow::RecreateMenu() if (!std::getenv("APPIMAGE")) { m_check_update_menu->Enable(false); } -#endif - helpMenu->Append(MAINFRAME_MENU_ID_HELP_GETTING_STARTED, _("&Getting started")); +#endif helpMenu->AppendSeparator(); helpMenu->Append(MAINFRAME_MENU_ID_HELP_ABOUT, _("&About Cemu")); diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index dd4d0d0d..beb86f98 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -103,7 +103,6 @@ public: void OnAccountSelect(wxCommandEvent& event); void OnConsoleLanguage(wxCommandEvent& event); void OnHelpAbout(wxCommandEvent& event); - void OnHelpGettingStarted(wxCommandEvent& event); void OnHelpUpdate(wxCommandEvent& event); void OnDebugSetting(wxCommandEvent& event); void OnDebugLoggingToggleFlagGeneric(wxCommandEvent& event); @@ -150,7 +149,6 @@ private: void RecreateMenu(); void UpdateChildWindowTitleRunningState(); static wxString GetInitialWindowTitle(); - void ShowGettingStartedDialog(); bool InstallUpdate(const fs::path& metaFilePath); diff --git a/src/main.cpp b/src/main.cpp index 1ccc2805..ea1df684 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,7 +5,6 @@ #include "Cafe/OS/RPL/rpl.h" #include "Cafe/OS/libs/gx2/GX2.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" -#include "Cafe/HW/Latte/Core/LatteOverlay.h" #include "Cafe/GameProfile/GameProfile.h" #include "Cafe/GraphicPack/GraphicPack2.h" #include "config/CemuConfig.h" @@ -160,7 +159,7 @@ void ExpressionParser_test(); void FSTVolumeTest(); void CRCTest(); -void unitTests() +void UnitTests() { ExpressionParser_test(); gx2CopySurfaceTest(); @@ -169,17 +168,6 @@ void unitTests() CRCTest(); } -int mainEmulatorHLE() -{ - LatteOverlay_init(); - // run a couple of tests if in non-release mode -#ifdef CEMU_DEBUG_ASSERT - unitTests(); -#endif - CemuCommonInit(); - return 0; -} - bool isConsoleConnected = false; void requireConsole() { From a1c1a608d77e6c1f7989127d472c655d3159df62 Mon Sep 17 00:00:00 2001 From: Joshua de Reeper <joshua@dereeper.co.nz> Date: Tue, 23 Jul 2024 02:18:48 +0100 Subject: [PATCH 143/299] nsyshid: Emulate Infinity Base (#1246) --- src/Cafe/CMakeLists.txt | 2 + src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp | 8 + src/Cafe/OS/libs/nsyshid/Infinity.cpp | 1102 +++++++++++++++++ src/Cafe/OS/libs/nsyshid/Infinity.h | 105 ++ src/Cafe/OS/libs/nsyshid/Skylander.cpp | 2 +- src/Cafe/OS/libs/nsyshid/Skylander.h | 8 +- src/config/CemuConfig.cpp | 2 + src/config/CemuConfig.h | 1 + .../EmulatedUSBDeviceFrame.cpp | 252 +++- .../EmulatedUSBDeviceFrame.h | 18 + 10 files changed, 1478 insertions(+), 22 deletions(-) create mode 100644 src/Cafe/OS/libs/nsyshid/Infinity.cpp create mode 100644 src/Cafe/OS/libs/nsyshid/Infinity.h diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index 1583bdd7..0fb7a44b 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -463,6 +463,8 @@ add_library(CemuCafe OS/libs/nsyshid/BackendLibusb.h OS/libs/nsyshid/BackendWindowsHID.cpp OS/libs/nsyshid/BackendWindowsHID.h + OS/libs/nsyshid/Infinity.cpp + OS/libs/nsyshid/Infinity.h OS/libs/nsyshid/Skylander.cpp OS/libs/nsyshid/Skylander.h OS/libs/nsyskbd/nsyskbd.cpp diff --git a/src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp b/src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp index 11a299ed..95eaf06a 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp +++ b/src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp @@ -1,4 +1,5 @@ #include "BackendEmulated.h" +#include "Infinity.h" #include "Skylander.h" #include "config/CemuConfig.h" @@ -25,5 +26,12 @@ namespace nsyshid::backend::emulated auto device = std::make_shared<SkylanderPortalDevice>(); AttachDevice(device); } + if (GetConfig().emulated_usb_devices.emulate_infinity_base && !FindDeviceById(0x0E6F, 0x0129)) + { + cemuLog_logDebug(LogType::Force, "Attaching Emulated Base"); + // Add Infinity Base + auto device = std::make_shared<InfinityBaseDevice>(); + AttachDevice(device); + } } } // namespace nsyshid::backend::emulated \ No newline at end of file diff --git a/src/Cafe/OS/libs/nsyshid/Infinity.cpp b/src/Cafe/OS/libs/nsyshid/Infinity.cpp new file mode 100644 index 00000000..ab44ef4a --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/Infinity.cpp @@ -0,0 +1,1102 @@ +#include "Infinity.h" + +#include <random> + +#include "nsyshid.h" +#include "Backend.h" + +#include "util/crypto/aes128.h" + +#include <openssl/crypto.h> +#include "openssl/sha.h" + +namespace nsyshid +{ + static constexpr std::array<uint8, 32> SHA1_CONSTANT = { + 0xAF, 0x62, 0xD2, 0xEC, 0x04, 0x91, 0x96, 0x8C, 0xC5, 0x2A, 0x1A, 0x71, 0x65, 0xF8, 0x65, 0xFE, + 0x28, 0x63, 0x29, 0x20, 0x44, 0x69, 0x73, 0x6e, 0x65, 0x79, 0x20, 0x32, 0x30, 0x31, 0x33}; + + InfinityUSB g_infinitybase; + + const std::map<const uint32, const std::pair<const uint8, const char*>> s_listFigures = { + {0x0F4241, {1, "Mr. Incredible"}}, + {0x0F4242, {1, "Sulley"}}, + {0x0F4243, {1, "Jack Sparrow"}}, + {0x0F4244, {1, "Lone Ranger"}}, + {0x0F4245, {1, "Tonto"}}, + {0x0F4246, {1, "Lightning McQueen"}}, + {0x0F4247, {1, "Holley Shiftwell"}}, + {0x0F4248, {1, "Buzz Lightyear"}}, + {0x0F4249, {1, "Jessie"}}, + {0x0F424A, {1, "Mike"}}, + {0x0F424B, {1, "Mrs. Incredible"}}, + {0x0F424C, {1, "Hector Barbossa"}}, + {0x0F424D, {1, "Davy Jones"}}, + {0x0F424E, {1, "Randy"}}, + {0x0F424F, {1, "Syndrome"}}, + {0x0F4250, {1, "Woody"}}, + {0x0F4251, {1, "Mater"}}, + {0x0F4252, {1, "Dash"}}, + {0x0F4253, {1, "Violet"}}, + {0x0F4254, {1, "Francesco Bernoulli"}}, + {0x0F4255, {1, "Sorcerer's Apprentice Mickey"}}, + {0x0F4256, {1, "Jack Skellington"}}, + {0x0F4257, {1, "Rapunzel"}}, + {0x0F4258, {1, "Anna"}}, + {0x0F4259, {1, "Elsa"}}, + {0x0F425A, {1, "Phineas"}}, + {0x0F425B, {1, "Agent P"}}, + {0x0F425C, {1, "Wreck-It Ralph"}}, + {0x0F425D, {1, "Vanellope"}}, + {0x0F425E, {1, "Mr. Incredible (Crystal)"}}, + {0x0F425F, {1, "Jack Sparrow (Crystal)"}}, + {0x0F4260, {1, "Sulley (Crystal)"}}, + {0x0F4261, {1, "Lightning McQueen (Crystal)"}}, + {0x0F4262, {1, "Lone Ranger (Crystal)"}}, + {0x0F4263, {1, "Buzz Lightyear (Crystal)"}}, + {0x0F4264, {1, "Agent P (Crystal)"}}, + {0x0F4265, {1, "Sorcerer's Apprentice Mickey (Crystal)"}}, + {0x0F4266, {1, "Buzz Lightyear (Glowing)"}}, + {0x0F42A4, {2, "Captain America"}}, + {0x0F42A5, {2, "Hulk"}}, + {0x0F42A6, {2, "Iron Man"}}, + {0x0F42A7, {2, "Thor"}}, + {0x0F42A8, {2, "Groot"}}, + {0x0F42A9, {2, "Rocket Raccoon"}}, + {0x0F42AA, {2, "Star-Lord"}}, + {0x0F42AB, {2, "Spider-Man"}}, + {0x0F42AC, {2, "Nick Fury"}}, + {0x0F42AD, {2, "Black Widow"}}, + {0x0F42AE, {2, "Hawkeye"}}, + {0x0F42AF, {2, "Drax"}}, + {0x0F42B0, {2, "Gamora"}}, + {0x0F42B1, {2, "Iron Fist"}}, + {0x0F42B2, {2, "Nova"}}, + {0x0F42B3, {2, "Venom"}}, + {0x0F42B4, {2, "Donald Duck"}}, + {0x0F42B5, {2, "Aladdin"}}, + {0x0F42B6, {2, "Stitch"}}, + {0x0F42B7, {2, "Merida"}}, + {0x0F42B8, {2, "Tinker Bell"}}, + {0x0F42B9, {2, "Maleficent"}}, + {0x0F42BA, {2, "Hiro"}}, + {0x0F42BB, {2, "Baymax"}}, + {0x0F42BC, {2, "Loki"}}, + {0x0F42BD, {2, "Ronan"}}, + {0x0F42BE, {2, "Green Goblin"}}, + {0x0F42BF, {2, "Falcon"}}, + {0x0F42C0, {2, "Yondu"}}, + {0x0F42C1, {2, "Jasmine"}}, + {0x0F42C6, {2, "Black Suit Spider-Man"}}, + {0x0F42D6, {3, "Sam Flynn"}}, + {0x0F42D7, {3, "Quorra"}}, + {0x0F4308, {3, "Anakin Skywalker"}}, + {0x0F4309, {3, "Obi-Wan Kenobi"}}, + {0x0F430A, {3, "Yoda"}}, + {0x0F430B, {3, "Ahsoka Tano"}}, + {0x0F430C, {3, "Darth Maul"}}, + {0x0F430E, {3, "Luke Skywalker"}}, + {0x0F430F, {3, "Han Solo"}}, + {0x0F4310, {3, "Princess Leia"}}, + {0x0F4311, {3, "Chewbacca"}}, + {0x0F4312, {3, "Darth Vader"}}, + {0x0F4313, {3, "Boba Fett"}}, + {0x0F4314, {3, "Ezra Bridger"}}, + {0x0F4315, {3, "Kanan Jarrus"}}, + {0x0F4316, {3, "Sabine Wren"}}, + {0x0F4317, {3, "Zeb Orrelios"}}, + {0x0F4318, {3, "Joy"}}, + {0x0F4319, {3, "Anger"}}, + {0x0F431A, {3, "Fear"}}, + {0x0F431B, {3, "Sadness"}}, + {0x0F431C, {3, "Disgust"}}, + {0x0F431D, {3, "Mickey Mouse"}}, + {0x0F431E, {3, "Minnie Mouse"}}, + {0x0F431F, {3, "Mulan"}}, + {0x0F4320, {3, "Olaf"}}, + {0x0F4321, {3, "Vision"}}, + {0x0F4322, {3, "Ultron"}}, + {0x0F4323, {3, "Ant-Man"}}, + {0x0F4325, {3, "Captain America - The First Avenger"}}, + {0x0F4326, {3, "Finn"}}, + {0x0F4327, {3, "Kylo Ren"}}, + {0x0F4328, {3, "Poe Dameron"}}, + {0x0F4329, {3, "Rey"}}, + {0x0F432B, {3, "Spot"}}, + {0x0F432C, {3, "Nick Wilde"}}, + {0x0F432D, {3, "Judy Hopps"}}, + {0x0F432E, {3, "Hulkbuster"}}, + {0x0F432F, {3, "Anakin Skywalker (Light FX)"}}, + {0x0F4330, {3, "Obi-Wan Kenobi (Light FX)"}}, + {0x0F4331, {3, "Yoda (Light FX)"}}, + {0x0F4332, {3, "Luke Skywalker (Light FX)"}}, + {0x0F4333, {3, "Darth Vader (Light FX)"}}, + {0x0F4334, {3, "Kanan Jarrus (Light FX)"}}, + {0x0F4335, {3, "Kylo Ren (Light FX)"}}, + {0x0F4336, {3, "Black Panther"}}, + {0x0F436C, {3, "Nemo"}}, + {0x0F436D, {3, "Dory"}}, + {0x0F436E, {3, "Baloo"}}, + {0x0F436F, {3, "Alice"}}, + {0x0F4370, {3, "Mad Hatter"}}, + {0x0F4371, {3, "Time"}}, + {0x0F4372, {3, "Peter Pan"}}, + {0x1E8481, {1, "Starter Play Set"}}, + {0x1E8482, {1, "Lone Ranger Play Set"}}, + {0x1E8483, {1, "Cars Play Set"}}, + {0x1E8484, {1, "Toy Story in Space Play Set"}}, + {0x1E84E4, {2, "Marvel's The Avengers Play Set"}}, + {0x1E84E5, {2, "Marvel's Spider-Man Play Set"}}, + {0x1E84E6, {2, "Marvel's Guardians of the Galaxy Play Set"}}, + {0x1E84E7, {2, "Assault on Asgard"}}, + {0x1E84E8, {2, "Escape from the Kyln"}}, + {0x1E84E9, {2, "Stitch's Tropical Rescue"}}, + {0x1E84EA, {2, "Brave Forest Siege"}}, + {0x1E8548, {3, "Inside Out Play Set"}}, + {0x1E8549, {3, "Star Wars: Twilight of the Republic Play Set"}}, + {0x1E854A, {3, "Star Wars: Rise Against the Empire Play Set"}}, + {0x1E854B, {3, "Star Wars: The Force Awakens Play Set"}}, + {0x1E854C, {3, "Marvel Battlegrounds Play Set"}}, + {0x1E854D, {3, "Toy Box Speedway"}}, + {0x1E854E, {3, "Toy Box Takeover"}}, + {0x1E85AC, {3, "Finding Dory Play Set"}}, + {0x2DC6C3, {1, "Bolt's Super Strength"}}, + {0x2DC6C4, {1, "Ralph's Power of Destruction"}}, + {0x2DC6C5, {1, "Chernabog's Power"}}, + {0x2DC6C6, {1, "C.H.R.O.M.E. Damage Increaser"}}, + {0x2DC6C7, {1, "Dr. Doofenshmirtz's Damage-Inator!"}}, + {0x2DC6C8, {1, "Electro-Charge"}}, + {0x2DC6C9, {1, "Fix-It Felix's Repair Power"}}, + {0x2DC6CA, {1, "Rapunzel's Healing"}}, + {0x2DC6CB, {1, "C.H.R.O.M.E. Armor Shield"}}, + {0x2DC6CC, {1, "Star Command Shield"}}, + {0x2DC6CD, {1, "Violet's Force Field"}}, + {0x2DC6CE, {1, "Pieces of Eight"}}, + {0x2DC6CF, {1, "Scrooge McDuck's Lucky Dime"}}, + {0x2DC6D0, {1, "User Control"}}, + {0x2DC6D1, {1, "Sorcerer Mickey's Hat"}}, + {0x2DC6FE, {1, "Emperor Zurg's Wrath"}}, + {0x2DC6FF, {1, "Merlin's Summon"}}, + {0x2DC765, {2, "Enchanted Rose"}}, + {0x2DC766, {2, "Mulan's Training Uniform"}}, + {0x2DC767, {2, "Flubber"}}, + {0x2DC768, {2, "S.H.I.E.L.D. Helicarrier Strike"}}, + {0x2DC769, {2, "Zeus' Thunderbolts"}}, + {0x2DC76A, {2, "King Louie's Monkeys"}}, + {0x2DC76B, {2, "Infinity Gauntlet"}}, + {0x2DC76D, {2, "Sorcerer Supreme"}}, + {0x2DC76E, {2, "Maleficent's Spell Cast"}}, + {0x2DC76F, {2, "Chernabog's Spirit Cyclone"}}, + {0x2DC770, {2, "Marvel Team-Up: Capt. Marvel"}}, + {0x2DC771, {2, "Marvel Team-Up: Iron Patriot"}}, + {0x2DC772, {2, "Marvel Team-Up: Ant-Man"}}, + {0x2DC773, {2, "Marvel Team-Up: White Tiger"}}, + {0x2DC774, {2, "Marvel Team-Up: Yondu"}}, + {0x2DC775, {2, "Marvel Team-Up: Winter Soldier"}}, + {0x2DC776, {2, "Stark Arc Reactor"}}, + {0x2DC777, {2, "Gamma Rays"}}, + {0x2DC778, {2, "Alien Symbiote"}}, + {0x2DC779, {2, "All for One"}}, + {0x2DC77A, {2, "Sandy Claws Surprise"}}, + {0x2DC77B, {2, "Glory Days"}}, + {0x2DC77C, {2, "Cursed Pirate Gold"}}, + {0x2DC77D, {2, "Sentinel of Liberty"}}, + {0x2DC77E, {2, "The Immortal Iron Fist"}}, + {0x2DC77F, {2, "Space Armor"}}, + {0x2DC780, {2, "Rags to Riches"}}, + {0x2DC781, {2, "Ultimate Falcon"}}, + {0x2DC788, {3, "Tomorrowland Time Bomb"}}, + {0x2DC78E, {3, "Galactic Team-Up: Mace Windu"}}, + {0x2DC791, {3, "Luke's Rebel Alliance Flight Suit Costume"}}, + {0x2DC798, {3, "Finn's Stormtrooper Costume"}}, + {0x2DC799, {3, "Poe's Resistance Jacket"}}, + {0x2DC79A, {3, "Resistance Tactical Strike"}}, + {0x2DC79E, {3, "Officer Nick Wilde"}}, + {0x2DC79F, {3, "Meter Maid Judy"}}, + {0x2DC7A2, {3, "Darkhawk's Blast"}}, + {0x2DC7A3, {3, "Cosmic Cube Blast"}}, + {0x2DC7A4, {3, "Princess Leia's Boushh Disguise"}}, + {0x2DC7A6, {3, "Nova Corps Strike"}}, + {0x2DC7A7, {3, "King Mickey"}}, + {0x3D0912, {1, "Mickey's Car"}}, + {0x3D0913, {1, "Cinderella's Coach"}}, + {0x3D0914, {1, "Electric Mayhem Bus"}}, + {0x3D0915, {1, "Cruella De Vil's Car"}}, + {0x3D0916, {1, "Pizza Planet Delivery Truck"}}, + {0x3D0917, {1, "Mike's New Car"}}, + {0x3D0919, {1, "Parking Lot Tram"}}, + {0x3D091A, {1, "Captain Hook's Ship"}}, + {0x3D091B, {1, "Dumbo"}}, + {0x3D091C, {1, "Calico Helicopter"}}, + {0x3D091D, {1, "Maximus"}}, + {0x3D091E, {1, "Angus"}}, + {0x3D091F, {1, "Abu the Elephant"}}, + {0x3D0920, {1, "Headless Horseman's Horse"}}, + {0x3D0921, {1, "Phillipe"}}, + {0x3D0922, {1, "Khan"}}, + {0x3D0923, {1, "Tantor"}}, + {0x3D0924, {1, "Dragon Firework Cannon"}}, + {0x3D0925, {1, "Stitch's Blaster"}}, + {0x3D0926, {1, "Toy Story Mania Blaster"}}, + {0x3D0927, {1, "Flamingo Croquet Mallet"}}, + {0x3D0928, {1, "Carl Fredricksen's Cane"}}, + {0x3D0929, {1, "Hangin' Ten Stitch With Surfboard"}}, + {0x3D092A, {1, "Condorman Glider"}}, + {0x3D092B, {1, "WALL-E's Fire Extinguisher"}}, + {0x3D092C, {1, "On the Grid"}}, + {0x3D092D, {1, "WALL-E's Collection"}}, + {0x3D092E, {1, "King Candy's Dessert Toppings"}}, + {0x3D0930, {1, "Victor's Experiments"}}, + {0x3D0931, {1, "Jack's Scary Decorations"}}, + {0x3D0933, {1, "Frozen Flourish"}}, + {0x3D0934, {1, "Rapunzel's Kingdom"}}, + {0x3D0935, {1, "TRON Interface"}}, + {0x3D0936, {1, "Buy N Large Atmosphere"}}, + {0x3D0937, {1, "Sugar Rush Sky"}}, + {0x3D0939, {1, "New Holland Skyline"}}, + {0x3D093A, {1, "Halloween Town Sky"}}, + {0x3D093C, {1, "Chill in the Air"}}, + {0x3D093D, {1, "Rapunzel's Birthday Sky"}}, + {0x3D0940, {1, "Astro Blasters Space Cruiser"}}, + {0x3D0941, {1, "Marlin's Reef"}}, + {0x3D0942, {1, "Nemo's Seascape"}}, + {0x3D0943, {1, "Alice's Wonderland"}}, + {0x3D0944, {1, "Tulgey Wood"}}, + {0x3D0945, {1, "Tri-State Area Terrain"}}, + {0x3D0946, {1, "Danville Sky"}}, + {0x3D0965, {2, "Stark Tech"}}, + {0x3D0966, {2, "Spider-Streets"}}, + {0x3D0967, {2, "World War Hulk"}}, + {0x3D0968, {2, "Gravity Falls Forest"}}, + {0x3D0969, {2, "Neverland"}}, + {0x3D096A, {2, "Simba's Pridelands"}}, + {0x3D096C, {2, "Calhoun's Command"}}, + {0x3D096D, {2, "Star-Lord's Galaxy"}}, + {0x3D096E, {2, "Dinosaur World"}}, + {0x3D096F, {2, "Groot's Roots"}}, + {0x3D0970, {2, "Mulan's Countryside"}}, + {0x3D0971, {2, "The Sands of Agrabah"}}, + {0x3D0974, {2, "A Small World"}}, + {0x3D0975, {2, "View from the Suit"}}, + {0x3D0976, {2, "Spider-Sky"}}, + {0x3D0977, {2, "World War Hulk Sky"}}, + {0x3D0978, {2, "Gravity Falls Sky"}}, + {0x3D0979, {2, "Second Star to the Right"}}, + {0x3D097A, {2, "The King's Domain"}}, + {0x3D097C, {2, "CyBug Swarm"}}, + {0x3D097D, {2, "The Rip"}}, + {0x3D097E, {2, "Forgotten Skies"}}, + {0x3D097F, {2, "Groot's View"}}, + {0x3D0980, {2, "The Middle Kingdom"}}, + {0x3D0984, {2, "Skies of the World"}}, + {0x3D0985, {2, "S.H.I.E.L.D. Containment Truck"}}, + {0x3D0986, {2, "Main Street Electrical Parade Float"}}, + {0x3D0987, {2, "Mr. Toad's Motorcar"}}, + {0x3D0988, {2, "Le Maximum"}}, + {0x3D0989, {2, "Alice in Wonderland's Caterpillar"}}, + {0x3D098A, {2, "Eglantine's Motorcycle"}}, + {0x3D098B, {2, "Medusa's Swamp Mobile"}}, + {0x3D098C, {2, "Hydra Motorcycle"}}, + {0x3D098D, {2, "Darkwing Duck's Ratcatcher"}}, + {0x3D098F, {2, "The USS Swinetrek"}}, + {0x3D0991, {2, "Spider-Copter"}}, + {0x3D0992, {2, "Aerial Area Rug"}}, + {0x3D0993, {2, "Jack-O-Lantern's Glider"}}, + {0x3D0994, {2, "Spider-Buggy"}}, + {0x3D0995, {2, "Jack Skellington's Reindeer"}}, + {0x3D0996, {2, "Fantasyland Carousel Horse"}}, + {0x3D0997, {2, "Odin's Horse"}}, + {0x3D0998, {2, "Gus the Mule"}}, + {0x3D099A, {2, "Darkwing Duck's Grappling Gun"}}, + {0x3D099C, {2, "Ghost Rider's Chain Whip"}}, + {0x3D099D, {2, "Lew Zealand's Boomerang Fish"}}, + {0x3D099E, {2, "Sergeant Calhoun's Blaster"}}, + {0x3D09A0, {2, "Falcon's Wings"}}, + {0x3D09A1, {2, "Mabel's Kittens for Fists"}}, + {0x3D09A2, {2, "Jim Hawkins' Solar Board"}}, + {0x3D09A3, {2, "Black Panther's Vibranium Knives"}}, + {0x3D09A4, {2, "Cloak of Levitation"}}, + {0x3D09A5, {2, "Aladdin's Magic Carpet"}}, + {0x3D09A6, {2, "Honey Lemon's Ice Capsules"}}, + {0x3D09A7, {2, "Jasmine's Palace View"}}, + {0x3D09C1, {2, "Lola"}}, + {0x3D09C2, {2, "Spider-Cycle"}}, + {0x3D09C3, {2, "The Avenjet"}}, + {0x3D09C4, {2, "Spider-Glider"}}, + {0x3D09C5, {2, "Light Cycle"}}, + {0x3D09C6, {2, "Light Jet"}}, + {0x3D09C9, {3, "Retro Ray Gun"}}, + {0x3D09CA, {3, "Tomorrowland Futurescape"}}, + {0x3D09CB, {3, "Tomorrowland Stratosphere"}}, + {0x3D09CC, {3, "Skies Over Felucia"}}, + {0x3D09CD, {3, "Forests of Felucia"}}, + {0x3D09CF, {3, "General Grievous' Wheel Bike"}}, + {0x3D09D2, {3, "Slave I Flyer"}}, + {0x3D09D3, {3, "Y-Wing Fighter"}}, + {0x3D09D4, {3, "Arlo"}}, + {0x3D09D5, {3, "Nash"}}, + {0x3D09D6, {3, "Butch"}}, + {0x3D09D7, {3, "Ramsey"}}, + {0x3D09DC, {3, "Stars Over Sahara Square"}}, + {0x3D09DD, {3, "Sahara Square Sands"}}, + {0x3D09E0, {3, "Ghost Rider's Motorcycle"}}, + {0x3D09E5, {3, "Quad Jumper"}}}; + + InfinityBaseDevice::InfinityBaseDevice() + : Device(0x0E6F, 0x0129, 1, 2, 0) + { + m_IsOpened = false; + } + + bool InfinityBaseDevice::Open() + { + if (!IsOpened()) + { + m_IsOpened = true; + } + return true; + } + + void InfinityBaseDevice::Close() + { + if (IsOpened()) + { + m_IsOpened = false; + } + } + + bool InfinityBaseDevice::IsOpened() + { + return m_IsOpened; + } + + Device::ReadResult InfinityBaseDevice::Read(ReadMessage* message) + { + memcpy(message->data, g_infinitybase.GetStatus().data(), message->length); + message->bytesRead = message->length; + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + return Device::ReadResult::Success; + } + + Device::WriteResult InfinityBaseDevice::Write(WriteMessage* message) + { + g_infinitybase.SendCommand(message->data, message->length); + message->bytesWritten = message->length; + return Device::WriteResult::Success; + } + + bool InfinityBaseDevice::GetDescriptor(uint8 descType, + uint8 descIndex, + uint8 lang, + uint8* output, + uint32 outputMaxLength) + { + uint8 configurationDescriptor[0x29]; + + uint8* currentWritePtr; + + // configuration descriptor + currentWritePtr = configurationDescriptor + 0; + *(uint8*)(currentWritePtr + 0) = 9; // bLength + *(uint8*)(currentWritePtr + 1) = 2; // bDescriptorType + *(uint16be*)(currentWritePtr + 2) = 0x0029; // wTotalLength + *(uint8*)(currentWritePtr + 4) = 1; // bNumInterfaces + *(uint8*)(currentWritePtr + 5) = 1; // bConfigurationValue + *(uint8*)(currentWritePtr + 6) = 0; // iConfiguration + *(uint8*)(currentWritePtr + 7) = 0x80; // bmAttributes + *(uint8*)(currentWritePtr + 8) = 0xFA; // MaxPower + currentWritePtr = currentWritePtr + 9; + // configuration descriptor + *(uint8*)(currentWritePtr + 0) = 9; // bLength + *(uint8*)(currentWritePtr + 1) = 0x04; // bDescriptorType + *(uint8*)(currentWritePtr + 2) = 0; // bInterfaceNumber + *(uint8*)(currentWritePtr + 3) = 0; // bAlternateSetting + *(uint8*)(currentWritePtr + 4) = 2; // bNumEndpoints + *(uint8*)(currentWritePtr + 5) = 3; // bInterfaceClass + *(uint8*)(currentWritePtr + 6) = 0; // bInterfaceSubClass + *(uint8*)(currentWritePtr + 7) = 0; // bInterfaceProtocol + *(uint8*)(currentWritePtr + 8) = 0; // iInterface + currentWritePtr = currentWritePtr + 9; + // configuration descriptor + *(uint8*)(currentWritePtr + 0) = 9; // bLength + *(uint8*)(currentWritePtr + 1) = 0x21; // bDescriptorType + *(uint16be*)(currentWritePtr + 2) = 0x0111; // bcdHID + *(uint8*)(currentWritePtr + 4) = 0x00; // bCountryCode + *(uint8*)(currentWritePtr + 5) = 0x01; // bNumDescriptors + *(uint8*)(currentWritePtr + 6) = 0x22; // bDescriptorType + *(uint16be*)(currentWritePtr + 7) = 0x001D; // wDescriptorLength + currentWritePtr = currentWritePtr + 9; + // endpoint descriptor 1 + *(uint8*)(currentWritePtr + 0) = 7; // bLength + *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType + *(uint8*)(currentWritePtr + 2) = 0x81; // bEndpointAddress + *(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes + *(uint16be*)(currentWritePtr + 4) = 0x40; // wMaxPacketSize + *(uint8*)(currentWritePtr + 6) = 0x01; // bInterval + currentWritePtr = currentWritePtr + 7; + // endpoint descriptor 2 + *(uint8*)(currentWritePtr + 0) = 7; // bLength + *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType + *(uint8*)(currentWritePtr + 1) = 0x02; // bEndpointAddress + *(uint8*)(currentWritePtr + 2) = 0x03; // bmAttributes + *(uint16be*)(currentWritePtr + 3) = 0x40; // wMaxPacketSize + *(uint8*)(currentWritePtr + 5) = 0x01; // bInterval + currentWritePtr = currentWritePtr + 7; + + cemu_assert_debug((currentWritePtr - configurationDescriptor) == 0x29); + + memcpy(output, configurationDescriptor, + std::min<uint32>(outputMaxLength, sizeof(configurationDescriptor))); + return true; + } + + bool InfinityBaseDevice::SetProtocol(uint8 ifIndex, uint8 protocol) + { + return true; + } + + bool InfinityBaseDevice::SetReport(ReportMessage* message) + { + return true; + } + + std::array<uint8, 32> InfinityUSB::GetStatus() + { + std::array<uint8, 32> response = {}; + + bool responded = false; + + do + { + if (!m_figureAddedRemovedResponses.empty()) + { + memcpy(response.data(), m_figureAddedRemovedResponses.front().data(), + 0x20); + m_figureAddedRemovedResponses.pop(); + responded = true; + } + else if (!m_queries.empty()) + { + memcpy(response.data(), m_queries.front().data(), 0x20); + m_queries.pop(); + responded = true; + } + else + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + /* code */ + } + while (!responded); + + return response; + } + + void InfinityUSB::SendCommand(uint8* buf, sint32 originalLength) + { + const uint8 command = buf[2]; + const uint8 sequence = buf[3]; + + std::array<uint8, 32> q_result{}; + + switch (command) + { + case 0x80: + { + q_result = {0xaa, 0x15, 0x00, 0x00, 0x0f, 0x01, 0x00, 0x03, + 0x02, 0x09, 0x09, 0x43, 0x20, 0x32, 0x62, 0x36, + 0x36, 0x4b, 0x34, 0x99, 0x67, 0x31, 0x93, 0x8c}; + break; + } + case 0x81: + { + // Initiate Challenge + g_infinitybase.DescrambleAndSeed(buf, sequence, q_result); + break; + } + case 0x83: + { + // Challenge Response + g_infinitybase.GetNextAndScramble(sequence, q_result); + break; + } + case 0x90: + case 0x92: + case 0x93: + case 0x95: + case 0x96: + { + // Color commands + g_infinitybase.GetBlankResponse(sequence, q_result); + break; + } + case 0xA1: + { + // Get Present Figures + g_infinitybase.GetPresentFigures(sequence, q_result); + break; + } + case 0xA2: + { + // Read Block from Figure + g_infinitybase.QueryBlock(buf[4], buf[5], q_result, sequence); + break; + } + case 0xA3: + { + // Write block to figure + g_infinitybase.WriteBlock(buf[4], buf[5], &buf[7], q_result, sequence); + break; + } + case 0xB4: + { + // Get figure ID + g_infinitybase.GetFigureIdentifier(buf[4], sequence, q_result); + break; + } + case 0xB5: + { + // Get status? + g_infinitybase.GetBlankResponse(sequence, q_result); + break; + } + default: + cemu_assert_error(); + break; + } + + m_queries.push(q_result); + } + + uint8 InfinityUSB::GenerateChecksum(const std::array<uint8, 32>& data, + int numOfBytes) const + { + int checksum = 0; + for (int i = 0; i < numOfBytes; i++) + { + checksum += data[i]; + } + return (checksum & 0xFF); + } + + void InfinityUSB::GetBlankResponse(uint8 sequence, + std::array<uint8, 32>& replyBuf) + { + replyBuf[0] = 0xaa; + replyBuf[1] = 0x01; + replyBuf[2] = sequence; + replyBuf[3] = GenerateChecksum(replyBuf, 3); + } + + void InfinityUSB::DescrambleAndSeed(uint8* buf, uint8 sequence, + std::array<uint8, 32>& replyBuf) + { + uint64 value = uint64(buf[4]) << 56 | uint64(buf[5]) << 48 | + uint64(buf[6]) << 40 | uint64(buf[7]) << 32 | + uint64(buf[8]) << 24 | uint64(buf[9]) << 16 | + uint64(buf[10]) << 8 | uint64(buf[11]); + uint32 seed = Descramble(value); + GenerateSeed(seed); + GetBlankResponse(sequence, replyBuf); + } + + void InfinityUSB::GetNextAndScramble(uint8 sequence, + std::array<uint8, 32>& replyBuf) + { + const uint32 nextRandom = GetNext(); + const uint64 scrambledNextRandom = Scramble(nextRandom, 0); + replyBuf = {0xAA, 0x09, sequence}; + replyBuf[3] = uint8((scrambledNextRandom >> 56) & 0xFF); + replyBuf[4] = uint8((scrambledNextRandom >> 48) & 0xFF); + replyBuf[5] = uint8((scrambledNextRandom >> 40) & 0xFF); + replyBuf[6] = uint8((scrambledNextRandom >> 32) & 0xFF); + replyBuf[7] = uint8((scrambledNextRandom >> 24) & 0xFF); + replyBuf[8] = uint8((scrambledNextRandom >> 16) & 0xFF); + replyBuf[9] = uint8((scrambledNextRandom >> 8) & 0xFF); + replyBuf[10] = uint8(scrambledNextRandom & 0xFF); + replyBuf[11] = GenerateChecksum(replyBuf, 11); + } + + uint32 InfinityUSB::Descramble(uint64 numToDescramble) + { + uint64 mask = 0x8E55AA1B3999E8AA; + uint32 ret = 0; + + for (int i = 0; i < 64; i++) + { + if (mask & 0x8000000000000000) + { + ret = (ret << 1) | (numToDescramble & 0x01); + } + + numToDescramble >>= 1; + mask <<= 1; + } + + return ret; + } + + uint64 InfinityUSB::Scramble(uint32 numToScramble, uint32 garbage) + { + uint64 mask = 0x8E55AA1B3999E8AA; + uint64 ret = 0; + + for (int i = 0; i < 64; i++) + { + ret <<= 1; + + if ((mask & 1) != 0) + { + ret |= (numToScramble & 1); + numToScramble >>= 1; + } + else + { + ret |= (garbage & 1); + garbage >>= 1; + } + + mask >>= 1; + } + + return ret; + } + + void InfinityUSB::GenerateSeed(uint32 seed) + { + m_randomA = 0xF1EA5EED; + m_randomB = seed; + m_randomC = seed; + m_randomD = seed; + + for (int i = 0; i < 23; i++) + { + GetNext(); + } + } + + uint32 InfinityUSB::GetNext() + { + uint32 a = m_randomA; + uint32 b = m_randomB; + uint32 c = m_randomC; + uint32 ret = std::rotl(m_randomB, 27); + + const uint32 temp = (a + ((ret ^ 0xFFFFFFFF) + 1)); + b ^= std::rotl(c, 17); + a = m_randomD; + c += a; + ret = b + temp; + a += temp; + + m_randomC = a; + m_randomA = b; + m_randomB = c; + m_randomD = ret; + + return ret; + } + + void InfinityUSB::GetPresentFigures(uint8 sequence, + std::array<uint8, 32>& replyBuf) + { + int x = 3; + for (uint8 i = 0; i < m_figures.size(); i++) + { + uint8 slot = i == 0 ? 0x10 : (i < 4) ? 0x20 + : 0x30; + if (m_figures[i].present) + { + replyBuf[x] = slot + m_figures[i].orderAdded; + replyBuf[x + 1] = 0x09; + x += 2; + } + } + replyBuf[0] = 0xaa; + replyBuf[1] = x - 2; + replyBuf[2] = sequence; + replyBuf[x] = GenerateChecksum(replyBuf, x); + } + + InfinityUSB::InfinityFigure& + InfinityUSB::GetFigureByOrder(uint8 orderAdded) + { + for (uint8 i = 0; i < m_figures.size(); i++) + { + if (m_figures[i].orderAdded == orderAdded) + { + return m_figures[i]; + } + } + return m_figures[0]; + } + + void InfinityUSB::QueryBlock(uint8 fig_num, uint8 block, + std::array<uint8, 32>& replyBuf, + uint8 sequence) + { + std::lock_guard lock(m_infinityMutex); + + InfinityFigure& figure = GetFigureByOrder(fig_num); + + replyBuf[0] = 0xaa; + replyBuf[1] = 0x12; + replyBuf[2] = sequence; + replyBuf[3] = 0x00; + const uint8 file_block = (block == 0) ? 1 : (block * 4); + if (figure.present && file_block < 20) + { + memcpy(&replyBuf[4], figure.data.data() + (16 * file_block), 16); + } + replyBuf[20] = GenerateChecksum(replyBuf, 20); + } + + void InfinityUSB::WriteBlock(uint8 fig_num, uint8 block, + const uint8* to_write_buf, + std::array<uint8, 32>& replyBuf, + uint8 sequence) + { + std::lock_guard lock(m_infinityMutex); + + InfinityFigure& figure = GetFigureByOrder(fig_num); + + replyBuf[0] = 0xaa; + replyBuf[1] = 0x02; + replyBuf[2] = sequence; + replyBuf[3] = 0x00; + const uint8 file_block = (block == 0) ? 1 : (block * 4); + if (figure.present && file_block < 20) + { + memcpy(figure.data.data() + (file_block * 16), to_write_buf, 16); + figure.Save(); + } + replyBuf[4] = GenerateChecksum(replyBuf, 4); + } + + void InfinityUSB::GetFigureIdentifier(uint8 fig_num, uint8 sequence, + std::array<uint8, 32>& replyBuf) + { + std::lock_guard lock(m_infinityMutex); + + InfinityFigure& figure = GetFigureByOrder(fig_num); + + replyBuf[0] = 0xaa; + replyBuf[1] = 0x09; + replyBuf[2] = sequence; + replyBuf[3] = 0x00; + + if (figure.present) + { + memcpy(&replyBuf[4], figure.data.data(), 7); + } + replyBuf[11] = GenerateChecksum(replyBuf, 11); + } + + std::pair<uint8, std::string> InfinityUSB::FindFigure(uint32 figNum) + { + for (const auto& it : GetFigureList()) + { + if (it.first == figNum) + { + return it.second; + } + } + return {0, fmt::format("Unknown Figure ({})", figNum)}; + } + + std::map<const uint32, const std::pair<const uint8, const char*>> InfinityUSB::GetFigureList() + { + return s_listFigures; + } + + void InfinityUSB::InfinityFigure::Save() + { + if (!infFile) + return; + + infFile->SetPosition(0); + infFile->writeData(data.data(), data.size()); + } + + bool InfinityUSB::RemoveFigure(uint8 position) + { + std::lock_guard lock(m_infinityMutex); + InfinityFigure& figure = m_figures[position]; + + figure.Save(); + figure.infFile.reset(); + + if (figure.present) + { + figure.present = false; + + position = DeriveFigurePosition(position); + if (position == 0) + { + return false; + } + + std::array<uint8, 32> figureChangeResponse = {0xab, 0x04, position, 0x09, figure.orderAdded, + 0x01}; + figureChangeResponse[6] = GenerateChecksum(figureChangeResponse, 6); + m_figureAddedRemovedResponses.push(figureChangeResponse); + + return true; + } + return false; + } + + uint32 + InfinityUSB::LoadFigure(const std::array<uint8, INF_FIGURE_SIZE>& buf, + std::unique_ptr<FileStream> inFile, uint8 position) + { + std::lock_guard lock(m_infinityMutex); + uint8 orderAdded; + + std::vector<uint8> sha1Calc = {SHA1_CONSTANT.begin(), SHA1_CONSTANT.end() - 1}; + for (int i = 0; i < 7; i++) + { + sha1Calc.push_back(buf[i]); + } + + std::array<uint8, 16> key = GenerateInfinityFigureKey(sha1Calc); + + std::array<uint8, 16> infinity_decrypted_block = {}; + std::array<uint8, 16> encryptedBlock = {}; + memcpy(encryptedBlock.data(), &buf[16], 16); + + AES128_ECB_decrypt(encryptedBlock.data(), key.data(), infinity_decrypted_block.data()); + + uint32 number = uint32(infinity_decrypted_block[1]) << 16 | uint32(infinity_decrypted_block[2]) << 8 | + uint32(infinity_decrypted_block[3]); + + InfinityFigure& figure = m_figures[position]; + + figure.infFile = std::move(inFile); + memcpy(figure.data.data(), buf.data(), figure.data.size()); + figure.present = true; + if (figure.orderAdded == 255) + { + figure.orderAdded = m_figureOrder; + m_figureOrder++; + } + orderAdded = figure.orderAdded; + + position = DeriveFigurePosition(position); + if (position == 0) + { + return 0; + } + + std::array<uint8, 32> figureChangeResponse = {0xab, 0x04, position, 0x09, orderAdded, 0x00}; + figureChangeResponse[6] = GenerateChecksum(figureChangeResponse, 6); + m_figureAddedRemovedResponses.push(figureChangeResponse); + + return number; + } + + static uint32 InfinityCRC32(const std::array<uint8, 16>& buffer) + { + static constexpr std::array<uint32, 256> CRC32_TABLE{ + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, + 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, + 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, + 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, + 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, + 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, + 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, + 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, + 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, + 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, + 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, + 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, + 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, + 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, + 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, + 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, + 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, + 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, + 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, + 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, + 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, + 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, + 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, + 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, + 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, + 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, + 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, + 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d}; + + // Infinity m_figures calculate their CRC32 based on 12 bytes in the block of 16 + uint32 ret = 0; + for (uint32 i = 0; i < 12; ++i) + { + uint8 index = uint8(ret & 0xFF) ^ buffer[i]; + ret = ((ret >> 8) ^ CRC32_TABLE[index]); + } + + return ret; + } + + bool InfinityUSB::CreateFigure(fs::path pathName, uint32 figureNum, uint8 series) + { + FileStream* infFile(FileStream::createFile2(pathName)); + if (!infFile) + { + return false; + } + std::array<uint8, INF_FIGURE_SIZE> fileData{}; + uint32 firstBlock = 0x17878E; + uint32 otherBlocks = 0x778788; + for (sint8 i = 2; i >= 0; i--) + { + fileData[0x38 - i] = uint8((firstBlock >> i * 8) & 0xFF); + } + for (uint32 index = 1; index < 0x05; index++) + { + for (sint8 i = 2; i >= 0; i--) + { + fileData[((index * 0x40) + 0x38) - i] = uint8((otherBlocks >> i * 8) & 0xFF); + } + } + // Create the vector to calculate the SHA1 hash with + std::vector<uint8> sha1Calc = {SHA1_CONSTANT.begin(), SHA1_CONSTANT.end() - 1}; + + // Generate random UID, used for AES encrypt/decrypt + std::random_device rd; + std::mt19937 mt(rd()); + std::uniform_int_distribution<int> dist(0, 255); + std::array<uint8, 16> uid_data = {0, 0, 0, 0, 0, 0, 0, 0x89, 0x44, 0x00, 0xC2}; + uid_data[0] = dist(mt); + uid_data[1] = dist(mt); + uid_data[2] = dist(mt); + uid_data[3] = dist(mt); + uid_data[4] = dist(mt); + uid_data[5] = dist(mt); + uid_data[6] = dist(mt); + for (sint8 i = 0; i < 7; i++) + { + sha1Calc.push_back(uid_data[i]); + } + std::array<uint8, 16> figureData = GenerateBlankFigureData(figureNum, series); + if (figureData[1] == 0x00) + return false; + + std::array<uint8, 16> key = GenerateInfinityFigureKey(sha1Calc); + + std::array<uint8, 16> encryptedBlock = {}; + std::array<uint8, 16> blankBlock = {}; + std::array<uint8, 16> encryptedBlank = {}; + + AES128_ECB_encrypt(figureData.data(), key.data(), encryptedBlock.data()); + AES128_ECB_encrypt(blankBlock.data(), key.data(), encryptedBlank.data()); + + memcpy(&fileData[0], uid_data.data(), uid_data.size()); + memcpy(&fileData[16], encryptedBlock.data(), encryptedBlock.size()); + memcpy(&fileData[16 * 0x04], encryptedBlank.data(), encryptedBlank.size()); + memcpy(&fileData[16 * 0x08], encryptedBlank.data(), encryptedBlank.size()); + memcpy(&fileData[16 * 0x0C], encryptedBlank.data(), encryptedBlank.size()); + memcpy(&fileData[16 * 0x0D], encryptedBlank.data(), encryptedBlank.size()); + + infFile->writeData(fileData.data(), fileData.size()); + + delete infFile; + + return true; + } + + std::array<uint8, 16> InfinityUSB::GenerateInfinityFigureKey(const std::vector<uint8>& sha1Data) + { + std::array<uint8, 20> digest = {}; + SHA_CTX ctx; + SHA1_Init(&ctx); + SHA1_Update(&ctx, sha1Data.data(), sha1Data.size()); + SHA1_Final(digest.data(), &ctx); + OPENSSL_cleanse(&ctx, sizeof(ctx)); + // Infinity AES keys are the first 16 bytes of the SHA1 Digest, every set of 4 bytes need to be + // reversed due to endianness + std::array<uint8, 16> key = {}; + for (int i = 0; i < 4; i++) + { + for (int x = 3; x >= 0; x--) + { + key[(3 - x) + (i * 4)] = digest[x + (i * 4)]; + } + } + return key; + } + + std::array<uint8, 16> InfinityUSB::GenerateBlankFigureData(uint32 figureNum, uint8 series) + { + std::array<uint8, 16> figureData = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0xD1, 0x1F}; + + // Figure Number, input by end user + figureData[1] = uint8((figureNum >> 16) & 0xFF); + figureData[2] = uint8((figureNum >> 8) & 0xFF); + figureData[3] = uint8(figureNum & 0xFF); + + // Manufacture date, formatted as YY/MM/DD. Set to release date of figure's series + if (series == 1) + { + figureData[4] = 0x0D; + figureData[5] = 0x08; + figureData[6] = 0x12; + } + else if (series == 2) + { + figureData[4] = 0x0E; + figureData[5] = 0x09; + figureData[6] = 0x12; + } + else if (series == 3) + { + figureData[4] = 0x0F; + figureData[5] = 0x08; + figureData[6] = 0x1C; + } + + uint32 checksum = InfinityCRC32(figureData); + for (sint8 i = 3; i >= 0; i--) + { + figureData[15 - i] = uint8((checksum >> i * 8) & 0xFF); + } + return figureData; + } + + uint8 InfinityUSB::DeriveFigurePosition(uint8 position) + { + // In the added/removed response, position needs to be 1 for the hexagon, 2 for Player 1 and + // Player 1's abilities, and 3 for Player 2 and Player 2's abilities. In the UI, positions 0, 1 + // and 2 represent the hexagon slot, 3, 4 and 5 represent Player 1's slot and 6, 7 and 8 represent + // Player 2's slot. + + switch (position) + { + case 0: + case 1: + case 2: + return 1; + case 3: + case 4: + case 5: + return 2; + case 6: + case 7: + case 8: + return 3; + + default: + return 0; + } + } +} // namespace nsyshid \ No newline at end of file diff --git a/src/Cafe/OS/libs/nsyshid/Infinity.h b/src/Cafe/OS/libs/nsyshid/Infinity.h new file mode 100644 index 00000000..aa98fd15 --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/Infinity.h @@ -0,0 +1,105 @@ +#pragma once + +#include <mutex> + +#include "nsyshid.h" +#include "Backend.h" + +#include "Common/FileStream.h" + +namespace nsyshid +{ + class InfinityBaseDevice final : public Device { + public: + InfinityBaseDevice(); + ~InfinityBaseDevice() = default; + + bool Open() override; + + void Close() override; + + bool IsOpened() override; + + ReadResult Read(ReadMessage* message) override; + + WriteResult Write(WriteMessage* message) override; + + bool GetDescriptor(uint8 descType, + uint8 descIndex, + uint8 lang, + uint8* output, + uint32 outputMaxLength) override; + + bool SetProtocol(uint8 ifIndex, uint8 protocol) override; + + bool SetReport(ReportMessage* message) override; + + private: + bool m_IsOpened; + }; + + constexpr uint16 INF_BLOCK_COUNT = 0x14; + constexpr uint16 INF_BLOCK_SIZE = 0x10; + constexpr uint16 INF_FIGURE_SIZE = INF_BLOCK_COUNT * INF_BLOCK_SIZE; + constexpr uint8 MAX_FIGURES = 9; + class InfinityUSB { + public: + struct InfinityFigure final + { + std::unique_ptr<FileStream> infFile; + std::array<uint8, INF_FIGURE_SIZE> data{}; + bool present = false; + uint8 orderAdded = 255; + void Save(); + }; + + void SendCommand(uint8* buf, sint32 originalLength); + std::array<uint8, 32> GetStatus(); + + void GetBlankResponse(uint8 sequence, std::array<uint8, 32>& replyBuf); + void DescrambleAndSeed(uint8* buf, uint8 sequence, + std::array<uint8, 32>& replyBuf); + void GetNextAndScramble(uint8 sequence, std::array<uint8, 32>& replyBuf); + void GetPresentFigures(uint8 sequence, std::array<uint8, 32>& replyBuf); + void QueryBlock(uint8 figNum, uint8 block, std::array<uint8, 32>& replyBuf, + uint8 sequence); + void WriteBlock(uint8 figNum, uint8 block, const uint8* toWriteBuf, + std::array<uint8, 32>& replyBuf, uint8 sequence); + void GetFigureIdentifier(uint8 figNum, uint8 sequence, + std::array<uint8, 32>& replyBuf); + + bool RemoveFigure(uint8 position); + uint32 LoadFigure(const std::array<uint8, INF_FIGURE_SIZE>& buf, + std::unique_ptr<FileStream>, uint8 position); + bool CreateFigure(fs::path pathName, uint32 figureNum, uint8 series); + static std::map<const uint32, const std::pair<const uint8, const char*>> GetFigureList(); + std::pair<uint8, std::string> FindFigure(uint32 figNum); + + protected: + std::shared_mutex m_infinityMutex; + std::array<InfinityFigure, 9> m_figures; + + private: + uint8 GenerateChecksum(const std::array<uint8, 32>& data, + int numOfBytes) const; + uint32 Descramble(uint64 numToDescramble); + uint64 Scramble(uint32 numToScramble, uint32 garbage); + void GenerateSeed(uint32 seed); + uint32 GetNext(); + InfinityFigure& GetFigureByOrder(uint8 orderAdded); + uint8 DeriveFigurePosition(uint8 position); + std::array<uint8, 16> GenerateInfinityFigureKey(const std::vector<uint8>& sha1Data); + std::array<uint8, 16> GenerateBlankFigureData(uint32 figureNum, uint8 series); + + uint32 m_randomA; + uint32 m_randomB; + uint32 m_randomC; + uint32 m_randomD; + + uint8 m_figureOrder = 0; + std::queue<std::array<uint8, 32>> m_figureAddedRemovedResponses; + std::queue<std::array<uint8, 32>> m_queries; + }; + extern InfinityUSB g_infinitybase; + +} // namespace nsyshid \ No newline at end of file diff --git a/src/Cafe/OS/libs/nsyshid/Skylander.cpp b/src/Cafe/OS/libs/nsyshid/Skylander.cpp index 7f17f8a3..a9888787 100644 --- a/src/Cafe/OS/libs/nsyshid/Skylander.cpp +++ b/src/Cafe/OS/libs/nsyshid/Skylander.cpp @@ -855,7 +855,7 @@ namespace nsyshid return false; } - std::array<uint8, BLOCK_COUNT * BLOCK_SIZE> data{}; + std::array<uint8, SKY_FIGURE_SIZE> data{}; uint32 first_block = 0x690F0F0F; uint32 other_blocks = 0x69080F7F; diff --git a/src/Cafe/OS/libs/nsyshid/Skylander.h b/src/Cafe/OS/libs/nsyshid/Skylander.h index ae8b5d92..95eaff0c 100644 --- a/src/Cafe/OS/libs/nsyshid/Skylander.h +++ b/src/Cafe/OS/libs/nsyshid/Skylander.h @@ -38,9 +38,9 @@ namespace nsyshid bool m_IsOpened; }; - constexpr uint16 BLOCK_COUNT = 0x40; - constexpr uint16 BLOCK_SIZE = 0x10; - constexpr uint16 FIGURE_SIZE = BLOCK_COUNT * BLOCK_SIZE; + constexpr uint16 SKY_BLOCK_COUNT = 0x40; + constexpr uint16 SKY_BLOCK_SIZE = 0x10; + constexpr uint16 SKY_FIGURE_SIZE = SKY_BLOCK_COUNT * SKY_BLOCK_SIZE; constexpr uint8 MAX_SKYLANDERS = 16; class SkylanderUSB { @@ -50,7 +50,7 @@ namespace nsyshid std::unique_ptr<FileStream> skyFile; uint8 status = 0; std::queue<uint8> queuedStatus; - std::array<uint8, BLOCK_COUNT * BLOCK_SIZE> data{}; + std::array<uint8, SKY_BLOCK_SIZE> data{}; uint32 lastId = 0; void Save(); diff --git a/src/config/CemuConfig.cpp b/src/config/CemuConfig.cpp index 03b12731..338392dd 100644 --- a/src/config/CemuConfig.cpp +++ b/src/config/CemuConfig.cpp @@ -344,6 +344,7 @@ void CemuConfig::Load(XMLConfigParser& parser) // emulatedusbdevices auto usbdevices = parser.get("EmulatedUsbDevices"); emulated_usb_devices.emulate_skylander_portal = usbdevices.get("EmulateSkylanderPortal", emulated_usb_devices.emulate_skylander_portal); + emulated_usb_devices.emulate_infinity_base = usbdevices.get("EmulateInfinityBase", emulated_usb_devices.emulate_infinity_base); } void CemuConfig::Save(XMLConfigParser& parser) @@ -541,6 +542,7 @@ void CemuConfig::Save(XMLConfigParser& parser) // emulated usb devices auto usbdevices = config.set("EmulatedUsbDevices"); usbdevices.set("EmulateSkylanderPortal", emulated_usb_devices.emulate_skylander_portal.GetValue()); + usbdevices.set("EmulateInfinityBase", emulated_usb_devices.emulate_infinity_base.GetValue()); } GameEntry* CemuConfig::GetGameEntryByTitleId(uint64 titleId) diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index 3f3da953..2a1d29cb 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -519,6 +519,7 @@ struct CemuConfig struct { ConfigValue<bool> emulate_skylander_portal{false}; + ConfigValue<bool> emulate_infinity_base{true}; }emulated_usb_devices{}; private: diff --git a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp index f43c3690..f4784f35 100644 --- a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp +++ b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp @@ -43,6 +43,7 @@ EmulatedUSBDeviceFrame::EmulatedUSBDeviceFrame(wxWindow* parent) auto* notebook = new wxNotebook(this, wxID_ANY); notebook->AddPage(AddSkylanderPage(notebook), _("Skylanders Portal")); + notebook->AddPage(AddInfinityPage(notebook), _("Infinity Base")); sizer->Add(notebook, 1, wxEXPAND | wxALL, 2); @@ -83,32 +84,98 @@ wxPanel* EmulatedUSBDeviceFrame::AddSkylanderPage(wxNotebook* notebook) return panel; } -wxBoxSizer* EmulatedUSBDeviceFrame::AddSkylanderRow(uint8 row_number, +wxPanel* EmulatedUSBDeviceFrame::AddInfinityPage(wxNotebook* notebook) +{ + auto* panel = new wxPanel(notebook); + auto* panelSizer = new wxBoxSizer(wxBOTH); + auto* box = new wxStaticBox(panel, wxID_ANY, _("Infinity Manager")); + auto* boxSizer = new wxStaticBoxSizer(box, wxBOTH); + + auto* row = new wxBoxSizer(wxHORIZONTAL); + + m_emulateBase = + new wxCheckBox(box, wxID_ANY, _("Emulate Infinity Base")); + m_emulateBase->SetValue( + GetConfig().emulated_usb_devices.emulate_infinity_base); + m_emulateBase->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) { + GetConfig().emulated_usb_devices.emulate_infinity_base = + m_emulateBase->IsChecked(); + g_config.Save(); + }); + row->Add(m_emulateBase, 1, wxEXPAND | wxALL, 2); + boxSizer->Add(row, 1, wxEXPAND | wxALL, 2); + boxSizer->Add(AddInfinityRow("Play Set/Power Disc", 0, box), 1, wxEXPAND | wxALL, 2); + boxSizer->Add(AddInfinityRow("Power Disc Two", 1, box), 1, wxEXPAND | wxALL, 2); + boxSizer->Add(AddInfinityRow("Power Disc Three", 2, box), 1, wxEXPAND | wxALL, 2); + boxSizer->Add(AddInfinityRow("Player One", 3, box), 1, wxEXPAND | wxALL, 2); + boxSizer->Add(AddInfinityRow("Player One Ability One", 4, box), 1, wxEXPAND | wxALL, 2); + boxSizer->Add(AddInfinityRow("Player One Ability Two", 5, box), 1, wxEXPAND | wxALL, 2); + boxSizer->Add(AddInfinityRow("Player Two", 6, box), 1, wxEXPAND | wxALL, 2); + boxSizer->Add(AddInfinityRow("Player Two Ability One", 7, box), 1, wxEXPAND | wxALL, 2); + boxSizer->Add(AddInfinityRow("Player Two Ability Two", 8, box), 1, wxEXPAND | wxALL, 2); + + panelSizer->Add(boxSizer, 1, wxEXPAND | wxALL, 2); + panel->SetSizerAndFit(panelSizer); + + return panel; +} + +wxBoxSizer* EmulatedUSBDeviceFrame::AddSkylanderRow(uint8 rowNumber, wxStaticBox* box) { auto* row = new wxBoxSizer(wxHORIZONTAL); row->Add(new wxStaticText(box, wxID_ANY, fmt::format("{} {}", _("Skylander").ToStdString(), - (row_number + 1))), + (rowNumber + 1))), 1, wxEXPAND | wxALL, 2); - m_skylanderSlots[row_number] = + m_skylanderSlots[rowNumber] = new wxTextCtrl(box, wxID_ANY, _("None"), wxDefaultPosition, wxDefaultSize, wxTE_READONLY); - m_skylanderSlots[row_number]->SetMinSize(wxSize(150, -1)); - m_skylanderSlots[row_number]->Disable(); - row->Add(m_skylanderSlots[row_number], 1, wxEXPAND | wxALL, 2); + m_skylanderSlots[rowNumber]->SetMinSize(wxSize(150, -1)); + m_skylanderSlots[rowNumber]->Disable(); + row->Add(m_skylanderSlots[rowNumber], 1, wxEXPAND | wxALL, 2); auto* loadButton = new wxButton(box, wxID_ANY, _("Load")); - loadButton->Bind(wxEVT_BUTTON, [row_number, this](wxCommandEvent&) { - LoadSkylander(row_number); + loadButton->Bind(wxEVT_BUTTON, [rowNumber, this](wxCommandEvent&) { + LoadSkylander(rowNumber); }); auto* createButton = new wxButton(box, wxID_ANY, _("Create")); - createButton->Bind(wxEVT_BUTTON, [row_number, this](wxCommandEvent&) { - CreateSkylander(row_number); + createButton->Bind(wxEVT_BUTTON, [rowNumber, this](wxCommandEvent&) { + CreateSkylander(rowNumber); }); auto* clearButton = new wxButton(box, wxID_ANY, _("Clear")); - clearButton->Bind(wxEVT_BUTTON, [row_number, this](wxCommandEvent&) { - ClearSkylander(row_number); + clearButton->Bind(wxEVT_BUTTON, [rowNumber, this](wxCommandEvent&) { + ClearSkylander(rowNumber); + }); + row->Add(loadButton, 1, wxEXPAND | wxALL, 2); + row->Add(createButton, 1, wxEXPAND | wxALL, 2); + row->Add(clearButton, 1, wxEXPAND | wxALL, 2); + + return row; +} + +wxBoxSizer* EmulatedUSBDeviceFrame::AddInfinityRow(wxString name, uint8 rowNumber, wxStaticBox* box) +{ + auto* row = new wxBoxSizer(wxHORIZONTAL); + + row->Add(new wxStaticText(box, wxID_ANY, name), 1, wxEXPAND | wxALL, 2); + m_infinitySlots[rowNumber] = + new wxTextCtrl(box, wxID_ANY, _("None"), wxDefaultPosition, wxDefaultSize, + wxTE_READONLY); + m_infinitySlots[rowNumber]->SetMinSize(wxSize(150, -1)); + m_infinitySlots[rowNumber]->Disable(); + row->Add(m_infinitySlots[rowNumber], 1, wxALL | wxEXPAND, 5); + auto* loadButton = new wxButton(box, wxID_ANY, _("Load")); + loadButton->Bind(wxEVT_BUTTON, [rowNumber, this](wxCommandEvent&) { + LoadFigure(rowNumber); + }); + auto* createButton = new wxButton(box, wxID_ANY, _("Create")); + createButton->Bind(wxEVT_BUTTON, [rowNumber, this](wxCommandEvent&) { + CreateFigure(rowNumber); + }); + auto* clearButton = new wxButton(box, wxID_ANY, _("Clear")); + clearButton->Bind(wxEVT_BUTTON, [rowNumber, this](wxCommandEvent&) { + ClearFigure(rowNumber); }); row->Add(loadButton, 1, wxEXPAND | wxALL, 2); row->Add(createButton, 1, wxEXPAND | wxALL, 2); @@ -138,7 +205,7 @@ void EmulatedUSBDeviceFrame::LoadSkylanderPath(uint8 slot, wxString path) return; } - std::array<uint8, 0x40 * 0x10> fileData; + std::array<uint8, nsyshid::SKY_FIGURE_SIZE> fileData; if (skyFile->readData(fileData.data(), fileData.size()) != fileData.size()) { wxMessageDialog open_error(this, "Failed to read file! File was too small"); @@ -218,15 +285,15 @@ CreateSkylanderDialog::CreateSkylanderDialog(wxWindow* parent, uint8 slot) long longSkyId; if (!editId->GetValue().ToLong(&longSkyId) || longSkyId > 0xFFFF) { - wxMessageDialog id_error(this, "Error Converting ID!", "ID Entered is Invalid"); - id_error.ShowModal(); + wxMessageDialog idError(this, "Error Converting ID!", "ID Entered is Invalid"); + idError.ShowModal(); return; } long longSkyVar; if (!editVar->GetValue().ToLong(&longSkyVar) || longSkyVar > 0xFFFF) { - wxMessageDialog id_error(this, "Error Converting Variant!", "Variant Entered is Invalid"); - id_error.ShowModal(); + wxMessageDialog idError(this, "Error Converting Variant!", "Variant Entered is Invalid"); + idError.ShowModal(); return; } uint16 skyId = longSkyId & 0xFFFF; @@ -284,6 +351,157 @@ wxString CreateSkylanderDialog::GetFilePath() const return m_filePath; } +CreateInfinityFigureDialog::CreateInfinityFigureDialog(wxWindow* parent, uint8 slot) + : wxDialog(parent, wxID_ANY, _("Infinity Figure Creator"), wxDefaultPosition, wxSize(500, 150)) +{ + auto* sizer = new wxBoxSizer(wxVERTICAL); + + auto* comboRow = new wxBoxSizer(wxHORIZONTAL); + + auto* comboBox = new wxComboBox(this, wxID_ANY); + comboBox->Append("---Select---", reinterpret_cast<void*>(0xFFFFFF)); + wxArrayString filterlist; + for (const auto& it : nsyshid::g_infinitybase.GetFigureList()) + { + const uint32 figure = it.first; + if ((slot == 0 && + ((figure > 0x1E8480 && figure < 0x2DC6BF) || (figure > 0x3D0900 && figure < 0x4C4B3F))) || + ((slot == 1 || slot == 2) && (figure > 0x3D0900 && figure < 0x4C4B3F)) || + ((slot == 3 || slot == 6) && figure < 0x1E847F) || + ((slot == 4 || slot == 5 || slot == 7 || slot == 8) && + (figure > 0x2DC6C0 && figure < 0x3D08FF))) + { + comboBox->Append(it.second.second, reinterpret_cast<void*>(figure)); + filterlist.Add(it.second.second); + } + } + comboBox->SetSelection(0); + bool enabled = comboBox->AutoComplete(filterlist); + comboRow->Add(comboBox, 1, wxEXPAND | wxALL, 2); + + auto* figNumRow = new wxBoxSizer(wxHORIZONTAL); + + wxIntegerValidator<uint32> validator; + + auto* labelFigNum = new wxStaticText(this, wxID_ANY, "Figure Number:"); + auto* editFigNum = new wxTextCtrl(this, wxID_ANY, _("0"), wxDefaultPosition, wxDefaultSize, 0, validator); + + figNumRow->Add(labelFigNum, 1, wxALL, 5); + figNumRow->Add(editFigNum, 1, wxALL, 5); + + auto* buttonRow = new wxBoxSizer(wxHORIZONTAL); + + auto* createButton = new wxButton(this, wxID_ANY, _("Create")); + createButton->Bind(wxEVT_BUTTON, [editFigNum, this](wxCommandEvent&) { + long longFigNum; + if (!editFigNum->GetValue().ToLong(&longFigNum)) + { + wxMessageDialog idError(this, "Error Converting Figure Number!", "Number Entered is Invalid"); + idError.ShowModal(); + this->EndModal(0);; + } + uint32 figNum = longFigNum & 0xFFFFFFFF; + auto figure = nsyshid::g_infinitybase.FindFigure(figNum); + wxString predefName = figure.second + ".bin"; + wxFileDialog + saveFileDialog(this, _("Create Infinity Figure file"), "", predefName, + "BIN files (*.bin)|*.bin", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + + if (saveFileDialog.ShowModal() == wxID_CANCEL) + this->EndModal(0);; + + m_filePath = saveFileDialog.GetPath(); + + nsyshid::g_infinitybase.CreateFigure(_utf8ToPath(m_filePath.utf8_string()), figNum, figure.first); + + this->EndModal(1); + }); + auto* cancelButton = new wxButton(this, wxID_ANY, _("Cancel")); + cancelButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { + this->EndModal(0); + }); + + comboBox->Bind(wxEVT_COMBOBOX, [comboBox, editFigNum, this](wxCommandEvent&) { + const uint64 fig_info = reinterpret_cast<uint64>(comboBox->GetClientData(comboBox->GetSelection())); + if (fig_info != 0xFFFFFF) + { + const uint32 figNum = fig_info & 0xFFFFFFFF; + + editFigNum->SetValue(wxString::Format(wxT("%i"), figNum)); + } + }); + + buttonRow->Add(createButton, 1, wxALL, 5); + buttonRow->Add(cancelButton, 1, wxALL, 5); + + sizer->Add(comboRow, 1, wxEXPAND | wxALL, 2); + sizer->Add(figNumRow, 1, wxEXPAND | wxALL, 2); + sizer->Add(buttonRow, 1, wxEXPAND | wxALL, 2); + + this->SetSizer(sizer); + this->Centre(wxBOTH); +} + +wxString CreateInfinityFigureDialog::GetFilePath() const +{ + return m_filePath; +} + +void EmulatedUSBDeviceFrame::LoadFigure(uint8 slot) +{ + wxFileDialog openFileDialog(this, _("Open Infinity Figure dump"), "", "", + "BIN files (*.bin)|*.bin", + wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (openFileDialog.ShowModal() != wxID_OK || openFileDialog.GetPath().empty()) + { + wxMessageDialog errorMessage(this, "File Okay Error"); + errorMessage.ShowModal(); + return; + } + + LoadFigurePath(slot, openFileDialog.GetPath()); +} + +void EmulatedUSBDeviceFrame::LoadFigurePath(uint8 slot, wxString path) +{ + std::unique_ptr<FileStream> infFile(FileStream::openFile2(_utf8ToPath(path.utf8_string()), true)); + if (!infFile) + { + wxMessageDialog errorMessage(this, "File Open Error"); + errorMessage.ShowModal(); + return; + } + + std::array<uint8, nsyshid::INF_FIGURE_SIZE> fileData; + if (infFile->readData(fileData.data(), fileData.size()) != fileData.size()) + { + wxMessageDialog open_error(this, "Failed to read file! File was too small"); + open_error.ShowModal(); + return; + } + ClearFigure(slot); + + uint32 number = nsyshid::g_infinitybase.LoadFigure(fileData, std::move(infFile), slot); + m_infinitySlots[slot]->ChangeValue(nsyshid::g_infinitybase.FindFigure(number).second); +} + +void EmulatedUSBDeviceFrame::CreateFigure(uint8 slot) +{ + cemuLog_log(LogType::Force, "Create Figure: {}", slot); + CreateInfinityFigureDialog create_dlg(this, slot); + create_dlg.ShowModal(); + if (create_dlg.GetReturnCode() == 1) + { + LoadFigurePath(slot, create_dlg.GetFilePath()); + } +} + +void EmulatedUSBDeviceFrame::ClearFigure(uint8 slot) +{ + m_infinitySlots[slot]->ChangeValue("None"); + nsyshid::g_infinitybase.RemoveFigure(slot); +} + void EmulatedUSBDeviceFrame::UpdateSkylanderEdits() { for (auto i = 0; i < nsyshid::MAX_SKYLANDERS; i++) diff --git a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h index 8988cb8a..ae29a036 100644 --- a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h +++ b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h @@ -5,6 +5,7 @@ #include <wx/dialog.h> #include <wx/frame.h> +#include "Cafe/OS/libs/nsyshid/Infinity.h" #include "Cafe/OS/libs/nsyshid/Skylander.h" class wxBoxSizer; @@ -23,15 +24,23 @@ class EmulatedUSBDeviceFrame : public wxFrame { private: wxCheckBox* m_emulatePortal; + wxCheckBox* m_emulateBase; std::array<wxTextCtrl*, nsyshid::MAX_SKYLANDERS> m_skylanderSlots; + std::array<wxTextCtrl*, nsyshid::MAX_FIGURES> m_infinitySlots; std::array<std::optional<std::tuple<uint8, uint16, uint16>>, nsyshid::MAX_SKYLANDERS> m_skySlots; wxPanel* AddSkylanderPage(wxNotebook* notebook); + wxPanel* AddInfinityPage(wxNotebook* notebook); wxBoxSizer* AddSkylanderRow(uint8 row_number, wxStaticBox* box); + wxBoxSizer* AddInfinityRow(wxString name, uint8 row_number, wxStaticBox* box); void LoadSkylander(uint8 slot); void LoadSkylanderPath(uint8 slot, wxString path); void CreateSkylander(uint8 slot); void ClearSkylander(uint8 slot); + void LoadFigure(uint8 slot); + void LoadFigurePath(uint8 slot, wxString path); + void CreateFigure(uint8 slot); + void ClearFigure(uint8 slot); void UpdateSkylanderEdits(); }; class CreateSkylanderDialog : public wxDialog { @@ -39,6 +48,15 @@ class CreateSkylanderDialog : public wxDialog { explicit CreateSkylanderDialog(wxWindow* parent, uint8 slot); wxString GetFilePath() const; + protected: + wxString m_filePath; +}; + +class CreateInfinityFigureDialog : public wxDialog { + public: + explicit CreateInfinityFigureDialog(wxWindow* parent, uint8 slot); + wxString GetFilePath() const; + protected: wxString m_filePath; }; \ No newline at end of file From e65abf48983f92a8de5259362f9842dbf3c28fb4 Mon Sep 17 00:00:00 2001 From: capitalistspz <keipitalists@proton.me> Date: Tue, 23 Jul 2024 21:18:55 +0100 Subject: [PATCH 144/299] Suppress unnecessary GTK messages (#1267) --- src/gui/CemuApp.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/gui/CemuApp.cpp b/src/gui/CemuApp.cpp index baa83888..f91c1e3a 100644 --- a/src/gui/CemuApp.cpp +++ b/src/gui/CemuApp.cpp @@ -235,6 +235,9 @@ void CemuApp::InitializeExistingMLCOrFail(fs::path mlc) bool CemuApp::OnInit() { +#if __WXGTK__ + GTKSuppressDiagnostics(G_LOG_LEVEL_MASK & ~G_LOG_FLAG_FATAL); +#endif std::set<fs::path> failedWriteAccess; DeterminePaths(failedWriteAccess); // make sure default cemu directories exist From 4b9c7c0d307495c679127381d6f00bab9f0c2933 Mon Sep 17 00:00:00 2001 From: Exverge <exverge@exverge.xyz> Date: Wed, 24 Jul 2024 02:32:40 -0400 Subject: [PATCH 145/299] Update Fedora build instructions (#1269) --- BUILD.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BUILD.md b/BUILD.md index 3ff2254f..1e92527e 100644 --- a/BUILD.md +++ b/BUILD.md @@ -57,7 +57,7 @@ At Step 3 in [Build Cemu using cmake and clang](#build-cemu-using-cmake-and-clan `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang-15 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-15 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja` #### For Fedora and derivatives: -`sudo dnf install clang cmake cubeb-devel freeglut-devel git glm-devel gtk3-devel kernel-headers libgcrypt-devel libsecret-devel libtool libusb1-devel llvm nasm ninja-build perl-core systemd-devel zlib-devel` +`sudo dnf install clang cmake cubeb-devel freeglut-devel git glm-devel gtk3-devel kernel-headers libgcrypt-devel libsecret-devel libtool libusb1-devel llvm nasm ninja-build perl-core systemd-devel zlib-devel zlib-static` ### Build Cemu From f1685eab665e1b262b47d6ea0c47d691fcc0f4a6 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Fri, 26 Jul 2024 05:48:42 +0200 Subject: [PATCH 146/299] h264: Use asynchronous decoding when possible (#1257) --- src/Cafe/CMakeLists.txt | 2 + .../OS/libs/coreinit/coreinit_SysHeap.cpp | 12 +- src/Cafe/OS/libs/coreinit/coreinit_SysHeap.h | 3 + src/Cafe/OS/libs/h264_avc/H264Dec.cpp | 755 +++--------------- .../OS/libs/h264_avc/H264DecBackendAVC.cpp | 502 ++++++++++++ src/Cafe/OS/libs/h264_avc/H264DecInternal.h | 139 ++++ .../OS/libs/h264_avc/parser/H264Parser.cpp | 17 +- src/Cafe/OS/libs/h264_avc/parser/H264Parser.h | 2 + 8 files changed, 787 insertions(+), 645 deletions(-) create mode 100644 src/Cafe/OS/libs/h264_avc/H264DecBackendAVC.cpp create mode 100644 src/Cafe/OS/libs/h264_avc/H264DecInternal.h diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index 0fb7a44b..91d257b2 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -374,7 +374,9 @@ add_library(CemuCafe OS/libs/gx2/GX2_Texture.h OS/libs/gx2/GX2_TilingAperture.cpp OS/libs/h264_avc/H264Dec.cpp + OS/libs/h264_avc/H264DecBackendAVC.cpp OS/libs/h264_avc/h264dec.h + OS/libs/h264_avc/H264DecInternal.h OS/libs/h264_avc/parser OS/libs/h264_avc/parser/H264Parser.cpp OS/libs/h264_avc/parser/H264Parser.h diff --git a/src/Cafe/OS/libs/coreinit/coreinit_SysHeap.cpp b/src/Cafe/OS/libs/coreinit/coreinit_SysHeap.cpp index e37949d7..2f819c50 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_SysHeap.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_SysHeap.cpp @@ -14,13 +14,10 @@ namespace coreinit return coreinit::MEMAllocFromExpHeapEx(_sysHeapHandle, size, alignment); } - void export_OSAllocFromSystem(PPCInterpreter_t* hCPU) + void OSFreeToSystem(void* ptr) { - ppcDefineParamU32(size, 0); - ppcDefineParamS32(alignment, 1); - MEMPTR<void> mem = OSAllocFromSystem(size, alignment); - cemuLog_logDebug(LogType::Force, "OSAllocFromSystem(0x{:x}, {}) -> 0x{:08x}", size, alignment, mem.GetMPTR()); - osLib_returnFromFunction(hCPU, mem.GetMPTR()); + _sysHeapFreeCounter++; + coreinit::MEMFreeToExpHeap(_sysHeapHandle, ptr); } void InitSysHeap() @@ -34,7 +31,8 @@ namespace coreinit void InitializeSysHeap() { - osLib_addFunction("coreinit", "OSAllocFromSystem", export_OSAllocFromSystem); + cafeExportRegister("h264", OSAllocFromSystem, LogType::CoreinitMem); + cafeExportRegister("h264", OSFreeToSystem, LogType::CoreinitMem); } } diff --git a/src/Cafe/OS/libs/coreinit/coreinit_SysHeap.h b/src/Cafe/OS/libs/coreinit/coreinit_SysHeap.h index 428224af..ad115754 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_SysHeap.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_SysHeap.h @@ -4,5 +4,8 @@ namespace coreinit { void InitSysHeap(); + void* OSAllocFromSystem(uint32 size, uint32 alignment); + void OSFreeToSystem(void* ptr); + void InitializeSysHeap(); } \ No newline at end of file diff --git a/src/Cafe/OS/libs/h264_avc/H264Dec.cpp b/src/Cafe/OS/libs/h264_avc/H264Dec.cpp index 024965fd..82db039b 100644 --- a/src/Cafe/OS/libs/h264_avc/H264Dec.cpp +++ b/src/Cafe/OS/libs/h264_avc/H264Dec.cpp @@ -1,17 +1,12 @@ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/HW/Espresso/PPCCallback.h" #include "Cafe/OS/libs/h264_avc/parser/H264Parser.h" +#include "Cafe/OS/libs/h264_avc/H264DecInternal.h" #include "util/highresolutiontimer/HighResolutionTimer.h" #include "Cafe/CafeSystem.h" #include "h264dec.h" -extern "C" -{ -#include "../dependencies/ih264d/common/ih264_typedefs.h" -#include "../dependencies/ih264d/decoder/ih264d.h" -}; - enum class H264DEC_STATUS : uint32 { SUCCESS = 0x0, @@ -33,10 +28,35 @@ namespace H264 return false; } + struct H264Context + { + struct + { + MEMPTR<void> ptr{ nullptr }; + uint32be length{ 0 }; + float64be timestamp; + }BitStream; + struct + { + MEMPTR<void> outputFunc{ nullptr }; + uint8be outputPerFrame{ 0 }; // whats the default? + MEMPTR<void> userMemoryParam{ nullptr }; + }Param; + // misc + uint32be sessionHandle; + + // decoder state + struct + { + uint32 numFramesInFlight{0}; + }decoderState; + }; + uint32 H264DECMemoryRequirement(uint32 codecProfile, uint32 codecLevel, uint32 width, uint32 height, uint32be* sizeRequirementOut) { if (H264_IsBotW()) { + static_assert(sizeof(H264Context) < 256); *sizeRequirementOut = 256; return 0; } @@ -169,590 +189,47 @@ namespace H264 return H264DEC_STATUS::BAD_STREAM; } - struct H264Context - { - struct - { - MEMPTR<void> ptr{ nullptr }; - uint32be length{ 0 }; - float64be timestamp; - }BitStream; - struct - { - MEMPTR<void> outputFunc{ nullptr }; - uint8be outputPerFrame{ 0 }; // whats the default? - MEMPTR<void> userMemoryParam{ nullptr }; - }Param; - // misc - uint32be sessionHandle; - }; - - class H264AVCDecoder - { - static void* ivd_aligned_malloc(void* ctxt, WORD32 alignment, WORD32 size) - { -#ifdef _WIN32 - return _aligned_malloc(size, alignment); -#else - // alignment is atleast sizeof(void*) - alignment = std::max<WORD32>(alignment, sizeof(void*)); - - //smallest multiple of 2 at least as large as alignment - alignment--; - alignment |= alignment << 1; - alignment |= alignment >> 1; - alignment |= alignment >> 2; - alignment |= alignment >> 4; - alignment |= alignment >> 8; - alignment |= alignment >> 16; - alignment ^= (alignment >> 1); - - void* temp; - posix_memalign(&temp, (size_t)alignment, (size_t)size); - return temp; -#endif - } - - static void ivd_aligned_free(void* ctxt, void* buf) - { -#ifdef _WIN32 - _aligned_free(buf); -#else - free(buf); -#endif - return; - } - - public: - struct DecodeResult - { - bool frameReady{ false }; - double timestamp; - void* imageOutput; - ivd_video_decode_op_t decodeOutput; - }; - - void Init(bool isBufferedMode) - { - ih264d_create_ip_t s_create_ip{ 0 }; - ih264d_create_op_t s_create_op{ 0 }; - - s_create_ip.s_ivd_create_ip_t.u4_size = sizeof(ih264d_create_ip_t); - s_create_ip.s_ivd_create_ip_t.e_cmd = IVD_CMD_CREATE; - s_create_ip.s_ivd_create_ip_t.u4_share_disp_buf = 1; // shared display buffer mode -> We give the decoder a list of buffers that it will use (?) - - s_create_op.s_ivd_create_op_t.u4_size = sizeof(ih264d_create_op_t); - s_create_ip.s_ivd_create_ip_t.e_output_format = IV_YUV_420SP_UV; - s_create_ip.s_ivd_create_ip_t.pf_aligned_alloc = ivd_aligned_malloc; - s_create_ip.s_ivd_create_ip_t.pf_aligned_free = ivd_aligned_free; - s_create_ip.s_ivd_create_ip_t.pv_mem_ctxt = NULL; - - WORD32 status = ih264d_api_function(m_codecCtx, &s_create_ip, &s_create_op); - cemu_assert(!status); - - m_codecCtx = (iv_obj_t*)s_create_op.s_ivd_create_op_t.pv_handle; - m_codecCtx->pv_fxns = (void*)&ih264d_api_function; - m_codecCtx->u4_size = sizeof(iv_obj_t); - - SetDecoderCoreCount(1); - - m_isBufferedMode = isBufferedMode; - - UpdateParameters(false); - - m_bufferedResults.clear(); - m_numDecodedFrames = 0; - m_hasBufferSizeInfo = false; - m_timestampIndex = 0; - } - - void Destroy() - { - if (!m_codecCtx) - return; - ih264d_delete_ip_t s_delete_ip{ 0 }; - ih264d_delete_op_t s_delete_op{ 0 }; - s_delete_ip.s_ivd_delete_ip_t.u4_size = sizeof(ih264d_delete_ip_t); - s_delete_ip.s_ivd_delete_ip_t.e_cmd = IVD_CMD_DELETE; - s_delete_op.s_ivd_delete_op_t.u4_size = sizeof(ih264d_delete_op_t); - WORD32 status = ih264d_api_function(m_codecCtx, &s_delete_ip, &s_delete_op); - cemu_assert_debug(!status); - m_codecCtx = nullptr; - } - - void SetDecoderCoreCount(uint32 coreCount) - { - ih264d_ctl_set_num_cores_ip_t s_set_cores_ip; - ih264d_ctl_set_num_cores_op_t s_set_cores_op; - s_set_cores_ip.e_cmd = IVD_CMD_VIDEO_CTL; - s_set_cores_ip.e_sub_cmd = (IVD_CONTROL_API_COMMAND_TYPE_T)IH264D_CMD_CTL_SET_NUM_CORES; - s_set_cores_ip.u4_num_cores = coreCount; // valid numbers are 1-4 - s_set_cores_ip.u4_size = sizeof(ih264d_ctl_set_num_cores_ip_t); - s_set_cores_op.u4_size = sizeof(ih264d_ctl_set_num_cores_op_t); - IV_API_CALL_STATUS_T status = ih264d_api_function(m_codecCtx, (void *)&s_set_cores_ip, (void *)&s_set_cores_op); - cemu_assert(status == IV_SUCCESS); - } - - static bool GetImageInfo(uint8* stream, uint32 length, uint32& imageWidth, uint32& imageHeight) - { - // create temporary decoder - ih264d_create_ip_t s_create_ip{ 0 }; - ih264d_create_op_t s_create_op{ 0 }; - s_create_ip.s_ivd_create_ip_t.u4_size = sizeof(ih264d_create_ip_t); - s_create_ip.s_ivd_create_ip_t.e_cmd = IVD_CMD_CREATE; - s_create_ip.s_ivd_create_ip_t.u4_share_disp_buf = 0; - s_create_op.s_ivd_create_op_t.u4_size = sizeof(ih264d_create_op_t); - s_create_ip.s_ivd_create_ip_t.e_output_format = IV_YUV_420SP_UV; - s_create_ip.s_ivd_create_ip_t.pf_aligned_alloc = ivd_aligned_malloc; - s_create_ip.s_ivd_create_ip_t.pf_aligned_free = ivd_aligned_free; - s_create_ip.s_ivd_create_ip_t.pv_mem_ctxt = NULL; - iv_obj_t* ctx = nullptr; - WORD32 status = ih264d_api_function(ctx, &s_create_ip, &s_create_op); - cemu_assert_debug(!status); - if (status != IV_SUCCESS) - return false; - ctx = (iv_obj_t*)s_create_op.s_ivd_create_op_t.pv_handle; - ctx->pv_fxns = (void*)&ih264d_api_function; - ctx->u4_size = sizeof(iv_obj_t); - // set header-only mode - ih264d_ctl_set_config_ip_t s_h264d_ctl_ip{ 0 }; - ih264d_ctl_set_config_op_t s_h264d_ctl_op{ 0 }; - ivd_ctl_set_config_ip_t* ps_ctl_ip = &s_h264d_ctl_ip.s_ivd_ctl_set_config_ip_t; - ivd_ctl_set_config_op_t* ps_ctl_op = &s_h264d_ctl_op.s_ivd_ctl_set_config_op_t; - ps_ctl_ip->u4_disp_wd = 0; - ps_ctl_ip->e_frm_skip_mode = IVD_SKIP_NONE; - ps_ctl_ip->e_frm_out_mode = IVD_DISPLAY_FRAME_OUT; - ps_ctl_ip->e_vid_dec_mode = IVD_DECODE_HEADER; - ps_ctl_ip->e_cmd = IVD_CMD_VIDEO_CTL; - ps_ctl_ip->e_sub_cmd = IVD_CMD_CTL_SETPARAMS; - ps_ctl_ip->u4_size = sizeof(ih264d_ctl_set_config_ip_t); - ps_ctl_op->u4_size = sizeof(ih264d_ctl_set_config_op_t); - status = ih264d_api_function(ctx, &s_h264d_ctl_ip, &s_h264d_ctl_op); - cemu_assert(!status); - // decode stream - ivd_video_decode_ip_t s_dec_ip{ 0 }; - ivd_video_decode_op_t s_dec_op{ 0 }; - s_dec_ip.u4_size = sizeof(ivd_video_decode_ip_t); - s_dec_op.u4_size = sizeof(ivd_video_decode_op_t); - s_dec_ip.e_cmd = IVD_CMD_VIDEO_DECODE; - s_dec_ip.pv_stream_buffer = stream; - s_dec_ip.u4_num_Bytes = length; - s_dec_ip.s_out_buffer.u4_num_bufs = 0; - - s_dec_op.u4_raw_wd = 0; - s_dec_op.u4_raw_ht = 0; - - status = ih264d_api_function(ctx, &s_dec_ip, &s_dec_op); - //cemu_assert(status == 0); -> This errors when not both the headers are present, but it will still set the parameters we need - bool isValid = false; - if (true)//status == 0) - { - imageWidth = s_dec_op.u4_raw_wd; - imageHeight = s_dec_op.u4_raw_ht; - cemu_assert_debug(imageWidth != 0 && imageHeight != 0); - isValid = true; - } - // destroy decoder - ih264d_delete_ip_t s_delete_ip{ 0 }; - ih264d_delete_op_t s_delete_op{ 0 }; - s_delete_ip.s_ivd_delete_ip_t.u4_size = sizeof(ih264d_delete_ip_t); - s_delete_ip.s_ivd_delete_ip_t.e_cmd = IVD_CMD_DELETE; - s_delete_op.s_ivd_delete_op_t.u4_size = sizeof(ih264d_delete_op_t); - status = ih264d_api_function(ctx, &s_delete_ip, &s_delete_op); - cemu_assert_debug(!status); - return isValid; - } - - void Decode(void* data, uint32 length, double timestamp, void* imageOutput, DecodeResult& decodeResult) - { - if (!m_hasBufferSizeInfo) - { - uint32 numByteConsumed = 0; - if (!DetermineBufferSizes(data, length, numByteConsumed)) - { - cemuLog_log(LogType::Force, "H264: Unable to determine picture size. Ignoring decode input"); - decodeResult.frameReady = false; - return; - } - length -= numByteConsumed; - data = (uint8*)data + numByteConsumed; - m_hasBufferSizeInfo = true; - } - - ivd_video_decode_ip_t s_dec_ip{ 0 }; - ivd_video_decode_op_t s_dec_op{ 0 }; - s_dec_ip.u4_size = sizeof(ivd_video_decode_ip_t); - s_dec_op.u4_size = sizeof(ivd_video_decode_op_t); - - s_dec_ip.e_cmd = IVD_CMD_VIDEO_DECODE; - - // remember timestamp and associated output buffer - m_timestamps[m_timestampIndex] = timestamp; - m_imageBuffers[m_timestampIndex] = imageOutput; - s_dec_ip.u4_ts = m_timestampIndex; - m_timestampIndex = (m_timestampIndex + 1) % 64; - - s_dec_ip.pv_stream_buffer = (uint8*)data; - s_dec_ip.u4_num_Bytes = length; - - s_dec_ip.s_out_buffer.u4_min_out_buf_size[0] = 0; - s_dec_ip.s_out_buffer.u4_min_out_buf_size[1] = 0; - s_dec_ip.s_out_buffer.u4_num_bufs = 0; - - BenchmarkTimer bt; - bt.Start(); - WORD32 status = ih264d_api_function(m_codecCtx, &s_dec_ip, &s_dec_op); - if (status != 0 && (s_dec_op.u4_error_code&0xFF) == IVD_RES_CHANGED) - { - // resolution change - ResetDecoder(); - m_hasBufferSizeInfo = false; - Decode(data, length, timestamp, imageOutput, decodeResult); - return; - } - else if (status != 0) - { - cemuLog_log(LogType::Force, "H264: Failed to decode frame (error 0x{:08x})", status); - decodeResult.frameReady = false; - return; - } - - bt.Stop(); - double decodeTime = bt.GetElapsedMilliseconds(); - - cemu_assert(s_dec_op.u4_frame_decoded_flag); - cemu_assert_debug(s_dec_op.u4_num_bytes_consumed == length); - - cemu_assert_debug(m_isBufferedMode || s_dec_op.u4_output_present); // if buffered mode is disabled, then every input should output a frame (except for partial slices?) - - if (s_dec_op.u4_output_present) - { - cemu_assert(s_dec_op.e_output_format == IV_YUV_420SP_UV); - if (H264_IsBotW()) - { - if (s_dec_op.s_disp_frm_buf.u4_y_wd == 1920 && s_dec_op.s_disp_frm_buf.u4_y_ht == 1088) - s_dec_op.s_disp_frm_buf.u4_y_ht = 1080; - } - DecodeResult tmpResult; - tmpResult.frameReady = s_dec_op.u4_output_present != 0; - tmpResult.timestamp = m_timestamps[s_dec_op.u4_ts]; - tmpResult.imageOutput = m_imageBuffers[s_dec_op.u4_ts]; - tmpResult.decodeOutput = s_dec_op; - AddBufferedResult(tmpResult); - // transfer image to PPC output buffer and also correct stride - bt.Start(); - CopyImageToResultBuffer((uint8*)s_dec_op.s_disp_frm_buf.pv_y_buf, (uint8*)s_dec_op.s_disp_frm_buf.pv_u_buf, (uint8*)m_imageBuffers[s_dec_op.u4_ts], s_dec_op); - bt.Stop(); - double copyTime = bt.GetElapsedMilliseconds(); - // release buffer - sint32 bufferId = -1; - for (size_t i = 0; i < m_displayBuf.size(); i++) - { - if (s_dec_op.s_disp_frm_buf.pv_y_buf >= m_displayBuf[i].data() && s_dec_op.s_disp_frm_buf.pv_y_buf < (m_displayBuf[i].data() + m_displayBuf[i].size())) - { - bufferId = (sint32)i; - break; - } - } - cemu_assert_debug(bufferId == s_dec_op.u4_disp_buf_id); - cemu_assert(bufferId >= 0); - ivd_rel_display_frame_ip_t s_video_rel_disp_ip{ 0 }; - ivd_rel_display_frame_op_t s_video_rel_disp_op{ 0 }; - s_video_rel_disp_ip.e_cmd = IVD_CMD_REL_DISPLAY_FRAME; - s_video_rel_disp_ip.u4_size = sizeof(ivd_rel_display_frame_ip_t); - s_video_rel_disp_op.u4_size = sizeof(ivd_rel_display_frame_op_t); - s_video_rel_disp_ip.u4_disp_buf_id = bufferId; - status = ih264d_api_function(m_codecCtx, &s_video_rel_disp_ip, &s_video_rel_disp_op); - cemu_assert(!status); - - cemuLog_log(LogType::H264, "H264Bench | DecodeTime {}ms CopyTime {}ms", decodeTime, copyTime); - } - else - { - cemuLog_log(LogType::H264, "H264Bench | DecodeTime{}ms", decodeTime); - } - - if (s_dec_op.u4_frame_decoded_flag) - m_numDecodedFrames++; - - if (m_isBufferedMode) - { - // in buffered mode, always buffer 5 frames regardless of actual reordering and decoder latency - if (m_numDecodedFrames > 5) - GetCurrentBufferedResult(decodeResult); - } - else if(m_numDecodedFrames > 0) - GetCurrentBufferedResult(decodeResult); - - // get VUI - //ih264d_ctl_get_vui_params_ip_t s_ctl_get_vui_params_ip; - //ih264d_ctl_get_vui_params_op_t s_ctl_get_vui_params_op; - - //s_ctl_get_vui_params_ip.e_cmd = IVD_CMD_VIDEO_CTL; - //s_ctl_get_vui_params_ip.e_sub_cmd = (IVD_CONTROL_API_COMMAND_TYPE_T)IH264D_CMD_CTL_GET_VUI_PARAMS; - //s_ctl_get_vui_params_ip.u4_size = sizeof(ih264d_ctl_get_vui_params_ip_t); - //s_ctl_get_vui_params_op.u4_size = sizeof(ih264d_ctl_get_vui_params_op_t); - - //status = ih264d_api_function(mCodecCtx, &s_ctl_get_vui_params_ip, &s_ctl_get_vui_params_op); - //cemu_assert(status == 0); - } - - std::vector<DecodeResult> Flush() - { - std::vector<DecodeResult> results; - // set flush mode - ivd_ctl_flush_ip_t s_video_flush_ip{ 0 }; - ivd_ctl_flush_op_t s_video_flush_op{ 0 }; - s_video_flush_ip.e_cmd = IVD_CMD_VIDEO_CTL; - s_video_flush_ip.e_sub_cmd = IVD_CMD_CTL_FLUSH; - s_video_flush_ip.u4_size = sizeof(ivd_ctl_flush_ip_t); - s_video_flush_op.u4_size = sizeof(ivd_ctl_flush_op_t); - WORD32 status = ih264d_api_function(m_codecCtx, &s_video_flush_ip, &s_video_flush_op); - if (status != 0) - cemuLog_log(LogType::Force, "H264Dec: Unexpected error during flush ({})", status); - // get all frames from the codec - while (true) - { - ivd_video_decode_ip_t s_dec_ip{ 0 }; - ivd_video_decode_op_t s_dec_op{ 0 }; - s_dec_ip.u4_size = sizeof(ivd_video_decode_ip_t); - s_dec_op.u4_size = sizeof(ivd_video_decode_op_t); - s_dec_ip.e_cmd = IVD_CMD_VIDEO_DECODE; - s_dec_ip.pv_stream_buffer = NULL; - s_dec_ip.u4_num_Bytes = 0; - s_dec_ip.s_out_buffer.u4_min_out_buf_size[0] = 0; - s_dec_ip.s_out_buffer.u4_min_out_buf_size[1] = 0; - s_dec_ip.s_out_buffer.u4_num_bufs = 0; - status = ih264d_api_function(m_codecCtx, &s_dec_ip, &s_dec_op); - if (status != 0) - break; - cemu_assert_debug(s_dec_op.u4_output_present != 0); // should never be zero? - if(s_dec_op.u4_output_present == 0) - continue; - if (H264_IsBotW()) - { - if (s_dec_op.s_disp_frm_buf.u4_y_wd == 1920 && s_dec_op.s_disp_frm_buf.u4_y_ht == 1088) - s_dec_op.s_disp_frm_buf.u4_y_ht = 1080; - } - DecodeResult tmpResult; - tmpResult.frameReady = s_dec_op.u4_output_present != 0; - tmpResult.timestamp = m_timestamps[s_dec_op.u4_ts]; - tmpResult.imageOutput = m_imageBuffers[s_dec_op.u4_ts]; - tmpResult.decodeOutput = s_dec_op; - AddBufferedResult(tmpResult); - CopyImageToResultBuffer((uint8*)s_dec_op.s_disp_frm_buf.pv_y_buf, (uint8*)s_dec_op.s_disp_frm_buf.pv_u_buf, (uint8*)m_imageBuffers[s_dec_op.u4_ts], s_dec_op); - } - results = std::move(m_bufferedResults); - return results; - } - - void CopyImageToResultBuffer(uint8* yIn, uint8* uvIn, uint8* bufOut, ivd_video_decode_op_t& decodeInfo) - { - uint32 imageWidth = decodeInfo.s_disp_frm_buf.u4_y_wd; - uint32 imageHeight = decodeInfo.s_disp_frm_buf.u4_y_ht; - - size_t inputStride = decodeInfo.s_disp_frm_buf.u4_y_strd; - size_t outputStride = (imageWidth + 0xFF) & ~0xFF; - - // copy Y - uint8* yOut = bufOut; - for (uint32 row = 0; row < imageHeight; row++) - { - memcpy(yOut, yIn, imageWidth); - yIn += inputStride; - yOut += outputStride; - } - - // copy UV - uint8* uvOut = bufOut + outputStride * imageHeight; - for (uint32 row = 0; row < imageHeight/2; row++) - { - memcpy(uvOut, uvIn, imageWidth); - uvIn += inputStride; - uvOut += outputStride; - } - } - - private: - - bool DetermineBufferSizes(void* data, uint32 length, uint32& numByteConsumed) - { - numByteConsumed = 0; - UpdateParameters(true); - - ivd_video_decode_ip_t s_dec_ip{ 0 }; - ivd_video_decode_op_t s_dec_op{ 0 }; - s_dec_ip.u4_size = sizeof(ivd_video_decode_ip_t); - s_dec_op.u4_size = sizeof(ivd_video_decode_op_t); - - s_dec_ip.e_cmd = IVD_CMD_VIDEO_DECODE; - s_dec_ip.pv_stream_buffer = (uint8*)data; - s_dec_ip.u4_num_Bytes = length; - s_dec_ip.s_out_buffer.u4_num_bufs = 0; - WORD32 status = ih264d_api_function(m_codecCtx, &s_dec_ip, &s_dec_op); - if (status != 0) - { - cemuLog_log(LogType::Force, "H264: Unable to determine buffer sizes for stream"); - return false; - } - numByteConsumed = s_dec_op.u4_num_bytes_consumed; - cemu_assert(status == 0); - if (s_dec_op.u4_pic_wd == 0 || s_dec_op.u4_pic_ht == 0) - return false; - UpdateParameters(false); - ReinitBuffers(); - return true; - } - - void ReinitBuffers() - { - ivd_ctl_getbufinfo_ip_t s_ctl_ip{ 0 }; - ivd_ctl_getbufinfo_op_t s_ctl_op{ 0 }; - WORD32 outlen = 0; - - s_ctl_ip.e_cmd = IVD_CMD_VIDEO_CTL; - s_ctl_ip.e_sub_cmd = IVD_CMD_CTL_GETBUFINFO; - s_ctl_ip.u4_size = sizeof(ivd_ctl_getbufinfo_ip_t); - s_ctl_op.u4_size = sizeof(ivd_ctl_getbufinfo_op_t); - - WORD32 status = ih264d_api_function(m_codecCtx, &s_ctl_ip, &s_ctl_op); - cemu_assert(!status); - - // allocate - for (uint32 i = 0; i < s_ctl_op.u4_num_disp_bufs; i++) - { - m_displayBuf.emplace_back().resize(s_ctl_op.u4_min_out_buf_size[0] + s_ctl_op.u4_min_out_buf_size[1]); - } - // set - ivd_set_display_frame_ip_t s_set_display_frame_ip{ 0 }; // make sure to zero-initialize this. The codec seems to check the first 3 pointers/sizes per frame, regardless of the value of u4_num_bufs - ivd_set_display_frame_op_t s_set_display_frame_op{ 0 }; - - s_set_display_frame_ip.e_cmd = IVD_CMD_SET_DISPLAY_FRAME; - s_set_display_frame_ip.u4_size = sizeof(ivd_set_display_frame_ip_t); - s_set_display_frame_op.u4_size = sizeof(ivd_set_display_frame_op_t); - - cemu_assert_debug(s_ctl_op.u4_min_num_out_bufs == 2); - cemu_assert_debug(s_ctl_op.u4_min_out_buf_size[0] != 0 && s_ctl_op.u4_min_out_buf_size[1] != 0); - - s_set_display_frame_ip.num_disp_bufs = s_ctl_op.u4_num_disp_bufs; - - for (uint32 i = 0; i < s_ctl_op.u4_num_disp_bufs; i++) - { - s_set_display_frame_ip.s_disp_buffer[i].u4_num_bufs = 2; - s_set_display_frame_ip.s_disp_buffer[i].u4_min_out_buf_size[0] = s_ctl_op.u4_min_out_buf_size[0]; - s_set_display_frame_ip.s_disp_buffer[i].u4_min_out_buf_size[1] = s_ctl_op.u4_min_out_buf_size[1]; - s_set_display_frame_ip.s_disp_buffer[i].pu1_bufs[0] = m_displayBuf[i].data() + 0; - s_set_display_frame_ip.s_disp_buffer[i].pu1_bufs[1] = m_displayBuf[i].data() + s_ctl_op.u4_min_out_buf_size[0]; - } - - status = ih264d_api_function(m_codecCtx, &s_set_display_frame_ip, &s_set_display_frame_op); - cemu_assert(!status); - - - // mark all as released (available) - for (uint32 i = 0; i < s_ctl_op.u4_num_disp_bufs; i++) - { - ivd_rel_display_frame_ip_t s_video_rel_disp_ip{ 0 }; - ivd_rel_display_frame_op_t s_video_rel_disp_op{ 0 }; - - s_video_rel_disp_ip.e_cmd = IVD_CMD_REL_DISPLAY_FRAME; - s_video_rel_disp_ip.u4_size = sizeof(ivd_rel_display_frame_ip_t); - s_video_rel_disp_op.u4_size = sizeof(ivd_rel_display_frame_op_t); - s_video_rel_disp_ip.u4_disp_buf_id = i; - - status = ih264d_api_function(m_codecCtx, &s_video_rel_disp_ip, &s_video_rel_disp_op); - cemu_assert(!status); - } - } - - void ResetDecoder() - { - ivd_ctl_reset_ip_t s_ctl_ip; - ivd_ctl_reset_op_t s_ctl_op; - - s_ctl_ip.e_cmd = IVD_CMD_VIDEO_CTL; - s_ctl_ip.e_sub_cmd = IVD_CMD_CTL_RESET; - s_ctl_ip.u4_size = sizeof(ivd_ctl_reset_ip_t); - s_ctl_op.u4_size = sizeof(ivd_ctl_reset_op_t); - - WORD32 status = ih264d_api_function(m_codecCtx, (void*)&s_ctl_ip, (void*)&s_ctl_op); - cemu_assert_debug(status == 0); - } - - void UpdateParameters(bool headerDecodeOnly) - { - ih264d_ctl_set_config_ip_t s_h264d_ctl_ip{ 0 }; - ih264d_ctl_set_config_op_t s_h264d_ctl_op{ 0 }; - ivd_ctl_set_config_ip_t* ps_ctl_ip = &s_h264d_ctl_ip.s_ivd_ctl_set_config_ip_t; - ivd_ctl_set_config_op_t* ps_ctl_op = &s_h264d_ctl_op.s_ivd_ctl_set_config_op_t; - - ps_ctl_ip->u4_disp_wd = 0; - ps_ctl_ip->e_frm_skip_mode = IVD_SKIP_NONE; - ps_ctl_ip->e_frm_out_mode = m_isBufferedMode ? IVD_DISPLAY_FRAME_OUT : IVD_DECODE_FRAME_OUT; - ps_ctl_ip->e_vid_dec_mode = headerDecodeOnly ? IVD_DECODE_HEADER : IVD_DECODE_FRAME; - ps_ctl_ip->e_cmd = IVD_CMD_VIDEO_CTL; - ps_ctl_ip->e_sub_cmd = IVD_CMD_CTL_SETPARAMS; - ps_ctl_ip->u4_size = sizeof(ih264d_ctl_set_config_ip_t); - ps_ctl_op->u4_size = sizeof(ih264d_ctl_set_config_op_t); - - WORD32 status = ih264d_api_function(m_codecCtx, &s_h264d_ctl_ip, &s_h264d_ctl_op); - cemu_assert(status == 0); - } - - /* In non-flush mode we have a delay of (at least?) 5 frames */ - void AddBufferedResult(DecodeResult& decodeResult) - { - if (decodeResult.frameReady) - m_bufferedResults.emplace_back(decodeResult); - } - - void GetCurrentBufferedResult(DecodeResult& decodeResult) - { - cemu_assert(!m_bufferedResults.empty()); - if (m_bufferedResults.empty()) - { - decodeResult.frameReady = false; - return; - } - decodeResult = m_bufferedResults.front(); - m_bufferedResults.erase(m_bufferedResults.begin()); - } - private: - iv_obj_t* m_codecCtx{nullptr}; - bool m_hasBufferSizeInfo{ false }; - bool m_isBufferedMode{ false }; - double m_timestamps[64]; - void* m_imageBuffers[64]; - uint32 m_timestampIndex{0}; - std::vector<DecodeResult> m_bufferedResults; - uint32 m_numDecodedFrames{0}; - std::vector<std::vector<uint8>> m_displayBuf; - }; - H264DEC_STATUS H264DECGetImageSize(uint8* stream, uint32 length, uint32 offset, uint32be* outputWidth, uint32be* outputHeight) { - cemu_assert(offset <= length); - - uint32 imageWidth, imageHeight; - - if (H264AVCDecoder::GetImageInfo(stream, length, imageWidth, imageHeight)) + if(!stream || length < 4 || !outputWidth || !outputHeight) + return H264DEC_STATUS::INVALID_PARAM; + if( (offset+4) > length ) + return H264DEC_STATUS::INVALID_PARAM; + uint8* cur = stream + offset; + uint8* end = stream + length; + cur += 2; // we access cur[-2] and cur[-1] so we need to start at offset 2 + while(cur < end-2) { - if (H264_IsBotW()) + // check for start code + if(*cur != 1) { - if (imageWidth == 1920 && imageHeight == 1088) - imageHeight = 1080; + cur++; + continue; } - *outputWidth = imageWidth; - *outputHeight = imageHeight; + // check if this is a valid NAL header + if(cur[-2] != 0 || cur[-1] != 0 || cur[0] != 1) + { + cur++; + continue; + } + uint8 nalHeader = cur[1]; + if((nalHeader & 0x1F) != 7) + { + cur++; + continue; + } + h264State_seq_parameter_set_t psp; + bool r = h264Parser_ParseSPS(cur+2, end-cur-2, psp); + if(!r) + { + cemu_assert_suspicious(); // should not happen + return H264DEC_STATUS::BAD_STREAM; + } + *outputWidth = (psp.pic_width_in_mbs_minus1 + 1) * 16; + *outputHeight = (psp.pic_height_in_map_units_minus1 + 1) * 16; // affected by frame_mbs_only_flag? + return H264DEC_STATUS::SUCCESS; } - else - { - *outputWidth = 0; - *outputHeight = 0; - return H264DEC_STATUS::BAD_STREAM; - } - - return H264DEC_STATUS::SUCCESS; + return H264DEC_STATUS::BAD_STREAM; } uint32 H264DECInitParam(uint32 workMemorySize, void* workMemory) @@ -762,26 +239,28 @@ namespace H264 return 0; } - std::unordered_map<uint32, H264AVCDecoder*> sDecoderSessions; + std::unordered_map<uint32, H264DecoderBackend*> sDecoderSessions; std::mutex sDecoderSessionsMutex; std::atomic_uint32_t sCurrentSessionHandle{ 1 }; - static H264AVCDecoder* _CreateDecoderSession(uint32& handleOut) + H264DecoderBackend* CreateAVCDecoder(); + + static H264DecoderBackend* _CreateDecoderSession(uint32& handleOut) { std::unique_lock _lock(sDecoderSessionsMutex); handleOut = sCurrentSessionHandle.fetch_add(1); - H264AVCDecoder* session = new H264AVCDecoder(); + H264DecoderBackend* session = CreateAVCDecoder(); sDecoderSessions.try_emplace(handleOut, session); return session; } - static H264AVCDecoder* _AcquireDecoderSession(uint32 handle) + static H264DecoderBackend* _AcquireDecoderSession(uint32 handle) { std::unique_lock _lock(sDecoderSessionsMutex); auto it = sDecoderSessions.find(handle); if (it == sDecoderSessions.end()) return nullptr; - H264AVCDecoder* session = it->second; + H264DecoderBackend* session = it->second; if (sDecoderSessions.size() >= 5) { cemuLog_log(LogType::Force, "H264: Warning - more than 5 active sessions"); @@ -790,7 +269,7 @@ namespace H264 return session; } - static void _ReleaseDecoderSession(H264AVCDecoder* session) + static void _ReleaseDecoderSession(H264DecoderBackend* session) { std::unique_lock _lock(sDecoderSessionsMutex); @@ -802,7 +281,7 @@ namespace H264 auto it = sDecoderSessions.find(handle); if (it == sDecoderSessions.end()) return; - H264AVCDecoder* session = it->second; + H264DecoderBackend* session = it->second; session->Destroy(); delete session; sDecoderSessions.erase(it); @@ -830,45 +309,44 @@ namespace H264 uint32 H264DECBegin(void* workMemory) { H264Context* ctx = (H264Context*)workMemory; - H264AVCDecoder* session = _AcquireDecoderSession(ctx->sessionHandle); + H264DecoderBackend* session = _AcquireDecoderSession(ctx->sessionHandle); if (!session) { cemuLog_log(LogType::Force, "H264DECBegin(): Invalid session"); return 0; } session->Init(ctx->Param.outputPerFrame == 0); + ctx->decoderState.numFramesInFlight = 0; _ReleaseDecoderSession(session); return 0; } - void H264DoFrameOutputCallback(H264Context* ctx, H264AVCDecoder::DecodeResult& decodeResult); - - void _async_H264DECEnd(coreinit::OSEvent* executeDoneEvent, H264AVCDecoder* session, H264Context* ctx, std::vector<H264AVCDecoder::DecodeResult>* decodeResultsOut) - { - *decodeResultsOut = session->Flush(); - coreinit::OSSignalEvent(executeDoneEvent); - } + void H264DoFrameOutputCallback(H264Context* ctx, H264DecoderBackend::DecodeResult& decodeResult); H264DEC_STATUS H264DECEnd(void* workMemory) { H264Context* ctx = (H264Context*)workMemory; - H264AVCDecoder* session = _AcquireDecoderSession(ctx->sessionHandle); + H264DecoderBackend* session = _AcquireDecoderSession(ctx->sessionHandle); if (!session) { cemuLog_log(LogType::Force, "H264DECEnd(): Invalid session"); return H264DEC_STATUS::SUCCESS; } - StackAllocator<coreinit::OSEvent> executeDoneEvent; - coreinit::OSInitEvent(&executeDoneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); - std::vector<H264AVCDecoder::DecodeResult> results; - auto asyncTask = std::async(std::launch::async, _async_H264DECEnd, executeDoneEvent.GetPointer(), session, ctx, &results); - coreinit::OSWaitEvent(&executeDoneEvent); - _ReleaseDecoderSession(session); - if (!results.empty()) + coreinit::OSEvent* flushEvt = &session->GetFlushEvent(); + coreinit::OSResetEvent(flushEvt); + session->QueueFlush(); + coreinit::OSWaitEvent(flushEvt); + while(true) { - for (auto& itr : results) - H264DoFrameOutputCallback(ctx, itr); + H264DecoderBackend::DecodeResult decodeResult; + if( !session->GetFrameOutputIfReady(decodeResult) ) + break; + // todo - output all frames in a single callback? + H264DoFrameOutputCallback(ctx, decodeResult); + ctx->decoderState.numFramesInFlight--; } + cemu_assert_debug(ctx->decoderState.numFramesInFlight == 0); // no frames should be in flight anymore. Exact behavior is not well understood but we may have to output dummy frames if necessary + _ReleaseDecoderSession(session); return H264DEC_STATUS::SUCCESS; } @@ -930,7 +408,6 @@ namespace H264 return 0; } - struct H264DECFrameOutput { /* +0x00 */ uint32be result; @@ -967,7 +444,7 @@ namespace H264 static_assert(sizeof(H264OutputCBStruct) == 12); - void H264DoFrameOutputCallback(H264Context* ctx, H264AVCDecoder::DecodeResult& decodeResult) + void H264DoFrameOutputCallback(H264Context* ctx, H264DecoderBackend::DecodeResult& decodeResult) { sint32 outputFrameCount = 1; @@ -984,14 +461,14 @@ namespace H264 frameOutput->imagePtr = (uint8*)decodeResult.imageOutput; frameOutput->result = 100; frameOutput->timestamp = decodeResult.timestamp; - frameOutput->frameWidth = decodeResult.decodeOutput.u4_pic_wd; - frameOutput->frameHeight = decodeResult.decodeOutput.u4_pic_ht; - frameOutput->bytesPerRow = (decodeResult.decodeOutput.u4_pic_wd + 0xFF) & ~0xFF; - frameOutput->cropEnable = decodeResult.decodeOutput.u1_frame_cropping_flag; - frameOutput->cropTop = decodeResult.decodeOutput.u1_frame_cropping_rect_top_ofst; - frameOutput->cropBottom = decodeResult.decodeOutput.u1_frame_cropping_rect_bottom_ofst; - frameOutput->cropLeft = decodeResult.decodeOutput.u1_frame_cropping_rect_left_ofst; - frameOutput->cropRight = decodeResult.decodeOutput.u1_frame_cropping_rect_right_ofst; + frameOutput->frameWidth = decodeResult.frameWidth; + frameOutput->frameHeight = decodeResult.frameHeight; + frameOutput->bytesPerRow = decodeResult.bytesPerRow; + frameOutput->cropEnable = decodeResult.cropEnable; + frameOutput->cropTop = decodeResult.cropTop; + frameOutput->cropBottom = decodeResult.cropBottom; + frameOutput->cropLeft = decodeResult.cropLeft; + frameOutput->cropRight = decodeResult.cropRight; StackAllocator<H264OutputCBStruct> stack_fptrOutputData; stack_fptrOutputData->frameCount = outputFrameCount; @@ -1006,29 +483,41 @@ namespace H264 } } - void _async_H264DECExecute(coreinit::OSEvent* executeDoneEvent, H264AVCDecoder* session, H264Context* ctx, void* imageOutput, H264AVCDecoder::DecodeResult* decodeResult) - { - session->Decode(ctx->BitStream.ptr.GetPtr(), ctx->BitStream.length, ctx->BitStream.timestamp, imageOutput, *decodeResult); - coreinit::OSSignalEvent(executeDoneEvent); - } - uint32 H264DECExecute(void* workMemory, void* imageOutput) { + BenchmarkTimer bt; + bt.Start(); H264Context* ctx = (H264Context*)workMemory; - H264AVCDecoder* session = _AcquireDecoderSession(ctx->sessionHandle); + H264DecoderBackend* session = _AcquireDecoderSession(ctx->sessionHandle); if (!session) { cemuLog_log(LogType::Force, "H264DECExecute(): Invalid session"); return 0; } - StackAllocator<coreinit::OSEvent> executeDoneEvent; - coreinit::OSInitEvent(&executeDoneEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_MANUAL); - H264AVCDecoder::DecodeResult decodeResult; - auto asyncTask = std::async(std::launch::async, _async_H264DECExecute, &executeDoneEvent, session, ctx, imageOutput , &decodeResult); - coreinit::OSWaitEvent(&executeDoneEvent); + // feed data to backend + session->QueueForDecode((uint8*)ctx->BitStream.ptr.GetPtr(), ctx->BitStream.length, ctx->BitStream.timestamp, imageOutput); + ctx->decoderState.numFramesInFlight++; + // H264DECExecute is synchronous and will return a frame after either every call (non-buffered) or after 6 calls (buffered) + // normally frame decoding happens only during H264DECExecute, but in order to hide the latency of our CPU decoder we will decode asynchronously in buffered mode + uint32 numFramesToBuffer = (ctx->Param.outputPerFrame == 0) ? 5 : 0; + if(ctx->decoderState.numFramesInFlight > numFramesToBuffer) + { + ctx->decoderState.numFramesInFlight--; + while(true) + { + coreinit::OSEvent& evt = session->GetFrameOutputEvent(); + coreinit::OSWaitEvent(&evt); + H264DecoderBackend::DecodeResult decodeResult; + if( !session->GetFrameOutputIfReady(decodeResult) ) + continue; + H264DoFrameOutputCallback(ctx, decodeResult); + break; + } + } _ReleaseDecoderSession(session); - if(decodeResult.frameReady) - H264DoFrameOutputCallback(ctx, decodeResult); + bt.Stop(); + double callTime = bt.GetElapsedMilliseconds(); + cemuLog_log(LogType::H264, "H264Bench | H264DECExecute took {}ms", callTime); return 0x80 | 100; } diff --git a/src/Cafe/OS/libs/h264_avc/H264DecBackendAVC.cpp b/src/Cafe/OS/libs/h264_avc/H264DecBackendAVC.cpp new file mode 100644 index 00000000..228f65a5 --- /dev/null +++ b/src/Cafe/OS/libs/h264_avc/H264DecBackendAVC.cpp @@ -0,0 +1,502 @@ +#include "H264DecInternal.h" +#include "util/highresolutiontimer/HighResolutionTimer.h" + +extern "C" +{ +#include "../dependencies/ih264d/common/ih264_typedefs.h" +#include "../dependencies/ih264d/decoder/ih264d.h" +}; + +namespace H264 +{ + bool H264_IsBotW(); + + class H264AVCDecoder : public H264DecoderBackend + { + static void* ivd_aligned_malloc(void* ctxt, WORD32 alignment, WORD32 size) + { +#ifdef _WIN32 + return _aligned_malloc(size, alignment); +#else + // alignment is atleast sizeof(void*) + alignment = std::max<WORD32>(alignment, sizeof(void*)); + + //smallest multiple of 2 at least as large as alignment + alignment--; + alignment |= alignment << 1; + alignment |= alignment >> 1; + alignment |= alignment >> 2; + alignment |= alignment >> 4; + alignment |= alignment >> 8; + alignment |= alignment >> 16; + alignment ^= (alignment >> 1); + + void* temp; + posix_memalign(&temp, (size_t)alignment, (size_t)size); + return temp; +#endif + } + + static void ivd_aligned_free(void* ctxt, void* buf) + { +#ifdef _WIN32 + _aligned_free(buf); +#else + free(buf); +#endif + } + + public: + H264AVCDecoder() + { + m_decoderThread = std::thread(&H264AVCDecoder::DecoderThread, this); + } + + ~H264AVCDecoder() + { + m_threadShouldExit = true; + m_decodeSem.increment(); + if (m_decoderThread.joinable()) + m_decoderThread.join(); + } + + void Init(bool isBufferedMode) + { + ih264d_create_ip_t s_create_ip{ 0 }; + ih264d_create_op_t s_create_op{ 0 }; + + s_create_ip.s_ivd_create_ip_t.u4_size = sizeof(ih264d_create_ip_t); + s_create_ip.s_ivd_create_ip_t.e_cmd = IVD_CMD_CREATE; + s_create_ip.s_ivd_create_ip_t.u4_share_disp_buf = 1; // shared display buffer mode -> We give the decoder a list of buffers that it will use (?) + + s_create_op.s_ivd_create_op_t.u4_size = sizeof(ih264d_create_op_t); + s_create_ip.s_ivd_create_ip_t.e_output_format = IV_YUV_420SP_UV; + s_create_ip.s_ivd_create_ip_t.pf_aligned_alloc = ivd_aligned_malloc; + s_create_ip.s_ivd_create_ip_t.pf_aligned_free = ivd_aligned_free; + s_create_ip.s_ivd_create_ip_t.pv_mem_ctxt = NULL; + + WORD32 status = ih264d_api_function(m_codecCtx, &s_create_ip, &s_create_op); + cemu_assert(!status); + + m_codecCtx = (iv_obj_t*)s_create_op.s_ivd_create_op_t.pv_handle; + m_codecCtx->pv_fxns = (void*)&ih264d_api_function; + m_codecCtx->u4_size = sizeof(iv_obj_t); + + SetDecoderCoreCount(1); + + m_isBufferedMode = isBufferedMode; + + UpdateParameters(false); + + m_numDecodedFrames = 0; + m_hasBufferSizeInfo = false; + } + + void Destroy() + { + if (!m_codecCtx) + return; + ih264d_delete_ip_t s_delete_ip{ 0 }; + ih264d_delete_op_t s_delete_op{ 0 }; + s_delete_ip.s_ivd_delete_ip_t.u4_size = sizeof(ih264d_delete_ip_t); + s_delete_ip.s_ivd_delete_ip_t.e_cmd = IVD_CMD_DELETE; + s_delete_op.s_ivd_delete_op_t.u4_size = sizeof(ih264d_delete_op_t); + WORD32 status = ih264d_api_function(m_codecCtx, &s_delete_ip, &s_delete_op); + cemu_assert_debug(!status); + m_codecCtx = nullptr; + } + + void PushDecodedFrame(ivd_video_decode_op_t& s_dec_op) + { + // copy image data outside of lock since its an expensive operation + CopyImageToResultBuffer((uint8*)s_dec_op.s_disp_frm_buf.pv_y_buf, (uint8*)s_dec_op.s_disp_frm_buf.pv_u_buf, (uint8*)m_decodedSliceArray[s_dec_op.u4_ts].result.imageOutput, s_dec_op); + + std::unique_lock _l(m_decodeQueueMtx); + cemu_assert(s_dec_op.u4_ts < m_decodedSliceArray.size()); + auto& result = m_decodedSliceArray[s_dec_op.u4_ts]; + cemu_assert_debug(result.isUsed); + cemu_assert_debug(s_dec_op.u4_output_present != 0); + + result.result.isDecoded = true; + result.result.hasFrame = s_dec_op.u4_output_present != 0; + result.result.frameWidth = s_dec_op.u4_pic_wd; + result.result.frameHeight = s_dec_op.u4_pic_ht; + result.result.bytesPerRow = (s_dec_op.u4_pic_wd + 0xFF) & ~0xFF; + result.result.cropEnable = s_dec_op.u1_frame_cropping_flag; + result.result.cropTop = s_dec_op.u1_frame_cropping_rect_top_ofst; + result.result.cropBottom = s_dec_op.u1_frame_cropping_rect_bottom_ofst; + result.result.cropLeft = s_dec_op.u1_frame_cropping_rect_left_ofst; + result.result.cropRight = s_dec_op.u1_frame_cropping_rect_right_ofst; + + m_displayQueue.push_back(s_dec_op.u4_ts); + + _l.unlock(); + coreinit::OSSignalEvent(m_displayQueueEvt); + } + + // called from async worker thread + void Decode(DecodedSlice& decodedSlice) + { + if (!m_hasBufferSizeInfo) + { + uint32 numByteConsumed = 0; + if (!DetermineBufferSizes(decodedSlice.dataToDecode.m_data, decodedSlice.dataToDecode.m_length, numByteConsumed)) + { + cemuLog_log(LogType::Force, "H264AVC: Unable to determine picture size. Ignoring decode input"); + std::unique_lock _l(m_decodeQueueMtx); + decodedSlice.result.isDecoded = true; + decodedSlice.result.hasFrame = false; + coreinit::OSSignalEvent(m_displayQueueEvt); + return; + } + decodedSlice.dataToDecode.m_length -= numByteConsumed; + decodedSlice.dataToDecode.m_data = (uint8*)decodedSlice.dataToDecode.m_data + numByteConsumed; + m_hasBufferSizeInfo = true; + } + + ivd_video_decode_ip_t s_dec_ip{ 0 }; + ivd_video_decode_op_t s_dec_op{ 0 }; + s_dec_ip.u4_size = sizeof(ivd_video_decode_ip_t); + s_dec_op.u4_size = sizeof(ivd_video_decode_op_t); + + s_dec_ip.e_cmd = IVD_CMD_VIDEO_DECODE; + + s_dec_ip.u4_ts = std::distance(m_decodedSliceArray.data(), &decodedSlice); + cemu_assert_debug(s_dec_ip.u4_ts < m_decodedSliceArray.size()); + + s_dec_ip.pv_stream_buffer = (uint8*)decodedSlice.dataToDecode.m_data; + s_dec_ip.u4_num_Bytes = decodedSlice.dataToDecode.m_length; + + s_dec_ip.s_out_buffer.u4_min_out_buf_size[0] = 0; + s_dec_ip.s_out_buffer.u4_min_out_buf_size[1] = 0; + s_dec_ip.s_out_buffer.u4_num_bufs = 0; + + BenchmarkTimer bt; + bt.Start(); + WORD32 status = ih264d_api_function(m_codecCtx, &s_dec_ip, &s_dec_op); + if (status != 0 && (s_dec_op.u4_error_code&0xFF) == IVD_RES_CHANGED) + { + // resolution change + ResetDecoder(); + m_hasBufferSizeInfo = false; + Decode(decodedSlice); + return; + } + else if (status != 0) + { + cemuLog_log(LogType::Force, "H264: Failed to decode frame (error 0x{:08x})", status); + decodedSlice.result.hasFrame = false; + cemu_assert_unimplemented(); + return; + } + + bt.Stop(); + double decodeTime = bt.GetElapsedMilliseconds(); + + cemu_assert(s_dec_op.u4_frame_decoded_flag); + cemu_assert_debug(s_dec_op.u4_num_bytes_consumed == decodedSlice.dataToDecode.m_length); + + cemu_assert_debug(m_isBufferedMode || s_dec_op.u4_output_present); // if buffered mode is disabled, then every input should output a frame (except for partial slices?) + + if (s_dec_op.u4_output_present) + { + cemu_assert(s_dec_op.e_output_format == IV_YUV_420SP_UV); + if (H264_IsBotW()) + { + if (s_dec_op.s_disp_frm_buf.u4_y_wd == 1920 && s_dec_op.s_disp_frm_buf.u4_y_ht == 1088) + s_dec_op.s_disp_frm_buf.u4_y_ht = 1080; + } + bt.Start(); + PushDecodedFrame(s_dec_op); + bt.Stop(); + double copyTime = bt.GetElapsedMilliseconds(); + // release buffer + sint32 bufferId = -1; + for (size_t i = 0; i < m_displayBuf.size(); i++) + { + if (s_dec_op.s_disp_frm_buf.pv_y_buf >= m_displayBuf[i].data() && s_dec_op.s_disp_frm_buf.pv_y_buf < (m_displayBuf[i].data() + m_displayBuf[i].size())) + { + bufferId = (sint32)i; + break; + } + } + cemu_assert_debug(bufferId == s_dec_op.u4_disp_buf_id); + cemu_assert(bufferId >= 0); + ivd_rel_display_frame_ip_t s_video_rel_disp_ip{ 0 }; + ivd_rel_display_frame_op_t s_video_rel_disp_op{ 0 }; + s_video_rel_disp_ip.e_cmd = IVD_CMD_REL_DISPLAY_FRAME; + s_video_rel_disp_ip.u4_size = sizeof(ivd_rel_display_frame_ip_t); + s_video_rel_disp_op.u4_size = sizeof(ivd_rel_display_frame_op_t); + s_video_rel_disp_ip.u4_disp_buf_id = bufferId; + status = ih264d_api_function(m_codecCtx, &s_video_rel_disp_ip, &s_video_rel_disp_op); + cemu_assert(!status); + + cemuLog_log(LogType::H264, "H264Bench | DecodeTime {}ms CopyTime {}ms", decodeTime, copyTime); + } + else + { + cemuLog_log(LogType::H264, "H264Bench | DecodeTime {}ms (no frame output)", decodeTime); + } + + if (s_dec_op.u4_frame_decoded_flag) + m_numDecodedFrames++; + // get VUI + //ih264d_ctl_get_vui_params_ip_t s_ctl_get_vui_params_ip; + //ih264d_ctl_get_vui_params_op_t s_ctl_get_vui_params_op; + + //s_ctl_get_vui_params_ip.e_cmd = IVD_CMD_VIDEO_CTL; + //s_ctl_get_vui_params_ip.e_sub_cmd = (IVD_CONTROL_API_COMMAND_TYPE_T)IH264D_CMD_CTL_GET_VUI_PARAMS; + //s_ctl_get_vui_params_ip.u4_size = sizeof(ih264d_ctl_get_vui_params_ip_t); + //s_ctl_get_vui_params_op.u4_size = sizeof(ih264d_ctl_get_vui_params_op_t); + + //status = ih264d_api_function(mCodecCtx, &s_ctl_get_vui_params_ip, &s_ctl_get_vui_params_op); + //cemu_assert(status == 0); + } + + void Flush() + { + // set flush mode + ivd_ctl_flush_ip_t s_video_flush_ip{ 0 }; + ivd_ctl_flush_op_t s_video_flush_op{ 0 }; + s_video_flush_ip.e_cmd = IVD_CMD_VIDEO_CTL; + s_video_flush_ip.e_sub_cmd = IVD_CMD_CTL_FLUSH; + s_video_flush_ip.u4_size = sizeof(ivd_ctl_flush_ip_t); + s_video_flush_op.u4_size = sizeof(ivd_ctl_flush_op_t); + WORD32 status = ih264d_api_function(m_codecCtx, &s_video_flush_ip, &s_video_flush_op); + if (status != 0) + cemuLog_log(LogType::Force, "H264Dec: Unexpected error during flush ({})", status); + // get all frames from the decoder + while (true) + { + ivd_video_decode_ip_t s_dec_ip{ 0 }; + ivd_video_decode_op_t s_dec_op{ 0 }; + s_dec_ip.u4_size = sizeof(ivd_video_decode_ip_t); + s_dec_op.u4_size = sizeof(ivd_video_decode_op_t); + s_dec_ip.e_cmd = IVD_CMD_VIDEO_DECODE; + s_dec_ip.pv_stream_buffer = NULL; + s_dec_ip.u4_num_Bytes = 0; + s_dec_ip.s_out_buffer.u4_min_out_buf_size[0] = 0; + s_dec_ip.s_out_buffer.u4_min_out_buf_size[1] = 0; + s_dec_ip.s_out_buffer.u4_num_bufs = 0; + status = ih264d_api_function(m_codecCtx, &s_dec_ip, &s_dec_op); + if (status != 0) + break; + cemu_assert_debug(s_dec_op.u4_output_present != 0); // should never be false? + if(s_dec_op.u4_output_present == 0) + continue; + if (H264_IsBotW()) + { + if (s_dec_op.s_disp_frm_buf.u4_y_wd == 1920 && s_dec_op.s_disp_frm_buf.u4_y_ht == 1088) + s_dec_op.s_disp_frm_buf.u4_y_ht = 1080; + } + PushDecodedFrame(s_dec_op); + } + } + + void CopyImageToResultBuffer(uint8* yIn, uint8* uvIn, uint8* bufOut, ivd_video_decode_op_t& decodeInfo) + { + uint32 imageWidth = decodeInfo.s_disp_frm_buf.u4_y_wd; + uint32 imageHeight = decodeInfo.s_disp_frm_buf.u4_y_ht; + + size_t inputStride = decodeInfo.s_disp_frm_buf.u4_y_strd; + size_t outputStride = (imageWidth + 0xFF) & ~0xFF; + + // copy Y + uint8* yOut = bufOut; + for (uint32 row = 0; row < imageHeight; row++) + { + memcpy(yOut, yIn, imageWidth); + yIn += inputStride; + yOut += outputStride; + } + + // copy UV + uint8* uvOut = bufOut + outputStride * imageHeight; + for (uint32 row = 0; row < imageHeight/2; row++) + { + memcpy(uvOut, uvIn, imageWidth); + uvIn += inputStride; + uvOut += outputStride; + } + } + private: + void SetDecoderCoreCount(uint32 coreCount) + { + ih264d_ctl_set_num_cores_ip_t s_set_cores_ip; + ih264d_ctl_set_num_cores_op_t s_set_cores_op; + s_set_cores_ip.e_cmd = IVD_CMD_VIDEO_CTL; + s_set_cores_ip.e_sub_cmd = (IVD_CONTROL_API_COMMAND_TYPE_T)IH264D_CMD_CTL_SET_NUM_CORES; + s_set_cores_ip.u4_num_cores = coreCount; // valid numbers are 1-4 + s_set_cores_ip.u4_size = sizeof(ih264d_ctl_set_num_cores_ip_t); + s_set_cores_op.u4_size = sizeof(ih264d_ctl_set_num_cores_op_t); + IV_API_CALL_STATUS_T status = ih264d_api_function(m_codecCtx, (void *)&s_set_cores_ip, (void *)&s_set_cores_op); + cemu_assert(status == IV_SUCCESS); + } + + bool DetermineBufferSizes(void* data, uint32 length, uint32& numByteConsumed) + { + numByteConsumed = 0; + UpdateParameters(true); + + ivd_video_decode_ip_t s_dec_ip{ 0 }; + ivd_video_decode_op_t s_dec_op{ 0 }; + s_dec_ip.u4_size = sizeof(ivd_video_decode_ip_t); + s_dec_op.u4_size = sizeof(ivd_video_decode_op_t); + + s_dec_ip.e_cmd = IVD_CMD_VIDEO_DECODE; + s_dec_ip.pv_stream_buffer = (uint8*)data; + s_dec_ip.u4_num_Bytes = length; + s_dec_ip.s_out_buffer.u4_num_bufs = 0; + WORD32 status = ih264d_api_function(m_codecCtx, &s_dec_ip, &s_dec_op); + if (status != 0) + { + cemuLog_log(LogType::Force, "H264: Unable to determine buffer sizes for stream"); + return false; + } + numByteConsumed = s_dec_op.u4_num_bytes_consumed; + cemu_assert(status == 0); + if (s_dec_op.u4_pic_wd == 0 || s_dec_op.u4_pic_ht == 0) + return false; + UpdateParameters(false); + ReinitBuffers(); + return true; + } + + void ReinitBuffers() + { + ivd_ctl_getbufinfo_ip_t s_ctl_ip{ 0 }; + ivd_ctl_getbufinfo_op_t s_ctl_op{ 0 }; + WORD32 outlen = 0; + + s_ctl_ip.e_cmd = IVD_CMD_VIDEO_CTL; + s_ctl_ip.e_sub_cmd = IVD_CMD_CTL_GETBUFINFO; + s_ctl_ip.u4_size = sizeof(ivd_ctl_getbufinfo_ip_t); + s_ctl_op.u4_size = sizeof(ivd_ctl_getbufinfo_op_t); + + WORD32 status = ih264d_api_function(m_codecCtx, &s_ctl_ip, &s_ctl_op); + cemu_assert(!status); + + // allocate + for (uint32 i = 0; i < s_ctl_op.u4_num_disp_bufs; i++) + { + m_displayBuf.emplace_back().resize(s_ctl_op.u4_min_out_buf_size[0] + s_ctl_op.u4_min_out_buf_size[1]); + } + // set + ivd_set_display_frame_ip_t s_set_display_frame_ip{ 0 }; // make sure to zero-initialize this. The codec seems to check the first 3 pointers/sizes per frame, regardless of the value of u4_num_bufs + ivd_set_display_frame_op_t s_set_display_frame_op{ 0 }; + + s_set_display_frame_ip.e_cmd = IVD_CMD_SET_DISPLAY_FRAME; + s_set_display_frame_ip.u4_size = sizeof(ivd_set_display_frame_ip_t); + s_set_display_frame_op.u4_size = sizeof(ivd_set_display_frame_op_t); + + cemu_assert_debug(s_ctl_op.u4_min_num_out_bufs == 2); + cemu_assert_debug(s_ctl_op.u4_min_out_buf_size[0] != 0 && s_ctl_op.u4_min_out_buf_size[1] != 0); + + s_set_display_frame_ip.num_disp_bufs = s_ctl_op.u4_num_disp_bufs; + + for (uint32 i = 0; i < s_ctl_op.u4_num_disp_bufs; i++) + { + s_set_display_frame_ip.s_disp_buffer[i].u4_num_bufs = 2; + s_set_display_frame_ip.s_disp_buffer[i].u4_min_out_buf_size[0] = s_ctl_op.u4_min_out_buf_size[0]; + s_set_display_frame_ip.s_disp_buffer[i].u4_min_out_buf_size[1] = s_ctl_op.u4_min_out_buf_size[1]; + s_set_display_frame_ip.s_disp_buffer[i].pu1_bufs[0] = m_displayBuf[i].data() + 0; + s_set_display_frame_ip.s_disp_buffer[i].pu1_bufs[1] = m_displayBuf[i].data() + s_ctl_op.u4_min_out_buf_size[0]; + } + + status = ih264d_api_function(m_codecCtx, &s_set_display_frame_ip, &s_set_display_frame_op); + cemu_assert(!status); + + + // mark all as released (available) + for (uint32 i = 0; i < s_ctl_op.u4_num_disp_bufs; i++) + { + ivd_rel_display_frame_ip_t s_video_rel_disp_ip{ 0 }; + ivd_rel_display_frame_op_t s_video_rel_disp_op{ 0 }; + + s_video_rel_disp_ip.e_cmd = IVD_CMD_REL_DISPLAY_FRAME; + s_video_rel_disp_ip.u4_size = sizeof(ivd_rel_display_frame_ip_t); + s_video_rel_disp_op.u4_size = sizeof(ivd_rel_display_frame_op_t); + s_video_rel_disp_ip.u4_disp_buf_id = i; + + status = ih264d_api_function(m_codecCtx, &s_video_rel_disp_ip, &s_video_rel_disp_op); + cemu_assert(!status); + } + } + + void ResetDecoder() + { + ivd_ctl_reset_ip_t s_ctl_ip; + ivd_ctl_reset_op_t s_ctl_op; + + s_ctl_ip.e_cmd = IVD_CMD_VIDEO_CTL; + s_ctl_ip.e_sub_cmd = IVD_CMD_CTL_RESET; + s_ctl_ip.u4_size = sizeof(ivd_ctl_reset_ip_t); + s_ctl_op.u4_size = sizeof(ivd_ctl_reset_op_t); + + WORD32 status = ih264d_api_function(m_codecCtx, (void*)&s_ctl_ip, (void*)&s_ctl_op); + cemu_assert_debug(status == 0); + } + + void UpdateParameters(bool headerDecodeOnly) + { + ih264d_ctl_set_config_ip_t s_h264d_ctl_ip{ 0 }; + ih264d_ctl_set_config_op_t s_h264d_ctl_op{ 0 }; + ivd_ctl_set_config_ip_t* ps_ctl_ip = &s_h264d_ctl_ip.s_ivd_ctl_set_config_ip_t; + ivd_ctl_set_config_op_t* ps_ctl_op = &s_h264d_ctl_op.s_ivd_ctl_set_config_op_t; + + ps_ctl_ip->u4_disp_wd = 0; + ps_ctl_ip->e_frm_skip_mode = IVD_SKIP_NONE; + ps_ctl_ip->e_frm_out_mode = m_isBufferedMode ? IVD_DISPLAY_FRAME_OUT : IVD_DECODE_FRAME_OUT; + ps_ctl_ip->e_vid_dec_mode = headerDecodeOnly ? IVD_DECODE_HEADER : IVD_DECODE_FRAME; + ps_ctl_ip->e_cmd = IVD_CMD_VIDEO_CTL; + ps_ctl_ip->e_sub_cmd = IVD_CMD_CTL_SETPARAMS; + ps_ctl_ip->u4_size = sizeof(ih264d_ctl_set_config_ip_t); + ps_ctl_op->u4_size = sizeof(ih264d_ctl_set_config_op_t); + + WORD32 status = ih264d_api_function(m_codecCtx, &s_h264d_ctl_ip, &s_h264d_ctl_op); + cemu_assert(status == 0); + } + + private: + void DecoderThread() + { + while(!m_threadShouldExit) + { + m_decodeSem.decrementWithWait(); + std::unique_lock _l(m_decodeQueueMtx); + if (m_decodeQueue.empty()) + continue; + uint32 decodeIndex = m_decodeQueue.front(); + m_decodeQueue.erase(m_decodeQueue.begin()); + _l.unlock(); + if(decodeIndex == CMD_FLUSH) + { + Flush(); + _l.lock(); + cemu_assert_debug(m_decodeQueue.empty()); // after flushing the queue should be empty since the sender is waiting for the flush to complete + _l.unlock(); + coreinit::OSSignalEvent(m_flushEvt); + } + else + { + auto& decodedSlice = m_decodedSliceArray[decodeIndex]; + Decode(decodedSlice); + } + } + } + + iv_obj_t* m_codecCtx{nullptr}; + bool m_hasBufferSizeInfo{ false }; + bool m_isBufferedMode{ false }; + uint32 m_numDecodedFrames{0}; + std::vector<std::vector<uint8>> m_displayBuf; + + std::thread m_decoderThread; + std::atomic_bool m_threadShouldExit{false}; + }; + + H264DecoderBackend* CreateAVCDecoder() + { + return new H264AVCDecoder(); + } +}; diff --git a/src/Cafe/OS/libs/h264_avc/H264DecInternal.h b/src/Cafe/OS/libs/h264_avc/H264DecInternal.h new file mode 100644 index 00000000..498cccfa --- /dev/null +++ b/src/Cafe/OS/libs/h264_avc/H264DecInternal.h @@ -0,0 +1,139 @@ +#pragma once + +#include "util/helpers/Semaphore.h" +#include "Cafe/OS/libs/coreinit/coreinit_Thread.h" +#include "Cafe/OS/libs/coreinit/coreinit_SysHeap.h" + +#include "Cafe/OS/libs/h264_avc/parser/H264Parser.h" + +namespace H264 +{ + class H264DecoderBackend + { + protected: + struct DataToDecode + { + uint8* m_data; + uint32 m_length; + std::vector<uint8> m_buffer; + }; + + static constexpr uint32 CMD_FLUSH = 0xFFFFFFFF; + + public: + struct DecodeResult + { + bool isDecoded{false}; + bool hasFrame{false}; // set to true if a full frame was successfully decoded + double timestamp{}; + void* imageOutput{nullptr}; + sint32 frameWidth{0}; + sint32 frameHeight{0}; + uint32 bytesPerRow{0}; + bool cropEnable{false}; + sint32 cropTop{0}; + sint32 cropBottom{0}; + sint32 cropLeft{0}; + sint32 cropRight{0}; + }; + + struct DecodedSlice + { + bool isUsed{false}; + DecodeResult result; + DataToDecode dataToDecode; + }; + + H264DecoderBackend() + { + m_displayQueueEvt = (coreinit::OSEvent*)coreinit::OSAllocFromSystem(sizeof(coreinit::OSEvent), 4); + coreinit::OSInitEvent(m_displayQueueEvt, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); + m_flushEvt = (coreinit::OSEvent*)coreinit::OSAllocFromSystem(sizeof(coreinit::OSEvent), 4); + coreinit::OSInitEvent(m_flushEvt, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); + }; + + virtual ~H264DecoderBackend() + { + coreinit::OSFreeToSystem(m_displayQueueEvt); + coreinit::OSFreeToSystem(m_flushEvt); + }; + + virtual void Init(bool isBufferedMode) = 0; + virtual void Destroy() = 0; + + void QueueForDecode(uint8* data, uint32 length, double timestamp, void* imagePtr) + { + std::unique_lock _l(m_decodeQueueMtx); + + DecodedSlice& ds = GetFreeDecodedSliceEntry(); + + ds.dataToDecode.m_buffer.assign(data, data + length); + ds.dataToDecode.m_data = ds.dataToDecode.m_buffer.data(); + ds.dataToDecode.m_length = length; + + ds.result.isDecoded = false; + ds.result.imageOutput = imagePtr; + ds.result.timestamp = timestamp; + + m_decodeQueue.push_back(std::distance(m_decodedSliceArray.data(), &ds)); + m_decodeSem.increment(); + } + + void QueueFlush() + { + std::unique_lock _l(m_decodeQueueMtx); + m_decodeQueue.push_back(CMD_FLUSH); + m_decodeSem.increment(); + } + + bool GetFrameOutputIfReady(DecodeResult& result) + { + std::unique_lock _l(m_decodeQueueMtx); + if(m_displayQueue.empty()) + return false; + uint32 sliceIndex = m_displayQueue.front(); + DecodedSlice& ds = m_decodedSliceArray[sliceIndex]; + cemu_assert_debug(ds.result.isDecoded); + std::swap(result, ds.result); + ds.isUsed = false; + m_displayQueue.erase(m_displayQueue.begin()); + return true; + } + + coreinit::OSEvent& GetFrameOutputEvent() + { + return *m_displayQueueEvt; + } + + coreinit::OSEvent& GetFlushEvent() + { + return *m_flushEvt; + } + + protected: + DecodedSlice& GetFreeDecodedSliceEntry() + { + for (auto& slice : m_decodedSliceArray) + { + if (!slice.isUsed) + { + slice.isUsed = true; + return slice; + } + } + cemu_assert_suspicious(); + return m_decodedSliceArray[0]; + } + + std::mutex m_decodeQueueMtx; + std::vector<uint32> m_decodeQueue; // indices into m_decodedSliceArray, in order of decode input + CounterSemaphore m_decodeSem; + std::vector<uint32> m_displayQueue; // indices into m_decodedSliceArray, in order of frame display output + coreinit::OSEvent* m_displayQueueEvt; // signalled when a new frame is ready for display + coreinit::OSEvent* m_flushEvt; // signalled after flush operation finished and all queued slices are decoded + + // frame output queue + std::mutex m_frameOutputMtx; + std::array<DecodedSlice, 32> m_decodedSliceArray; + }; +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/h264_avc/parser/H264Parser.cpp b/src/Cafe/OS/libs/h264_avc/parser/H264Parser.cpp index d77e551f..36f70f81 100644 --- a/src/Cafe/OS/libs/h264_avc/parser/H264Parser.cpp +++ b/src/Cafe/OS/libs/h264_avc/parser/H264Parser.cpp @@ -319,6 +319,17 @@ bool parseNAL_pic_parameter_set_rbsp(h264ParserState_t* h264ParserState, h264Par return true; } +bool h264Parser_ParseSPS(uint8* data, uint32 length, h264State_seq_parameter_set_t& sps) +{ + h264ParserState_t parserState; + RBSPInputBitstream nalStream(data, length); + bool r = parseNAL_seq_parameter_set_rbsp(&parserState, nullptr, nalStream); + if(!r || !parserState.hasSPS) + return false; + sps = parserState.sps; + return true; +} + void parseNAL_ref_pic_list_modification(const h264State_seq_parameter_set_t& sps, const h264State_pic_parameter_set_t& pps, RBSPInputBitstream& nalStream, nal_slice_header_t* sliceHeader) { if (!sliceHeader->slice_type.isSliceTypeI() && !sliceHeader->slice_type.isSliceTypeSI()) @@ -688,9 +699,8 @@ void _calculateFrameOrder(h264ParserState_t* h264ParserState, const h264State_se else if (sps.pic_order_cnt_type == 2) { // display order matches decode order - uint32 prevFrameNum = h264ParserState->picture_order.prevFrameNum; - ; + uint32 FrameNumOffset; if (sliceHeader->IdrPicFlag) { @@ -706,9 +716,6 @@ void _calculateFrameOrder(h264ParserState_t* h264ParserState, const h264State_se FrameNumOffset = prevFrameNumOffset + sps.getMaxFrameNum(); else FrameNumOffset = prevFrameNumOffset; - - - } uint32 tempPicOrderCnt; diff --git a/src/Cafe/OS/libs/h264_avc/parser/H264Parser.h b/src/Cafe/OS/libs/h264_avc/parser/H264Parser.h index ee32ca8b..6f2b3cf6 100644 --- a/src/Cafe/OS/libs/h264_avc/parser/H264Parser.h +++ b/src/Cafe/OS/libs/h264_avc/parser/H264Parser.h @@ -513,6 +513,8 @@ typedef struct void h264Parse(h264ParserState_t* h264ParserState, h264ParserOutput_t* output, uint8* data, uint32 length, bool parseSlices = true); sint32 h264GetUnitLength(h264ParserState_t* h264ParserState, uint8* data, uint32 length); +bool h264Parser_ParseSPS(uint8* data, uint32 length, h264State_seq_parameter_set_t& sps); + void h264Parser_getScalingMatrix4x4(h264State_seq_parameter_set_t* sps, h264State_pic_parameter_set_t* pps, nal_slice_header_t* sliceHeader, sint32 index, uint8* matrix4x4); void h264Parser_getScalingMatrix8x8(h264State_seq_parameter_set_t* sps, h264State_pic_parameter_set_t* pps, nal_slice_header_t* sliceHeader, sint32 index, uint8* matrix8x8); From 026d547dccd568a67bd42214728b173811694a1e Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Fri, 26 Jul 2024 01:45:34 +0200 Subject: [PATCH 147/299] Use HTTP 1.1 in Nintendo API requests --- src/Cemu/napi/napi_helper.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Cemu/napi/napi_helper.cpp b/src/Cemu/napi/napi_helper.cpp index 164de7e5..182c5371 100644 --- a/src/Cemu/napi/napi_helper.cpp +++ b/src/Cemu/napi/napi_helper.cpp @@ -107,6 +107,7 @@ CurlRequestHelper::CurlRequestHelper() curl_easy_setopt(m_curl, CURLOPT_FOLLOWLOCATION, 1); curl_easy_setopt(m_curl, CURLOPT_MAXREDIRS, 2); + curl_easy_setopt(m_curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); if(GetConfig().proxy_server.GetValue() != "") { @@ -263,6 +264,7 @@ CurlSOAPHelper::CurlSOAPHelper(NetworkService service) m_curl = curl_easy_init(); curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, __curlWriteCallback); curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, this); + curl_easy_setopt(m_curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); // SSL if (!IsNetworkServiceSSLDisabled(service)) From 252429933f8ae8dde9443ee5cc2c17b83b7b9dc7 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Fri, 26 Jul 2024 03:31:42 +0200 Subject: [PATCH 148/299] debugger: Slightly optimize symbol list updates --- src/gui/debugger/SymbolCtrl.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/gui/debugger/SymbolCtrl.cpp b/src/gui/debugger/SymbolCtrl.cpp index cb1f3b1a..aa862987 100644 --- a/src/gui/debugger/SymbolCtrl.cpp +++ b/src/gui/debugger/SymbolCtrl.cpp @@ -46,25 +46,25 @@ SymbolListCtrl::SymbolListCtrl(wxWindow* parent, const wxWindowID& id, const wxP 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; + wxString libNameWX = wxString::FromAscii((const char*)symbol_info->libName); + wxString symbolNameWX = wxString::FromAscii((const char*)symbol_info->symbolName); + wxString searchNameWX = libNameWX + symbolNameWX; + searchNameWX.MakeLower(); + auto new_entry = m_data.try_emplace( symbol_info->address, - (char*)(symbol_info->symbolName), - (char*)(symbol_info->libName), - "", + symbolNameWX, + libNameWX, + searchNameWX, 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.IsEmpty()) new_entry.first->second.visible = true; else if (new_entry.first->second.searchName.Contains(m_list_filter)) From 47f1dcf99691fbf0f8125f98f7df5ebf9eed221a Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Fri, 26 Jul 2024 05:08:38 +0200 Subject: [PATCH 149/299] debugger: Add symbol support to PPC stack traces Also moved the declaration to precompiled.h instead of redefining it wherever it is used --- src/Cafe/HW/Espresso/Debugger/Debugger.cpp | 2 -- src/Cafe/OS/libs/coreinit/coreinit.cpp | 14 +++++++++++--- src/Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.cpp | 2 -- src/Common/ExceptionHandler/ExceptionHandler.cpp | 4 +--- src/Common/precompiled.h | 3 +++ .../PPCThreadsViewer/DebugPPCThreadsWindow.cpp | 4 +--- 6 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/Cafe/HW/Espresso/Debugger/Debugger.cpp b/src/Cafe/HW/Espresso/Debugger/Debugger.cpp index 62a5d592..e7369af6 100644 --- a/src/Cafe/HW/Espresso/Debugger/Debugger.cpp +++ b/src/Cafe/HW/Espresso/Debugger/Debugger.cpp @@ -501,8 +501,6 @@ void debugger_createPPCStateSnapshot(PPCInterpreter_t* hCPU) debuggerState.debugSession.ppcSnapshot.cr[i] = hCPU->cr[i]; } -void DebugLogStackTrace(OSThread_t* thread, MPTR sp); - void debugger_enterTW(PPCInterpreter_t* hCPU) { // handle logging points diff --git a/src/Cafe/OS/libs/coreinit/coreinit.cpp b/src/Cafe/OS/libs/coreinit/coreinit.cpp index 49d232f8..00327a97 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit.cpp @@ -1,6 +1,6 @@ #include "Cafe/OS/common/OSCommon.h" #include "Common/SysAllocator.h" -#include "Cafe/OS/RPL/rpl.h" +#include "Cafe/OS/RPL/rpl_symbol_storage.h" #include "Cafe/OS/libs/coreinit/coreinit_Misc.h" @@ -69,7 +69,7 @@ sint32 ScoreStackTrace(OSThread_t* thread, MPTR sp) return score; } -void DebugLogStackTrace(OSThread_t* thread, MPTR sp) +void DebugLogStackTrace(OSThread_t* thread, MPTR sp, bool printSymbols) { // sp might not point to a valid stackframe // scan stack and evaluate which sp is most likely the beginning of the stackframe @@ -107,7 +107,15 @@ void DebugLogStackTrace(OSThread_t* thread, MPTR sp) uint32 returnAddress = 0; returnAddress = memory_readU32(nextStackPtr + 4); - cemuLog_log(LogType::Force, fmt::format("SP {0:08x} ReturnAddr {1:08x}", nextStackPtr, returnAddress)); + + RPLStoredSymbol* symbol = nullptr; + if(printSymbols) + symbol = rplSymbolStorage_getByClosestAddress(returnAddress); + + if(symbol) + cemuLog_log(LogType::Force, fmt::format("SP {:08x} ReturnAddr {:08x} ({}.{}+0x{:x})", nextStackPtr, returnAddress, (const char*)symbol->libName, (const char*)symbol->symbolName, returnAddress - symbol->address)); + else + cemuLog_log(LogType::Force, fmt::format("SP {:08x} ReturnAddr {:08x}", nextStackPtr, returnAddress)); currentStackPtr = nextStackPtr; } diff --git a/src/Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.cpp b/src/Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.cpp index 7ddadcf1..552a610f 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.cpp @@ -2,8 +2,6 @@ #include "Cafe/HW/Espresso/PPCCallback.h" #include "Cafe/OS/libs/coreinit/coreinit_MEM_ExpHeap.h" -void DebugLogStackTrace(OSThread_t* thread, MPTR sp); - #define EXP_HEAP_GET_FROM_FREE_BLOCKCHAIN(__blockchain__) (MEMExpHeapHead2*)((uintptr_t)__blockchain__ - offsetof(MEMExpHeapHead2, expHeapHead) - offsetof(MEMExpHeapHead40_t, chainFreeBlocks)) namespace coreinit diff --git a/src/Common/ExceptionHandler/ExceptionHandler.cpp b/src/Common/ExceptionHandler/ExceptionHandler.cpp index b6755fd8..7530a2eb 100644 --- a/src/Common/ExceptionHandler/ExceptionHandler.cpp +++ b/src/Common/ExceptionHandler/ExceptionHandler.cpp @@ -6,8 +6,6 @@ #include "Cafe/HW/Espresso/Debugger/GDBStub.h" #include "ExceptionHandler.h" -void DebugLogStackTrace(OSThread_t* thread, MPTR sp); - bool crashLogCreated = false; bool CrashLog_Create() @@ -97,7 +95,7 @@ void ExceptionHandler_LogGeneralInfo() MPTR currentStackVAddr = hCPU->gpr[1]; CrashLog_WriteLine(""); CrashLog_WriteHeader("PPC stack trace"); - DebugLogStackTrace(currentThread, currentStackVAddr); + DebugLogStackTrace(currentThread, currentStackVAddr, true); // stack dump CrashLog_WriteLine(""); diff --git a/src/Common/precompiled.h b/src/Common/precompiled.h index 790a001a..61707519 100644 --- a/src/Common/precompiled.h +++ b/src/Common/precompiled.h @@ -552,6 +552,9 @@ inline uint32 GetTitleIdLow(uint64 titleId) #include "Cafe/HW/Espresso/PPCState.h" #include "Cafe/HW/Espresso/PPCCallback.h" +// PPC stack trace printer +void DebugLogStackTrace(struct OSThread_t* thread, MPTR sp, bool printSymbols = false); + // generic formatter for enums (to underlying) template <typename Enum> requires std::is_enum_v<Enum> diff --git a/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp b/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp index dfbaf76e..f4e5b7af 100644 --- a/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp +++ b/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp @@ -277,12 +277,10 @@ void DebugPPCThreadsWindow::RefreshThreadList() m_thread_list->SetScrollPos(0, scrollPos, true); } -void DebugLogStackTrace(OSThread_t* thread, MPTR sp); - void DebugPPCThreadsWindow::DumpStackTrace(OSThread_t* thread) { cemuLog_log(LogType::Force, "Dumping stack trace for thread {0:08x} LR: {1:08x}", memory_getVirtualOffsetFromPointer(thread), _swapEndianU32(thread->context.lr)); - DebugLogStackTrace(thread, _swapEndianU32(thread->context.gpr[1])); + DebugLogStackTrace(thread, _swapEndianU32(thread->context.gpr[1]), true); } void DebugPPCThreadsWindow::PresentProfileResults(OSThread_t* thread, const std::unordered_map<VAddr, uint32>& samples) From 5328e9eb10b2abeaf303310b06b37269d79dde12 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Fri, 26 Jul 2024 05:13:45 +0200 Subject: [PATCH 150/299] CPU: Fix overflow bit calculation in SUBFO instruction Since rD can overlap with rA or rB the result needs to be stored in a temporary --- src/Cafe/HW/Espresso/Interpreter/PPCInterpreterALU.hpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterALU.hpp b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterALU.hpp index ed97288d..fe9316f0 100644 --- a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterALU.hpp +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterALU.hpp @@ -212,11 +212,12 @@ static void PPCInterpreter_SUBF(PPCInterpreter_t* hCPU, uint32 opcode) static void PPCInterpreter_SUBFO(PPCInterpreter_t* hCPU, uint32 opcode) { - // untested (Don't Starve Giant Edition uses this) + // Seen in Don't Starve Giant Edition and Teslagrad // also used by DS Virtual Console (Super Mario 64 DS) PPC_OPC_TEMPL3_XO(); - hCPU->gpr[rD] = ~hCPU->gpr[rA] + hCPU->gpr[rB] + 1; - PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(~hCPU->gpr[rA], hCPU->gpr[rB], hCPU->gpr[rD])); + uint32 result = ~hCPU->gpr[rA] + hCPU->gpr[rB] + 1; + PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(~hCPU->gpr[rA], hCPU->gpr[rB], result)); + hCPU->gpr[rD] = result; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); From c73fa3761c9572db4d09cdb976a0f1510cda548a Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 27 Jul 2024 04:45:36 +0200 Subject: [PATCH 151/299] Fix compatibility with GCC --- src/resource/embedded/fontawesome.S | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/resource/embedded/fontawesome.S b/src/resource/embedded/fontawesome.S index 29b4f93a..db23e7ae 100644 --- a/src/resource/embedded/fontawesome.S +++ b/src/resource/embedded/fontawesome.S @@ -1,4 +1,4 @@ -.rodata +.section .rodata,"",%progbits .global g_fontawesome_data, g_fontawesome_size g_fontawesome_data: @@ -6,3 +6,4 @@ g_fontawesome_data: g_fontawesome_size: .int g_fontawesome_size - g_fontawesome_data +.section .note.GNU-stack,"",%progbits \ No newline at end of file From 593da5ed79cab9ca175391d0ba6666a1f8a52500 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 27 Jul 2024 18:33:01 +0200 Subject: [PATCH 152/299] CI: Workaround for MoltenVK crash 1.2.10 and later crash during descriptor set creation. So for now let's stick with the older version --- .github/workflows/build.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a2342c27..28efa833 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -239,7 +239,17 @@ jobs: - name: "Install system dependencies" run: | brew update - brew install llvm@15 ninja nasm molten-vk automake libtool + brew install llvm@15 ninja nasm automake libtool + brew install cmake python3 ninja + + - name: "Build and install molten-vk" + run: | + git clone https://github.com/KhronosGroup/MoltenVK.git + cd MoltenVK + git checkout bf097edc74ec3b6dfafdcd5a38d3ce14b11952d6 + ./fetchDependencies --macos + make macos + make install - name: "Setup cmake" uses: jwlawson/actions-setup-cmake@v2 From 517e68fe57ac1ac112f37c321d3d40a30ea5a8d6 Mon Sep 17 00:00:00 2001 From: Joshua de Reeper <joshua@dereeper.co.nz> Date: Sun, 28 Jul 2024 17:50:20 +0100 Subject: [PATCH 153/299] nsyshid: Tidyups and Fixes (#1275) --- src/Cafe/OS/libs/nsyshid/Skylander.cpp | 2 +- src/Cafe/OS/libs/nsyshid/Skylander.h | 2 +- src/config/CemuConfig.h | 2 +- src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Cafe/OS/libs/nsyshid/Skylander.cpp b/src/Cafe/OS/libs/nsyshid/Skylander.cpp index a9888787..1b4515ef 100644 --- a/src/Cafe/OS/libs/nsyshid/Skylander.cpp +++ b/src/Cafe/OS/libs/nsyshid/Skylander.cpp @@ -978,7 +978,7 @@ namespace nsyshid { for (const auto& it : GetListSkylanders()) { - if(it.first.first == skyId && it.first.second == skyVar) + if (it.first.first == skyId && it.first.second == skyVar) { return it.second; } diff --git a/src/Cafe/OS/libs/nsyshid/Skylander.h b/src/Cafe/OS/libs/nsyshid/Skylander.h index 95eaff0c..986ef185 100644 --- a/src/Cafe/OS/libs/nsyshid/Skylander.h +++ b/src/Cafe/OS/libs/nsyshid/Skylander.h @@ -50,7 +50,7 @@ namespace nsyshid std::unique_ptr<FileStream> skyFile; uint8 status = 0; std::queue<uint8> queuedStatus; - std::array<uint8, SKY_BLOCK_SIZE> data{}; + std::array<uint8, SKY_FIGURE_SIZE> data{}; uint32 lastId = 0; void Save(); diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index 2a1d29cb..ac861c9a 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -519,7 +519,7 @@ struct CemuConfig struct { ConfigValue<bool> emulate_skylander_portal{false}; - ConfigValue<bool> emulate_infinity_base{true}; + ConfigValue<bool> emulate_infinity_base{false}; }emulated_usb_devices{}; private: diff --git a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp index f4784f35..3a0f534a 100644 --- a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp +++ b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp @@ -398,7 +398,7 @@ CreateInfinityFigureDialog::CreateInfinityFigureDialog(wxWindow* parent, uint8 s { wxMessageDialog idError(this, "Error Converting Figure Number!", "Number Entered is Invalid"); idError.ShowModal(); - this->EndModal(0);; + this->EndModal(0); } uint32 figNum = longFigNum & 0xFFFFFFFF; auto figure = nsyshid::g_infinitybase.FindFigure(figNum); @@ -408,7 +408,7 @@ CreateInfinityFigureDialog::CreateInfinityFigureDialog(wxWindow* parent, uint8 s "BIN files (*.bin)|*.bin", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (saveFileDialog.ShowModal() == wxID_CANCEL) - this->EndModal(0);; + this->EndModal(0); m_filePath = saveFileDialog.GetPath(); From 1575866eca8f84bbe94f2e3a5c2bc8938a5856bb Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 4 Aug 2024 14:45:57 +0200 Subject: [PATCH 154/299] Vulkan: Add R32_X8_FLOAT format --- src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 9209e3cd..b9922fc3 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -2439,6 +2439,11 @@ void VulkanRenderer::GetTextureFormatInfoVK(Latte::E_GX2SURFFMT format, bool isD // used by Color Splash and Resident Evil formatInfoOut->vkImageFormat = VK_FORMAT_R8G8B8A8_UINT; // todo - should we use ABGR format? formatInfoOut->decoder = TextureDecoder_X24_G8_UINT::getInstance(); // todo - verify + case Latte::E_GX2SURFFMT::R32_X8_FLOAT: + // seen in Disney Infinity 3.0 + formatInfoOut->vkImageFormat = VK_FORMAT_R32_SFLOAT; + formatInfoOut->decoder = TextureDecoder_NullData64::getInstance(); + break; default: cemuLog_log(LogType::Force, "Unsupported color texture format {:04x}", (uint32)format); cemu_assert_debug(false); From d81eb952a4c8273670c9a29fc3c7e69961282d41 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Tue, 6 Aug 2024 22:58:23 +0200 Subject: [PATCH 155/299] nsyshid: Silence some logging in release builds --- src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp index 44e01399..267111b2 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp +++ b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp @@ -67,13 +67,6 @@ namespace nsyshid::backend::windows device->m_productId); } } - else - { - cemuLog_log(LogType::Force, - "nsyshid::BackendWindowsHID: device not on whitelist: {:04x}:{:04x}", - device->m_vendorId, - device->m_productId); - } } CloseHandle(hHIDDevice); } @@ -125,14 +118,12 @@ namespace nsyshid::backend::windows } if (maxPacketInputLength <= 0 || maxPacketInputLength >= 0xF000) { - cemuLog_log(LogType::Force, "HID: Input packet length not available or out of range (length = {})", - maxPacketInputLength); + cemuLog_logDebug(LogType::Force, "HID: Input packet length not available or out of range (length = {})", maxPacketInputLength); maxPacketInputLength = 0x20; } if (maxPacketOutputLength <= 0 || maxPacketOutputLength >= 0xF000) { - cemuLog_log(LogType::Force, "HID: Output packet length not available or out of range (length = {})", - maxPacketOutputLength); + cemuLog_logDebug(LogType::Force, "HID: Output packet length not available or out of range (length = {})", maxPacketOutputLength); maxPacketOutputLength = 0x20; } From 21296447812794f8cde76db93cf3697a96da7ac9 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Tue, 6 Aug 2024 23:02:28 +0200 Subject: [PATCH 156/299] Remove shaderCache directory The location of the shaderCache path is different for non-portable cases so let's not confuse the user by shipping with a precreated directory that isn't actually used --- bin/shaderCache/info.txt | 1 - 1 file changed, 1 deletion(-) delete mode 100644 bin/shaderCache/info.txt diff --git a/bin/shaderCache/info.txt b/bin/shaderCache/info.txt deleted file mode 100644 index 962cf88b..00000000 --- a/bin/shaderCache/info.txt +++ /dev/null @@ -1 +0,0 @@ -If you plan to transfer the shader cache to a different PC or Cemu installation you only need to copy the 'transferable' directory. \ No newline at end of file From b52b676413a5566adc4a5c1f2472fa6cc961a94c Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 7 Aug 2024 02:50:24 +0200 Subject: [PATCH 157/299] vcpkg: Automatically unshallow submodule --- CMakeLists.txt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b5f3881..48e18637 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,24 @@ if (EXPERIMENTAL_VERSION) endif() if (ENABLE_VCPKG) + # check if vcpkg is shallow and unshallow it if necessary + execute_process( + COMMAND git rev-parse --is-shallow-repository + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/dependencies/vcpkg + OUTPUT_VARIABLE is_vcpkg_shallow + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + if(is_vcpkg_shallow STREQUAL "true") + message(STATUS "vcpkg is shallow. Unshallowing it now...") + execute_process( + COMMAND git fetch --unshallow + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/dependencies/vcpkg" + RESULT_VARIABLE result + OUTPUT_VARIABLE output + ) + endif() + if(UNIX AND NOT APPLE) set(VCPKG_OVERLAY_PORTS "${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports_linux") elseif(APPLE) From bf2208145b21505f5ebe3b1245e4c32bfc6f0d45 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 7 Aug 2024 16:18:40 +0200 Subject: [PATCH 158/299] Enable async shader compile by default --- src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp | 4 +++- src/config/CemuConfig.h | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index b9922fc3..09515993 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -7,6 +7,7 @@ #include "Cafe/HW/Latte/Core/LatteBufferCache.h" #include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" +#include "Cafe/HW/Latte/Core/LatteOverlay.h" #include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h" @@ -29,6 +30,7 @@ #include <glslang/Public/ShaderLang.h> #include <wx/msgdlg.h> +#include <wx/intl.h> // for localization #ifndef VK_API_VERSION_MAJOR #define VK_API_VERSION_MAJOR(version) (((uint32_t)(version) >> 22) & 0x7FU) @@ -285,7 +287,7 @@ void VulkanRenderer::GetDeviceFeatures() cemuLog_log(LogType::Force, "VK_EXT_pipeline_creation_cache_control not supported. Cannot use asynchronous shader and pipeline compilation"); // if async shader compilation is enabled show warning message if (GetConfig().async_compile) - wxMessageBox(_("The currently installed graphics driver does not support the Vulkan extension necessary for asynchronous shader compilation. Asynchronous compilation cannot be used.\n \nRequired extension: VK_EXT_pipeline_creation_cache_control\n\nInstalling the latest graphics driver may solve this error."), _("Information"), wxOK | wxCENTRE); + LatteOverlay_pushNotification(_("Async shader compile is enabled but not supported by the graphics driver\nCemu will use synchronous compilation which can cause additional stutter").utf8_string(), 10000); } if (!m_featureControl.deviceExtensions.custom_border_color_without_format) { diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index ac861c9a..5db8f58c 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -441,7 +441,7 @@ struct CemuConfig ConfigValue<int> vsync{ 0 }; // 0 = off, 1+ = on depending on render backend ConfigValue<bool> gx2drawdone_sync {true}; ConfigValue<bool> render_upside_down{ false }; - ConfigValue<bool> async_compile{ false }; + ConfigValue<bool> async_compile{ true }; ConfigValue<bool> vk_accurate_barriers{ true }; From 54e695a6e81efd4fb9a632c62abdb4585f8f776e Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Thu, 8 Aug 2024 15:58:24 +0200 Subject: [PATCH 159/299] git: unshallow vcpkg, shallow vulkan-headers and imgui (#1282) --- .gitmodules | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index f352d478..dc69c441 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,10 +9,12 @@ [submodule "dependencies/vcpkg"] path = dependencies/vcpkg url = https://github.com/microsoft/vcpkg - shallow = true + shallow = false [submodule "dependencies/Vulkan-Headers"] path = dependencies/Vulkan-Headers url = https://github.com/KhronosGroup/Vulkan-Headers + shallow = true [submodule "dependencies/imgui"] path = dependencies/imgui url = https://github.com/ocornut/imgui + shallow = true From 598298cb3d28fd608878f13ef1e76add75173692 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Thu, 8 Aug 2024 16:07:08 +0200 Subject: [PATCH 160/299] Vulkan: Fix stencil front mask --- src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp index ce582b9a..ba094a84 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp @@ -826,7 +826,7 @@ void PipelineCompiler::InitDepthStencilState() depthStencilState.front.reference = stencilRefFront; depthStencilState.front.compareMask = stencilCompareMaskFront; - depthStencilState.front.writeMask = stencilWriteMaskBack; + depthStencilState.front.writeMask = stencilWriteMaskFront; depthStencilState.front.compareOp = vkDepthCompareTable[(size_t)frontStencilFunc]; depthStencilState.front.depthFailOp = stencilOpTable[(size_t)frontStencilZFail]; depthStencilState.front.failOp = stencilOpTable[(size_t)frontStencilFail]; From 7fd532436d5af65af5a27a532d7ea5cb6ac5895c Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Thu, 8 Aug 2024 16:07:36 +0200 Subject: [PATCH 161/299] CI: Manual unshallow of vcpkg is no longer needed --- .github/workflows/build.yml | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 28efa833..015ef367 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,11 +24,6 @@ jobs: submodules: "recursive" fetch-depth: 0 - - name: "Fetch full history for vcpkg submodule" - run: | - cd dependencies/vcpkg - git fetch --unshallow - - name: Setup release mode parameters (for deploy) if: ${{ inputs.deploymode == 'release' }} run: | @@ -133,11 +128,6 @@ jobs: with: submodules: "recursive" - - name: "Fetch full history for vcpkg submodule" - run: | - cd dependencies/vcpkg - git fetch --unshallow - - name: Setup release mode parameters (for deploy) if: ${{ inputs.deploymode == 'release' }} run: | @@ -212,11 +202,6 @@ jobs: with: submodules: "recursive" - - name: "Fetch full history for vcpkg submodule" - run: | - cd dependencies/vcpkg - git fetch --unshallow - - name: Setup release mode parameters (for deploy) if: ${{ inputs.deploymode == 'release' }} run: | From 9812a47cb182331f7c7c7a6a16eff014098a6206 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Thu, 8 Aug 2024 19:35:50 +0200 Subject: [PATCH 162/299] clang-format: Put class braces on a new line (#1283) --- .clang-format | 1 + 1 file changed, 1 insertion(+) diff --git a/.clang-format b/.clang-format index b22a1048..0cef9ae4 100644 --- a/.clang-format +++ b/.clang-format @@ -15,6 +15,7 @@ BinPackArguments: true BinPackParameters: true BraceWrapping: AfterCaseLabel: true + AfterClass: true AfterControlStatement: Always AfterEnum: true AfterExternBlock: true From e02cc42d675ffe203c3f047f60669583934841ad Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Tue, 13 Aug 2024 01:00:49 +0200 Subject: [PATCH 163/299] COS: Implement PPC va_list, va_arg and update related functions --- src/Cafe/OS/common/OSCommon.h | 83 +++++++++++++++ src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp | 112 ++++++++++---------- src/Cafe/OS/libs/coreinit/coreinit_Misc.h | 14 +++ src/Common/MemPtr.h | 19 ++-- src/Common/betype.h | 25 +++++ 5 files changed, 182 insertions(+), 71 deletions(-) diff --git a/src/Cafe/OS/common/OSCommon.h b/src/Cafe/OS/common/OSCommon.h index 4fb65a47..34f207bb 100644 --- a/src/Cafe/OS/common/OSCommon.h +++ b/src/Cafe/OS/common/OSCommon.h @@ -23,3 +23,86 @@ void osLib_returnFromFunction64(PPCInterpreter_t* hCPU, uint64 returnValue64); // utility functions #include "Cafe/OS/common/OSUtil.h" + +// va_list +struct ppc_va_list +{ + uint8be gprIndex; + uint8be fprIndex; + uint8be _padding2[2]; + MEMPTR<uint8be> overflow_arg_area; + MEMPTR<uint8be> reg_save_area; +}; +static_assert(sizeof(ppc_va_list) == 0xC); + +struct ppc_va_list_reg_storage +{ + uint32be gpr_save_area[8]; // 32 bytes, r3 to r10 + float64be fpr_save_area[8]; // 64 bytes, f1 to f8 + ppc_va_list vargs; + uint32be padding; +}; +static_assert(sizeof(ppc_va_list_reg_storage) == 0x70); + +// Equivalent of va_start for PPC HLE functions. Must be called before any StackAllocator<> definitions +#define ppc_define_va_list(__gprIndex, __fprIndex) \ + MPTR vaOriginalR1 = PPCInterpreter_getCurrentInstance()->gpr[1]; \ + StackAllocator<ppc_va_list_reg_storage> va_list_storage; \ + for(int i=3; i<=10; i++) va_list_storage->gpr_save_area[i-3] = PPCInterpreter_getCurrentInstance()->gpr[i]; \ + for(int i=1; i<=8; i++) va_list_storage->fpr_save_area[i-1] = PPCInterpreter_getCurrentInstance()->fpr[i].fp0; \ + va_list_storage->vargs.gprIndex = __gprIndex; \ + va_list_storage->vargs.fprIndex = __fprIndex; \ + va_list_storage->vargs.reg_save_area = (uint8be*)&va_list_storage; \ + va_list_storage->vargs.overflow_arg_area = {vaOriginalR1 + 8}; \ + ppc_va_list& vargs = va_list_storage->vargs; + +enum class ppc_va_type +{ + INT32 = 1, + INT64 = 2, + FLOAT_OR_DOUBLE = 3, +}; + +static void* _ppc_va_arg(ppc_va_list* vargs, ppc_va_type argType) +{ + void* r; + switch ( argType ) + { + default: + cemu_assert_suspicious(); + case ppc_va_type::INT32: + if ( vargs[0].gprIndex < 8u ) + { + r = &vargs->reg_save_area[4 * vargs->gprIndex]; + vargs->gprIndex++; + return r; + } + r = vargs->overflow_arg_area; + vargs->overflow_arg_area += 4; + return r; + case ppc_va_type::INT64: + if ( (vargs->gprIndex & 1) != 0 ) + vargs->gprIndex++; + if ( vargs->gprIndex < 8 ) + { + r = &vargs->reg_save_area[4 * vargs->gprIndex]; + vargs->gprIndex += 2; + return r; + } + vargs->overflow_arg_area = {(vargs->overflow_arg_area.GetMPTR()+7) & 0xFFFFFFF8}; + r = vargs->overflow_arg_area; + vargs->overflow_arg_area += 8; + return r; + case ppc_va_type::FLOAT_OR_DOUBLE: + if ( vargs->fprIndex < 8 ) + { + r = &vargs->reg_save_area[0x20 + 8 * vargs->fprIndex]; + vargs->fprIndex++; + return r; + } + vargs->overflow_arg_area = {(vargs->overflow_arg_area.GetMPTR()+7) & 0xFFFFFFF8}; + r = vargs->overflow_arg_area; + vargs->overflow_arg_area += 8; + return r; + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp index e2b50661..71a7d6e2 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Misc.cpp @@ -7,14 +7,9 @@ namespace coreinit { - - /* coreinit logging and string format */ - - sint32 ppcSprintf(const char* formatStr, char* strOut, sint32 maxLength, PPCInterpreter_t* hCPU, sint32 initialParamIndex) + sint32 ppc_vprintf(const char* formatStr, char* strOut, sint32 maxLength, ppc_va_list* vargs) { char tempStr[4096]; - sint32 integerParamIndex = initialParamIndex; - sint32 floatParamIndex = 0; sint32 writeIndex = 0; while (*formatStr) { @@ -101,8 +96,7 @@ namespace coreinit tempFormat[(formatStr - formatStart)] = '\0'; else tempFormat[sizeof(tempFormat) - 1] = '\0'; - sint32 tempLen = sprintf(tempStr, tempFormat, PPCInterpreter_getCallParamU32(hCPU, integerParamIndex)); - integerParamIndex++; + sint32 tempLen = sprintf(tempStr, tempFormat, (uint32)*(uint32be*)_ppc_va_arg(vargs, ppc_va_type::INT32)); for (sint32 i = 0; i < tempLen; i++) { if (writeIndex >= maxLength) @@ -120,13 +114,12 @@ namespace coreinit tempFormat[(formatStr - formatStart)] = '\0'; else tempFormat[sizeof(tempFormat) - 1] = '\0'; - MPTR strOffset = PPCInterpreter_getCallParamU32(hCPU, integerParamIndex); + MPTR strOffset = *(uint32be*)_ppc_va_arg(vargs, ppc_va_type::INT32); sint32 tempLen = 0; if (strOffset == MPTR_NULL) tempLen = sprintf(tempStr, "NULL"); else tempLen = sprintf(tempStr, tempFormat, memory_getPointerFromVirtualOffset(strOffset)); - integerParamIndex++; for (sint32 i = 0; i < tempLen; i++) { if (writeIndex >= maxLength) @@ -136,25 +129,6 @@ namespace coreinit } strOut[std::min(maxLength - 1, writeIndex)] = '\0'; } - else if (*formatStr == 'f') - { - // float - formatStr++; - strncpy(tempFormat, formatStart, std::min((std::ptrdiff_t)sizeof(tempFormat) - 1, formatStr - formatStart)); - if ((formatStr - formatStart) < sizeof(tempFormat)) - tempFormat[(formatStr - formatStart)] = '\0'; - else - tempFormat[sizeof(tempFormat) - 1] = '\0'; - sint32 tempLen = sprintf(tempStr, tempFormat, (float)hCPU->fpr[1 + floatParamIndex].fp0); - floatParamIndex++; - for (sint32 i = 0; i < tempLen; i++) - { - if (writeIndex >= maxLength) - break; - strOut[writeIndex] = tempStr[i]; - writeIndex++; - } - } else if (*formatStr == 'c') { // character @@ -164,8 +138,24 @@ namespace coreinit tempFormat[(formatStr - formatStart)] = '\0'; else tempFormat[sizeof(tempFormat) - 1] = '\0'; - sint32 tempLen = sprintf(tempStr, tempFormat, PPCInterpreter_getCallParamU32(hCPU, integerParamIndex)); - integerParamIndex++; + sint32 tempLen = sprintf(tempStr, tempFormat, (uint32)*(uint32be*)_ppc_va_arg(vargs, ppc_va_type::INT32)); + for (sint32 i = 0; i < tempLen; i++) + { + if (writeIndex >= maxLength) + break; + strOut[writeIndex] = tempStr[i]; + writeIndex++; + } + } + else if (*formatStr == 'f' || *formatStr == 'g' || *formatStr == 'G') + { + formatStr++; + strncpy(tempFormat, formatStart, std::min((std::ptrdiff_t)sizeof(tempFormat) - 1, formatStr - formatStart)); + if ((formatStr - formatStart) < sizeof(tempFormat)) + tempFormat[(formatStr - formatStart)] = '\0'; + else + tempFormat[sizeof(tempFormat) - 1] = '\0'; + sint32 tempLen = sprintf(tempStr, tempFormat, (double)*(betype<double>*)_ppc_va_arg(vargs, ppc_va_type::FLOAT_OR_DOUBLE)); for (sint32 i = 0; i < tempLen; i++) { if (writeIndex >= maxLength) @@ -183,8 +173,7 @@ namespace coreinit tempFormat[(formatStr - formatStart)] = '\0'; else tempFormat[sizeof(tempFormat) - 1] = '\0'; - sint32 tempLen = sprintf(tempStr, tempFormat, (double)hCPU->fpr[1 + floatParamIndex].fp0); - floatParamIndex++; + sint32 tempLen = sprintf(tempStr, tempFormat, (double)*(betype<double>*)_ppc_va_arg(vargs, ppc_va_type::FLOAT_OR_DOUBLE)); for (sint32 i = 0; i < tempLen; i++) { if (writeIndex >= maxLength) @@ -196,16 +185,13 @@ namespace coreinit else if ((formatStr[0] == 'l' && formatStr[1] == 'l' && (formatStr[2] == 'x' || formatStr[2] == 'X'))) { formatStr += 3; - // double (64bit) + // 64bit int strncpy(tempFormat, formatStart, std::min((std::ptrdiff_t)sizeof(tempFormat) - 1, formatStr - formatStart)); if ((formatStr - formatStart) < sizeof(tempFormat)) tempFormat[(formatStr - formatStart)] = '\0'; else tempFormat[sizeof(tempFormat) - 1] = '\0'; - if (integerParamIndex & 1) - integerParamIndex++; - sint32 tempLen = sprintf(tempStr, tempFormat, PPCInterpreter_getCallParamU64(hCPU, integerParamIndex)); - integerParamIndex += 2; + sint32 tempLen = sprintf(tempStr, tempFormat, (uint64)*(uint64be*)_ppc_va_arg(vargs, ppc_va_type::INT64)); for (sint32 i = 0; i < tempLen; i++) { if (writeIndex >= maxLength) @@ -223,10 +209,7 @@ namespace coreinit tempFormat[(formatStr - formatStart)] = '\0'; else tempFormat[sizeof(tempFormat) - 1] = '\0'; - if (integerParamIndex & 1) - integerParamIndex++; - sint32 tempLen = sprintf(tempStr, tempFormat, PPCInterpreter_getCallParamU64(hCPU, integerParamIndex)); - integerParamIndex += 2; + sint32 tempLen = sprintf(tempStr, tempFormat, (sint64)*(sint64be*)_ppc_va_arg(vargs, ppc_va_type::INT64)); for (sint32 i = 0; i < tempLen; i++) { if (writeIndex >= maxLength) @@ -255,9 +238,12 @@ namespace coreinit return std::min(writeIndex, maxLength - 1); } + /* coreinit logging and string format */ + sint32 __os_snprintf(char* outputStr, sint32 maxLength, const char* formatStr) { - sint32 r = ppcSprintf(formatStr, outputStr, maxLength, PPCInterpreter_getCurrentInstance(), 3); + ppc_define_va_list(3, 0); + sint32 r = ppc_vprintf(formatStr, outputStr, maxLength, &vargs); return r; } @@ -322,32 +308,40 @@ namespace coreinit } } - void OSReport(const char* format) + void COSVReport(COSReportModule module, COSReportLevel level, const char* format, ppc_va_list* vargs) { - char buffer[1024 * 2]; - sint32 len = ppcSprintf(format, buffer, sizeof(buffer), PPCInterpreter_getCurrentInstance(), 1); - WriteCafeConsole(CafeLogType::OSCONSOLE, buffer, len); + char tmpBuffer[1024]; + sint32 len = ppc_vprintf(format, tmpBuffer, sizeof(tmpBuffer), vargs); + WriteCafeConsole(CafeLogType::OSCONSOLE, tmpBuffer, len); } - void OSVReport(const char* format, MPTR vaArgs) + void OSReport(const char* format) { - cemu_assert_unimplemented(); + ppc_define_va_list(1, 0); + COSVReport(COSReportModule::coreinit, COSReportLevel::Info, format, &vargs); + } + + void OSVReport(const char* format, ppc_va_list* vargs) + { + COSVReport(COSReportModule::coreinit, COSReportLevel::Info, format, vargs); } void COSWarn(int moduleId, const char* format) { - char buffer[1024 * 2]; - int prefixLen = sprintf(buffer, "[COSWarn-%d] ", moduleId); - sint32 len = ppcSprintf(format, buffer + prefixLen, sizeof(buffer) - prefixLen, PPCInterpreter_getCurrentInstance(), 2); - WriteCafeConsole(CafeLogType::OSCONSOLE, buffer, len + prefixLen); + ppc_define_va_list(2, 0); + char tmpBuffer[1024]; + int prefixLen = sprintf(tmpBuffer, "[COSWarn-%d] ", moduleId); + sint32 len = ppc_vprintf(format, tmpBuffer + prefixLen, sizeof(tmpBuffer) - prefixLen, &vargs); + WriteCafeConsole(CafeLogType::OSCONSOLE, tmpBuffer, len + prefixLen); } void OSLogPrintf(int ukn1, int ukn2, int ukn3, const char* format) { - char buffer[1024 * 2]; - int prefixLen = sprintf(buffer, "[OSLogPrintf-%d-%d-%d] ", ukn1, ukn2, ukn3); - sint32 len = ppcSprintf(format, buffer + prefixLen, sizeof(buffer) - prefixLen, PPCInterpreter_getCurrentInstance(), 4); - WriteCafeConsole(CafeLogType::OSCONSOLE, buffer, len + prefixLen); + ppc_define_va_list(4, 0); + char tmpBuffer[1024]; + int prefixLen = sprintf(tmpBuffer, "[OSLogPrintf-%d-%d-%d] ", ukn1, ukn2, ukn3); + sint32 len = ppc_vprintf(format, tmpBuffer + prefixLen, sizeof(tmpBuffer) - prefixLen, &vargs); + WriteCafeConsole(CafeLogType::OSCONSOLE, tmpBuffer, len + prefixLen); } void OSConsoleWrite(const char* strPtr, sint32 length) @@ -562,9 +556,11 @@ namespace coreinit s_transitionToForeground = false; cafeExportRegister("coreinit", __os_snprintf, LogType::Placeholder); + + cafeExportRegister("coreinit", COSVReport, LogType::Placeholder); + cafeExportRegister("coreinit", COSWarn, LogType::Placeholder); cafeExportRegister("coreinit", OSReport, LogType::Placeholder); cafeExportRegister("coreinit", OSVReport, LogType::Placeholder); - cafeExportRegister("coreinit", COSWarn, LogType::Placeholder); cafeExportRegister("coreinit", OSLogPrintf, LogType::Placeholder); cafeExportRegister("coreinit", OSConsoleWrite, LogType::Placeholder); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Misc.h b/src/Cafe/OS/libs/coreinit/coreinit_Misc.h index 7abba92f..36f6b06a 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Misc.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_Misc.h @@ -26,5 +26,19 @@ namespace coreinit uint32 OSDriver_Register(uint32 moduleHandle, sint32 priority, OSDriverInterface* driverCallbacks, sint32 driverId, uint32be* outUkn1, uint32be* outUkn2, uint32be* outUkn3); uint32 OSDriver_Deregister(uint32 moduleHandle, sint32 driverId); + enum class COSReportModule + { + coreinit = 0, + }; + + enum class COSReportLevel + { + Error = 0, + Warn = 1, + Info = 2 + }; + + sint32 ppc_vprintf(const char* formatStr, char* strOut, sint32 maxLength, ppc_va_list* vargs); + void miscInit(); }; \ No newline at end of file diff --git a/src/Common/MemPtr.h b/src/Common/MemPtr.h index 5fb73479..142da7e4 100644 --- a/src/Common/MemPtr.h +++ b/src/Common/MemPtr.h @@ -92,19 +92,6 @@ public: template <typename X> explicit operator MEMPTR<X>() const { return MEMPTR<X>(this->m_value); } - //bool operator==(const MEMPTR<T>& v) const { return m_value == v.m_value; } - //bool operator==(const T* rhs) const { return (T*)(m_value == 0 ? nullptr : memory_base + (uint32)m_value) == rhs; } -> ambigious (implicit cast to T* allows for T* == T*) - //bool operator==(std::nullptr_t rhs) const { return m_value == 0; } - - //bool operator!=(const MEMPTR<T>& v) const { return !(*this == v); } - //bool operator!=(const void* rhs) const { return !(*this == rhs); } - //bool operator!=(int rhs) const { return !(*this == rhs); } - - //bool operator==(const void* rhs) const { return (void*)(m_value == 0 ? nullptr : memory_base + (uint32)m_value) == rhs; } - - //explicit bool operator==(int rhs) const { return *this == (const void*)(size_t)rhs; } - - MEMPTR operator+(const MEMPTR& ptr) { return MEMPTR(this->GetMPTR() + ptr.GetMPTR()); } MEMPTR operator-(const MEMPTR& ptr) { return MEMPTR(this->GetMPTR() - ptr.GetMPTR()); } @@ -120,6 +107,12 @@ public: return MEMPTR(this->GetMPTR() - v * 4); } + MEMPTR& operator+=(sint32 v) + { + m_value += v * sizeof(T); + return *this; + } + template <class Q = T> typename std::enable_if<!std::is_same<Q, void>::value, Q>::type& operator*() const { return *GetPtr(); } diff --git a/src/Common/betype.h b/src/Common/betype.h index e684fb93..60a64b7a 100644 --- a/src/Common/betype.h +++ b/src/Common/betype.h @@ -121,6 +121,12 @@ public: return *this; } + betype<T>& operator+=(const T& v) requires std::integral<T> + { + m_value = SwapEndian(T(value() + v)); + return *this; + } + betype<T>& operator-=(const betype<T>& v) { m_value = SwapEndian(T(value() - v.value())); @@ -188,17 +194,36 @@ public: return from_bevalue(T(~m_value)); } + // pre-increment betype<T>& operator++() requires std::integral<T> { m_value = SwapEndian(T(value() + 1)); return *this; } + // post-increment + betype<T> operator++(int) requires std::integral<T> + { + betype<T> tmp(*this); + m_value = SwapEndian(T(value() + 1)); + return tmp; + } + + // pre-decrement betype<T>& operator--() requires std::integral<T> { m_value = SwapEndian(T(value() - 1)); return *this; } + + // post-decrement + betype<T> operator--(int) requires std::integral<T> + { + betype<T> tmp(*this); + m_value = SwapEndian(T(value() - 1)); + return tmp; + } + private: //T m_value{}; // before 1.26.2 T m_value; From f52970c822b7f671f6f9a80e828bf53152feb783 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Tue, 13 Aug 2024 04:47:43 +0200 Subject: [PATCH 164/299] Vulkan: Allow RGBA16F texture format with SRGB bit --- src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 09515993..81b0b0f1 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -2212,6 +2212,7 @@ void VulkanRenderer::GetTextureFormatInfoVK(Latte::E_GX2SURFFMT format, bool isD formatInfoOut->decoder = TextureDecoder_R32_G32_B32_A32_UINT::getInstance(); break; case Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT: + case Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT | Latte::E_GX2SURFFMT::FMT_BIT_SRGB: // Seen in Sonic Transformed level Starry Speedway. SRGB should just be ignored for native float formats? formatInfoOut->vkImageFormat = VK_FORMAT_R16G16B16A16_SFLOAT; formatInfoOut->decoder = TextureDecoder_R16_G16_B16_A16_FLOAT::getInstance(); break; From e551f8f5245f9e94f677094d56e29b11870cd3f4 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Tue, 13 Aug 2024 05:57:51 +0200 Subject: [PATCH 165/299] Fix clang compile error --- src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 81b0b0f1..fb54a803 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -2200,6 +2200,8 @@ void VulkanRenderer::GetTextureFormatInfoVK(Latte::E_GX2SURFFMT format, bool isD else { formatInfoOut->vkImageAspect = VK_IMAGE_ASPECT_COLOR_BIT; + if(format == (Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT | Latte::E_GX2SURFFMT::FMT_BIT_SRGB)) // Seen in Sonic Transformed level Starry Speedway. SRGB should just be ignored for native float formats? + format = Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT; switch (format) { // RGBA formats @@ -2212,7 +2214,6 @@ void VulkanRenderer::GetTextureFormatInfoVK(Latte::E_GX2SURFFMT format, bool isD formatInfoOut->decoder = TextureDecoder_R32_G32_B32_A32_UINT::getInstance(); break; case Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT: - case Latte::E_GX2SURFFMT::R16_G16_B16_A16_FLOAT | Latte::E_GX2SURFFMT::FMT_BIT_SRGB: // Seen in Sonic Transformed level Starry Speedway. SRGB should just be ignored for native float formats? formatInfoOut->vkImageFormat = VK_FORMAT_R16G16B16A16_SFLOAT; formatInfoOut->decoder = TextureDecoder_R16_G16_B16_A16_FLOAT::getInstance(); break; From a6d8c0fb9f139817b90d82775e36a4f3d1a4ce76 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Tue, 13 Aug 2024 15:48:13 +0200 Subject: [PATCH 166/299] CI: Fix macOS build (#1291) --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 015ef367..9fb775e2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -225,7 +225,7 @@ jobs: run: | brew update brew install llvm@15 ninja nasm automake libtool - brew install cmake python3 ninja + brew install cmake ninja - name: "Build and install molten-vk" run: | From c49296acdc4acf3998249d8f67e8cbc984b9e276 Mon Sep 17 00:00:00 2001 From: "Skyth (Asilkan)" <19259897+blueskythlikesclouds@users.noreply.github.com> Date: Tue, 13 Aug 2024 16:53:04 +0300 Subject: [PATCH 167/299] Add support for iterating directories in graphics pack content folders. (#1288) --- src/Cafe/Filesystem/FST/fstUtil.h | 65 +++++++++++++++++++++-- src/Cafe/Filesystem/fsc.h | 2 +- src/Cafe/Filesystem/fscDeviceRedirect.cpp | 13 +++-- src/Cafe/GraphicPack/GraphicPack2.cpp | 2 +- 4 files changed, 73 insertions(+), 9 deletions(-) diff --git a/src/Cafe/Filesystem/FST/fstUtil.h b/src/Cafe/Filesystem/FST/fstUtil.h index 01283684..a432cc95 100644 --- a/src/Cafe/Filesystem/FST/fstUtil.h +++ b/src/Cafe/Filesystem/FST/fstUtil.h @@ -3,6 +3,8 @@ #include <boost/container/small_vector.hpp> +#include "../fsc.h" + // path parser and utility class for Wii U paths // optimized to be allocation-free for common path lengths class FSCPath @@ -119,9 +121,7 @@ public: template<typename F> class FSAFileTree { -public: - -private: + private: enum NODETYPE : uint8 { @@ -133,6 +133,7 @@ private: { std::string name; std::vector<node_t*> subnodes; + size_t fileSize; F* custom; NODETYPE type; }; @@ -179,13 +180,54 @@ private: return newNode; } + class DirectoryIterator : public FSCVirtualFile + { + public: + DirectoryIterator(node_t* node) + : m_node(node), m_subnodeIndex(0) + { + } + + sint32 fscGetType() override + { + return FSC_TYPE_DIRECTORY; + } + + bool fscDirNext(FSCDirEntry* dirEntry) override + { + if (m_subnodeIndex >= m_node->subnodes.size()) + return false; + + const node_t* subnode = m_node->subnodes[m_subnodeIndex]; + + strncpy(dirEntry->path, subnode->name.c_str(), sizeof(dirEntry->path) - 1); + dirEntry->path[sizeof(dirEntry->path) - 1] = '\0'; + dirEntry->isDirectory = subnode->type == FSAFileTree::NODETYPE_DIRECTORY; + dirEntry->isFile = subnode->type == FSAFileTree::NODETYPE_FILE; + dirEntry->fileSize = subnode->type == FSAFileTree::NODETYPE_FILE ? subnode->fileSize : 0; + + ++m_subnodeIndex; + return true; + } + + bool fscRewindDir() override + { + m_subnodeIndex = 0; + return true; + } + + private: + node_t* m_node; + size_t m_subnodeIndex; + }; + public: FSAFileTree() { rootNode.type = NODETYPE_DIRECTORY; } - bool addFile(std::string_view path, F* custom) + bool addFile(std::string_view path, size_t fileSize, F* custom) { FSCPath p(path); if (p.GetNodeCount() == 0) @@ -196,6 +238,7 @@ public: return false; // node already exists // add file node node_t* fileNode = newNode(directoryNode, NODETYPE_FILE, p.GetNodeName(p.GetNodeCount() - 1)); + fileNode->fileSize = fileSize; fileNode->custom = custom; return true; } @@ -214,6 +257,20 @@ public: return true; } + bool getDirectory(std::string_view path, FSCVirtualFile*& dirIterator) + { + FSCPath p(path); + if (p.GetNodeCount() == 0) + return false; + node_t* node = getByNodePath(p, p.GetNodeCount(), false); + if (node == nullptr) + return false; + if (node->type != NODETYPE_DIRECTORY) + return false; + dirIterator = new DirectoryIterator(node); + return true; + } + bool removeFile(std::string_view path) { FSCPath p(path); diff --git a/src/Cafe/Filesystem/fsc.h b/src/Cafe/Filesystem/fsc.h index a3df2af2..8b8ed5ef 100644 --- a/src/Cafe/Filesystem/fsc.h +++ b/src/Cafe/Filesystem/fsc.h @@ -212,4 +212,4 @@ bool FSCDeviceHostFS_Mount(std::string_view mountPath, std::string_view hostTarg // redirect device void fscDeviceRedirect_map(); -void fscDeviceRedirect_add(std::string_view virtualSourcePath, const fs::path& targetFilePath, sint32 priority); +void fscDeviceRedirect_add(std::string_view virtualSourcePath, size_t fileSize, const fs::path& targetFilePath, sint32 priority); diff --git a/src/Cafe/Filesystem/fscDeviceRedirect.cpp b/src/Cafe/Filesystem/fscDeviceRedirect.cpp index d25bff86..9c62d37a 100644 --- a/src/Cafe/Filesystem/fscDeviceRedirect.cpp +++ b/src/Cafe/Filesystem/fscDeviceRedirect.cpp @@ -11,7 +11,7 @@ struct RedirectEntry FSAFileTree<RedirectEntry> redirectTree; -void fscDeviceRedirect_add(std::string_view virtualSourcePath, const fs::path& targetFilePath, sint32 priority) +void fscDeviceRedirect_add(std::string_view virtualSourcePath, size_t fileSize, const fs::path& targetFilePath, sint32 priority) { // check if source already has a redirection RedirectEntry* existingEntry; @@ -24,7 +24,7 @@ void fscDeviceRedirect_add(std::string_view virtualSourcePath, const fs::path& t delete existingEntry; } RedirectEntry* entry = new RedirectEntry(targetFilePath, priority); - redirectTree.addFile(virtualSourcePath, entry); + redirectTree.addFile(virtualSourcePath, fileSize, entry); } class fscDeviceTypeRedirect : public fscDeviceC @@ -32,8 +32,15 @@ class fscDeviceTypeRedirect : public fscDeviceC FSCVirtualFile* fscDeviceOpenByPath(std::string_view path, FSC_ACCESS_FLAG accessFlags, void* ctx, sint32* fscStatus) override { RedirectEntry* redirectionEntry; - if (redirectTree.getFile(path, redirectionEntry)) + + if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE) && redirectTree.getFile(path, redirectionEntry)) return FSCVirtualFile_Host::OpenFile(redirectionEntry->dstPath, accessFlags, *fscStatus); + + FSCVirtualFile* dirIterator; + + if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR) && redirectTree.getDirectory(path, dirIterator)) + return dirIterator; + return nullptr; } diff --git a/src/Cafe/GraphicPack/GraphicPack2.cpp b/src/Cafe/GraphicPack/GraphicPack2.cpp index 27d423b9..c54c31cb 100644 --- a/src/Cafe/GraphicPack/GraphicPack2.cpp +++ b/src/Cafe/GraphicPack/GraphicPack2.cpp @@ -830,7 +830,7 @@ void GraphicPack2::_iterateReplacedFiles(const fs::path& currentPath, bool isAOC { virtualMountPath = fs::path("vol/content/") / virtualMountPath; } - fscDeviceRedirect_add(virtualMountPath.generic_string(), it.path().generic_string(), m_fs_priority); + fscDeviceRedirect_add(virtualMountPath.generic_string(), it.file_size(), it.path().generic_string(), m_fs_priority); } } } From b0bab273e21f8f648de011688d96baf78e064526 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Thu, 15 Aug 2024 02:16:03 +0200 Subject: [PATCH 168/299] padscore: Simulate queue behaviour for KPADRead --- src/Cafe/OS/libs/padscore/padscore.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/Cafe/OS/libs/padscore/padscore.cpp b/src/Cafe/OS/libs/padscore/padscore.cpp index 47f3bc4f..8ae4730d 100644 --- a/src/Cafe/OS/libs/padscore/padscore.cpp +++ b/src/Cafe/OS/libs/padscore/padscore.cpp @@ -12,6 +12,7 @@ enum class KPAD_ERROR : sint32 { NONE = 0, + NO_SAMPLE_DATA = -1, NO_CONTROLLER = -2, NOT_INITIALIZED = -5, }; @@ -106,6 +107,9 @@ void padscoreExport_WPADProbe(PPCInterpreter_t* hCPU) } else { + if(type) + *type = 253; + osLib_returnFromFunction(hCPU, WPAD_ERR_NO_CONTROLLER); } } @@ -420,9 +424,12 @@ void padscoreExport_KPADSetConnectCallback(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, old_callback.GetMPTR()); } +uint64 g_kpadLastRead[InputManager::kMaxWPADControllers] = {0}; bool g_kpadIsInited = true; + sint32 _KPADRead(uint32 channel, KPADStatus_t* samplingBufs, uint32 length, betype<KPAD_ERROR>* errResult) { + if (channel >= InputManager::kMaxWPADControllers) { debugBreakpoint(); @@ -446,6 +453,19 @@ sint32 _KPADRead(uint32 channel, KPADStatus_t* samplingBufs, uint32 length, bety return 0; } + // On console new input samples are only received every few ms and calling KPADRead(Ex) clears the internal queue regardless of length value + // thus calling KPADRead(Ex) again too soon on the same channel will result in no data being returned + // Games that depend on this: Affordable Space Adventures + uint64 currentTime = coreinit::OSGetTime(); + uint64 timeDif = currentTime - g_kpadLastRead[channel]; + if(length == 0 || timeDif < coreinit::EspressoTime::ConvertNsToTimerTicks(1000000)) + { + if (errResult) + *errResult = KPAD_ERROR::NO_SAMPLE_DATA; + return 0; + } + g_kpadLastRead[channel] = currentTime; + memset(samplingBufs, 0x00, sizeof(KPADStatus_t)); samplingBufs->wpadErr = WPAD_ERR_NONE; samplingBufs->data_format = controller->get_data_format(); From 2843da4479630e82d93ca0bb0c7e0c1748c86c48 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Thu, 15 Aug 2024 05:00:09 +0200 Subject: [PATCH 169/299] padscore: Invoke sampling callbacks every 5ms This fixes high input latency in games like Pokemon Rumble U which update input via the sampling callbacks --- src/Cafe/OS/libs/padscore/padscore.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Cafe/OS/libs/padscore/padscore.cpp b/src/Cafe/OS/libs/padscore/padscore.cpp index 8ae4730d..a83711fe 100644 --- a/src/Cafe/OS/libs/padscore/padscore.cpp +++ b/src/Cafe/OS/libs/padscore/padscore.cpp @@ -746,7 +746,8 @@ namespace padscore // call sampling callback for (auto i = 0; i < InputManager::kMaxWPADControllers; ++i) { - if (g_padscore.controller_data[i].sampling_callback) { + if (g_padscore.controller_data[i].sampling_callback) + { if (const auto controller = instance.get_wpad_controller(i)) { cemuLog_log(LogType::InputAPI, "Calling WPADsamplingCallback({})", i); @@ -761,7 +762,7 @@ namespace padscore { OSCreateAlarm(&g_padscore.alarm); const uint64 start_tick = coreinit::coreinit_getOSTime(); - const uint64 period_tick = coreinit::EspressoTime::GetTimerClock(); // once a second + const uint64 period_tick = coreinit::EspressoTime::GetTimerClock() / 200; // every 5ms MPTR handler = PPCInterpreter_makeCallableExportDepr(TickFunction); OSSetPeriodicAlarm(&g_padscore.alarm, start_tick, period_tick, handler); } From 294a6de779ddf9c6294dafa603ad23a404052ec3 Mon Sep 17 00:00:00 2001 From: 20943204920434 <160030054+20943204920434@users.noreply.github.com> Date: Thu, 15 Aug 2024 16:22:41 +0200 Subject: [PATCH 170/299] Update appimage.sh to support runtime libstdc++.so.6 loading (#1292) Add checkrt plugin in order to detect the right libstdc++.so.6 version to load. --- dist/linux/appimage.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dist/linux/appimage.sh b/dist/linux/appimage.sh index 7bfc4701..e9081521 100755 --- a/dist/linux/appimage.sh +++ b/dist/linux/appimage.sh @@ -10,6 +10,8 @@ curl -sSfL https://github.com"$(curl https://github.com/probonopd/go-appimage/re chmod a+x mkappimage.AppImage curl -sSfLO "https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh" chmod a+x linuxdeploy-plugin-gtk.sh +curl -sSfLO "https://github.com/darealshinji/linuxdeploy-plugin-checkrt/releases/download/continuous/linuxdeploy-plugin-checkrt.sh" +chmod a+x linuxdeploy-plugin-checkrt.sh if [[ ! -e /usr/lib/x86_64-linux-gnu ]]; then sed -i 's#lib\/x86_64-linux-gnu#lib64#g' linuxdeploy-plugin-gtk.sh @@ -39,7 +41,8 @@ export NO_STRIP=1 -d "${GITHUB_WORKSPACE}"/AppDir/info.cemu.Cemu.desktop \ -i "${GITHUB_WORKSPACE}"/AppDir/info.cemu.Cemu.png \ -e "${GITHUB_WORKSPACE}"/AppDir/usr/bin/Cemu \ - --plugin gtk + --plugin gtk \ + --plugin checkrt if ! GITVERSION="$(git rev-parse --short HEAD 2>/dev/null)"; then GITVERSION=experimental From 958137a301208141b1e62f6b2f5b3ce2f04335d6 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Thu, 15 Aug 2024 18:26:58 +0200 Subject: [PATCH 171/299] vpad: Keep second channel empty if no extra GamePad is configured --- src/Cafe/OS/libs/padscore/padscore.cpp | 1 - src/Cafe/OS/libs/vpad/vpad.cpp | 24 +++++++++++------------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/Cafe/OS/libs/padscore/padscore.cpp b/src/Cafe/OS/libs/padscore/padscore.cpp index a83711fe..0a577b97 100644 --- a/src/Cafe/OS/libs/padscore/padscore.cpp +++ b/src/Cafe/OS/libs/padscore/padscore.cpp @@ -494,7 +494,6 @@ void padscoreExport_KPADReadEx(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, samplesRead); } -bool debugUseDRC1 = true; void padscoreExport_KPADRead(PPCInterpreter_t* hCPU) { ppcDefineParamU32(channel, 0); diff --git a/src/Cafe/OS/libs/vpad/vpad.cpp b/src/Cafe/OS/libs/vpad/vpad.cpp index 94bb0ca2..ded4304d 100644 --- a/src/Cafe/OS/libs/vpad/vpad.cpp +++ b/src/Cafe/OS/libs/vpad/vpad.cpp @@ -50,7 +50,6 @@ extern bool isLaunchTypeELF; -bool debugUseDRC = true; VPADDir g_vpadGyroDirOverwrite[VPAD_MAX_CONTROLLERS] = { {{1.0f,0.0f,0.0f}, {0.0f,1.0f,0.0f}, {0.0f, 0.0f, 0.1f}}, @@ -240,19 +239,20 @@ namespace vpad status->tpProcessed2.validity = VPAD_TP_VALIDITY_INVALID_XY; const auto controller = InputManager::instance().get_vpad_controller(channel); - if (!controller || debugUseDRC == false) + if (!controller) { - // no controller + // most games expect the Wii U GamePad to be connected, so even if the user has not set it up we should still return empty samples for channel 0 + if(channel != 0) + { + if (error) + *error = VPAD_READ_ERR_NO_CONTROLLER; + if (length > 0) + status->vpadErr = -1; + return 0; + } if (error) - *error = VPAD_READ_ERR_NONE; // VPAD_READ_ERR_NO_DATA; // VPAD_READ_ERR_NO_CONTROLLER; - + *error = VPAD_READ_ERR_NONE; return 1; - //osLib_returnFromFunction(hCPU, 1); return; - } - - if (channel != 0) - { - debugBreakpoint(); } const bool vpadDelayEnabled = ActiveSettings::VPADDelayEnabled(); @@ -274,9 +274,7 @@ namespace vpad // not ready yet if (error) *error = VPAD_READ_ERR_NONE; - return 0; - //osLib_returnFromFunction(hCPU, 0); return; } else if (dif <= ESPRESSO_TIMER_CLOCK) { From 9e53c1ce2760ac6a33d52f8813d79402b5183203 Mon Sep 17 00:00:00 2001 From: Cemu-Language CI <github-actions@github.com> Date: Thu, 22 Aug 2024 05:17:01 +0000 Subject: [PATCH 172/299] Update translation files --- bin/resources/es/cemu.mo | Bin 65733 -> 68744 bytes bin/resources/ko/cemu.mo | Bin 69784 -> 70573 bytes bin/resources/ru/cemu.mo | Bin 89284 -> 90752 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/bin/resources/es/cemu.mo b/bin/resources/es/cemu.mo index 856049de037de70de90a2868e6da991fe25d1235..a43d4a1dc46c8db327cd91f2ae906dccb734169b 100644 GIT binary patch delta 20556 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 a-@xeozio_(w*BKsObmbcl=y2)%>MwjNrbNe delta 17814 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 H+>rkVj?5=5 diff --git a/bin/resources/ko/cemu.mo b/bin/resources/ko/cemu.mo index 63d82220c256cda72cbc9cf550ff5f36d8da6bb4..b812df970a34b41a6d2407c9ec4771adadd6a5db 100644 GIT binary patch delta 19394 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 XEra)f9nuMETi7-Ezl1Lqc75<a7UARC delta 18861 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 D^s~21 diff --git a/bin/resources/ru/cemu.mo b/bin/resources/ru/cemu.mo index 619ba6783440be10ac2b2c0c77cda8523926e8da..4ff04e2bf7ba65f396cd0c2f640e8bc7df3c35cf 100644 GIT binary patch delta 22957 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 Pe0X5OzH4rB?~V8`fV=g7 delta 21666 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 C7n9Kd From 573c98b2f80826c2bf0a9994c4e7d1f0950b4dc7 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Fri, 23 Aug 2024 19:26:33 +0200 Subject: [PATCH 173/299] GfxPack: Workaround for invisible detail panel Fixes #1307 There is probably a better way to calculate the maximum width. But this suffices for now as a workaround --- src/gui/GraphicPacksWindow2.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/GraphicPacksWindow2.cpp b/src/gui/GraphicPacksWindow2.cpp index 29f4b865..c49cbeae 100644 --- a/src/gui/GraphicPacksWindow2.cpp +++ b/src/gui/GraphicPacksWindow2.cpp @@ -458,10 +458,10 @@ void GraphicPacksWindow2::OnTreeSelectionChanged(wxTreeEvent& event) m_shown_graphic_pack = gp; - m_graphic_pack_name->Wrap(m_graphic_pack_name->GetParent()->GetClientSize().GetWidth() - 10); + m_graphic_pack_name->Wrap(m_graphic_pack_name->GetParent()->GetClientSize().GetWidth() - 20); m_graphic_pack_name->GetGrandParent()->Layout(); - m_graphic_pack_description->Wrap(m_graphic_pack_description->GetParent()->GetClientSize().GetWidth() - 10); + m_graphic_pack_description->Wrap(m_graphic_pack_description->GetParent()->GetClientSize().GetWidth() - 20); m_graphic_pack_description->GetGrandParent()->Layout(); m_right_panel->FitInside(); From dc9d99b03b38f3cf427714ad1ebb4d6d29f645fa Mon Sep 17 00:00:00 2001 From: bl <147349656+squelchiee@users.noreply.github.com> Date: Sat, 24 Aug 2024 16:03:03 -0300 Subject: [PATCH 174/299] nn_fp: Implement GetMyComment and UpdateCommentAsync (#1173) --- src/Cafe/IOSU/legacy/iosu_fpd.cpp | 55 ++++++++++++++++++++++++++----- src/Cafe/IOSU/legacy/iosu_fpd.h | 2 ++ src/Cafe/OS/libs/nn_fp/nn_fp.cpp | 33 +++++++++++++++++++ src/Cemu/nex/nexFriends.cpp | 25 +++++++++++++- src/Cemu/nex/nexFriends.h | 7 +++- 5 files changed, 111 insertions(+), 11 deletions(-) diff --git a/src/Cafe/IOSU/legacy/iosu_fpd.cpp b/src/Cafe/IOSU/legacy/iosu_fpd.cpp index aca1a332..28d248ae 100644 --- a/src/Cafe/IOSU/legacy/iosu_fpd.cpp +++ b/src/Cafe/IOSU/legacy/iosu_fpd.cpp @@ -511,6 +511,8 @@ namespace iosu return CallHandler_GetBlackList(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::GetFriendListEx: return CallHandler_GetFriendListEx(fpdClient, vecIn, numVecIn, vecOut, numVecOut); + case FPD_REQUEST_ID::UpdateCommentAsync: + return CallHandler_UpdateCommentAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::UpdatePreferenceAsync: return CallHandler_UpdatePreferenceAsync(fpdClient, vecIn, numVecIn, vecOut, numVecOut); case FPD_REQUEST_ID::AddFriendRequestByPlayRecordAsync: @@ -719,18 +721,23 @@ namespace iosu nnResult CallHandler_GetMyComment(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { - static constexpr uint32 MY_COMMENT_LENGTH = 0x12; // are comments utf16? Buffer length is 0x24 if(numVecIn != 0 || numVecOut != 1) return FPResult_InvalidIPCParam; - if(vecOut->size != MY_COMMENT_LENGTH*sizeof(uint16be)) - { - cemuLog_log(LogType::Force, "GetMyComment: Unexpected output size"); - return FPResult_InvalidIPCParam; - } std::basic_string<uint16be> myComment; - myComment.resize(MY_COMMENT_LENGTH); - memcpy(vecOut->basePhys.GetPtr(), myComment.data(), MY_COMMENT_LENGTH*sizeof(uint16be)); - return 0; + if(g_fpd.nexFriendSession) + { + if(vecOut->size != MY_COMMENT_LENGTH * sizeof(uint16be)) + { + cemuLog_log(LogType::Force, "GetMyComment: Unexpected output size"); + return FPResult_InvalidIPCParam; + } + nexComment myNexComment; + g_fpd.nexFriendSession->getMyComment(myNexComment); + myComment = StringHelpers::FromUtf8(myNexComment.commentString); + } + myComment.insert(0, 1, '\0'); + memcpy(vecOut->basePhys.GetPtr(), myComment.c_str(), MY_COMMENT_LENGTH * sizeof(uint16be)); + return FPResult_Ok; } nnResult CallHandler_GetMyPreference(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) @@ -1143,6 +1150,36 @@ namespace iosu return FPResult_Ok; } + nnResult CallHandler_UpdateCommentAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) + { + std::unique_lock _l(g_fpd.mtxFriendSession); + if (numVecIn != 1 || numVecOut != 0) + return FPResult_InvalidIPCParam; + if (!g_fpd.nexFriendSession) + return FPResult_RequestFailed; + uint32 messageLength = vecIn[0].size / sizeof(uint16be); + DeclareInputPtr(newComment, uint16be, messageLength, 0); + if (messageLength == 0 || newComment[messageLength-1] != 0) + { + cemuLog_log(LogType::Force, "UpdateCommentAsync: Message must contain at least a null-termination character"); + return FPResult_InvalidIPCParam; + } + IPCCommandBody* cmd = ServiceCallDelayCurrentResponse(); + + auto utf8_comment = StringHelpers::ToUtf8(newComment, messageLength); + nexComment temporaryComment; + temporaryComment.ukn0 = 0; + temporaryComment.commentString = utf8_comment; + temporaryComment.ukn1 = 0; + + g_fpd.nexFriendSession->updateCommentAsync(temporaryComment, [cmd](NexFriends::RpcErrorCode result) { + if (result != NexFriends::ERR_NONE) + return ServiceCallAsyncRespond(cmd, FPResult_RequestFailed); + ServiceCallAsyncRespond(cmd, FPResult_Ok); + }); + return FPResult_Ok; + } + nnResult CallHandler_UpdatePreferenceAsync(FPDClient* fpdClient, IPCIoctlVector* vecIn, uint32 numVecIn, IPCIoctlVector* vecOut, uint32 numVecOut) { std::unique_lock _l(g_fpd.mtxFriendSession); diff --git a/src/Cafe/IOSU/legacy/iosu_fpd.h b/src/Cafe/IOSU/legacy/iosu_fpd.h index 0a6f0885..b1c30765 100644 --- a/src/Cafe/IOSU/legacy/iosu_fpd.h +++ b/src/Cafe/IOSU/legacy/iosu_fpd.h @@ -212,6 +212,7 @@ namespace iosu static const int RELATIONSHIP_FRIEND = 3; static const int GAMEMODE_MAX_MESSAGE_LENGTH = 0x80; // limit includes null-terminator character, so only 0x7F actual characters can be used + static const int MY_COMMENT_LENGTH = 0x12; enum class FPD_REQUEST_ID { @@ -245,6 +246,7 @@ namespace iosu CheckSettingStatusAsync = 0x7596, GetFriendListEx = 0x75F9, GetFriendRequestListEx = 0x76C1, + UpdateCommentAsync = 0x7726, UpdatePreferenceAsync = 0x7727, RemoveFriendAsync = 0x7789, DeleteFriendFlagsAsync = 0x778A, diff --git a/src/Cafe/OS/libs/nn_fp/nn_fp.cpp b/src/Cafe/OS/libs/nn_fp/nn_fp.cpp index fc757ea9..86ca4708 100644 --- a/src/Cafe/OS/libs/nn_fp/nn_fp.cpp +++ b/src/Cafe/OS/libs/nn_fp/nn_fp.cpp @@ -464,6 +464,14 @@ namespace nn return ipcCtx->Submit(std::move(ipcCtx)); } + nnResult GetMyPlayingGame(iosu::fpd::GameKey* myPlayingGame) + { + FP_API_BASE(); + auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::GetMyPlayingGame); + ipcCtx->AddOutput(myPlayingGame, sizeof(iosu::fpd::GameKey)); + return ipcCtx->Submit(std::move(ipcCtx)); + } + nnResult GetMyPreference(iosu::fpd::FPDPreference* myPreference) { FP_API_BASE(); @@ -472,6 +480,14 @@ namespace nn return ipcCtx->Submit(std::move(ipcCtx)); } + nnResult GetMyComment(uint16be* myComment) + { + FP_API_BASE(); + auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::GetMyComment); + ipcCtx->AddOutput(myComment, iosu::fpd::MY_COMMENT_LENGTH * sizeof(uint16be)); + return ipcCtx->Submit(std::move(ipcCtx)); + } + nnResult GetMyMii(FFLData_t* fflData) { FP_API_BASE(); @@ -607,6 +623,20 @@ namespace nn return resultBuf != 0 ? 1 : 0; } + nnResult UpdateCommentAsync(uint16be* newComment, void* funcPtr, void* customParam) + { + FP_API_BASE(); + auto ipcCtx = std::make_unique<FPIpcContext>(iosu::fpd::FPD_REQUEST_ID::UpdateCommentAsync); + uint32 commentLen = CafeStringHelpers::Length(newComment, iosu::fpd::MY_COMMENT_LENGTH-1); + if (commentLen >= iosu::fpd::MY_COMMENT_LENGTH-1) + { + cemuLog_log(LogType::Force, "UpdateCommentAsync: message too long"); + return FPResult_InvalidIPCParam; + } + ipcCtx->AddInput(newComment, sizeof(uint16be) * commentLen + 2); + return ipcCtx->SubmitAsync(std::move(ipcCtx), funcPtr, customParam); + } + nnResult UpdatePreferenceAsync(iosu::fpd::FPDPreference* newPreference, void* funcPtr, void* customParam) { FP_API_BASE(); @@ -763,7 +793,9 @@ namespace nn cafeExportRegisterFunc(GetMyAccountId, "nn_fp", "GetMyAccountId__Q2_2nn2fpFPc", LogType::NN_FP); cafeExportRegisterFunc(GetMyScreenName, "nn_fp", "GetMyScreenName__Q2_2nn2fpFPw", LogType::NN_FP); cafeExportRegisterFunc(GetMyMii, "nn_fp", "GetMyMii__Q2_2nn2fpFP12FFLStoreData", LogType::NN_FP); + cafeExportRegisterFunc(GetMyPlayingGame, "nn_fp", "GetMyPlayingGame__Q2_2nn2fpFPQ3_2nn2fp7GameKey", LogType::NN_FP); cafeExportRegisterFunc(GetMyPreference, "nn_fp", "GetMyPreference__Q2_2nn2fpFPQ3_2nn2fp10Preference", LogType::NN_FP); + cafeExportRegisterFunc(GetMyComment, "nn_fp", "GetMyComment__Q2_2nn2fpFPQ3_2nn2fp7Comment", LogType::NN_FP); cafeExportRegisterFunc(GetFriendAccountId, "nn_fp", "GetFriendAccountId__Q2_2nn2fpFPA17_cPCUiUi", LogType::NN_FP); cafeExportRegisterFunc(GetFriendScreenName, "nn_fp", "GetFriendScreenName__Q2_2nn2fpFPA11_wPCUiUibPUc", LogType::NN_FP); @@ -774,6 +806,7 @@ namespace nn cafeExportRegisterFunc(CheckSettingStatusAsync, "nn_fp", "CheckSettingStatusAsync__Q2_2nn2fpFPUcPFQ2_2nn6ResultPv_vPv", LogType::NN_FP); cafeExportRegisterFunc(IsPreferenceValid, "nn_fp", "IsPreferenceValid__Q2_2nn2fpFv", LogType::NN_FP); + cafeExportRegisterFunc(UpdateCommentAsync, "nn_fp", "UpdateCommentAsync__Q2_2nn2fpFPCwPFQ2_2nn6ResultPv_vPv", LogType::NN_FP); cafeExportRegisterFunc(UpdatePreferenceAsync, "nn_fp", "UpdatePreferenceAsync__Q2_2nn2fpFPCQ3_2nn2fp10PreferencePFQ2_2nn6ResultPv_vPv", LogType::NN_FP); cafeExportRegisterFunc(GetRequestBlockSettingAsync, "nn_fp", "GetRequestBlockSettingAsync__Q2_2nn2fpFPUcPCUiUiPFQ2_2nn6ResultPv_vPv", LogType::NN_FP); diff --git a/src/Cemu/nex/nexFriends.cpp b/src/Cemu/nex/nexFriends.cpp index 927418ca..36ba4a53 100644 --- a/src/Cemu/nex/nexFriends.cpp +++ b/src/Cemu/nex/nexFriends.cpp @@ -277,7 +277,8 @@ void NexFriends::handleResponse_getAllInformation(nexServiceResponse_t* response } NexFriends* session = (NexFriends*)nexFriends; session->myPreference = nexPrincipalPreference(&response->data); - nexComment comment(&response->data); + auto comment = nexComment(&response->data); + session->myComment = comment; if (response->data.hasReadOutOfBounds()) return; // acquire lock on lists @@ -391,6 +392,28 @@ void NexFriends::getMyPreference(nexPrincipalPreference& preference) preference = myPreference; } +bool NexFriends::updateCommentAsync(nexComment newComment, std::function<void(RpcErrorCode)> cb) +{ + uint8 tempNexBufferArray[1024]; + nexPacketBuffer packetBuffer(tempNexBufferArray, sizeof(tempNexBufferArray), true); + newComment.writeData(&packetBuffer); + nexCon->callMethod( + NEX_PROTOCOL_FRIENDS_WIIU, 15, &packetBuffer, [this, cb, newComment](nexServiceResponse_t* response) -> void { + if (!response->isSuccessful) + return cb(NexFriends::ERR_RPC_FAILED); + this->myComment = newComment; + return cb(NexFriends::ERR_NONE); + }, + true); + // TEST + return true; +} + +void NexFriends::getMyComment(nexComment& comment) +{ + comment = myComment; +} + bool NexFriends::addProvisionalFriendByPidGuessed(uint32 principalId) { uint8 tempNexBufferArray[512]; diff --git a/src/Cemu/nex/nexFriends.h b/src/Cemu/nex/nexFriends.h index 1077b0d5..05cc433f 100644 --- a/src/Cemu/nex/nexFriends.h +++ b/src/Cemu/nex/nexFriends.h @@ -297,7 +297,9 @@ public: void writeData(nexPacketBuffer* pb) const override { - cemu_assert_unimplemented(); + pb->writeU8(ukn0); + pb->writeString(commentString.c_str()); + pb->writeU64(ukn1); } void readData(nexPacketBuffer* pb) override @@ -554,6 +556,7 @@ public: bool getFriendRequestByMessageId(nexFriendRequest& friendRequestData, bool* isIncoming, uint64 messageId); bool isOnline(); void getMyPreference(nexPrincipalPreference& preference); + void getMyComment(nexComment& comment); // asynchronous API (data has to be requested) bool addProvisionalFriend(char* name, std::function<void(RpcErrorCode)> cb); @@ -565,6 +568,7 @@ public: void acceptFriendRequest(uint64 messageId, std::function<void(RpcErrorCode)> cb); void deleteFriendRequest(uint64 messageId, std::function<void(RpcErrorCode)> cb); // rejecting incoming friend request (differs from blocking friend requests) bool updatePreferencesAsync(const nexPrincipalPreference newPreferences, std::function<void(RpcErrorCode)> cb); + bool updateCommentAsync(const nexComment newComment, std::function<void(RpcErrorCode)> cb); void updateMyPresence(nexPresenceV2& myPresence); void setNotificationHandler(void(*notificationHandler)(NOTIFICATION_TYPE notificationType, uint32 pid)); @@ -619,6 +623,7 @@ private: // local friend state nexPresenceV2 myPresence; nexPrincipalPreference myPreference; + nexComment myComment; std::recursive_mutex mtx_lists; std::vector<nexFriend> list_friends; From d7f39aab054a715e7b0481407298cbd654212fb9 Mon Sep 17 00:00:00 2001 From: Cemu-Language CI <github-actions@github.com> Date: Mon, 26 Aug 2024 09:16:11 +0000 Subject: [PATCH 175/299] Update translation files --- bin/resources/hu/cemu.mo | Bin 71267 -> 72404 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/bin/resources/hu/cemu.mo b/bin/resources/hu/cemu.mo index dd3d2fbc74157e457fac7fefe3356bdbfbbe9e11..51b00d083cc320a55615d50774d60c74f71108d5 100644 GIT binary patch delta 22802 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 delta 21762 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 From 1234e2c11808a97c76381f147e564d74f6ffbbd1 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Mon, 26 Aug 2024 11:43:38 +0200 Subject: [PATCH 176/299] Preparations for 2.1 (#1306) --- .github/getversion.cpp | 9 -- .github/workflows/build.yml | 66 ++++--------- .github/workflows/build_check.yml | 3 - .../workflows/deploy_experimental_release.yml | 93 ++++++++++++++++--- .../workflows/determine_release_version.yml | 74 +++++++++++++++ CMakeLists.txt | 27 +++--- src/Cafe/HW/Latte/Core/LatteShader.cpp | 2 +- src/Cafe/HW/Latte/Renderer/RendererShader.cpp | 6 +- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 4 +- .../OS/libs/coreinit/coreinit_Spinlock.cpp | 2 +- src/Common/version.h | 29 ++---- src/config/CemuConfig.cpp | 2 + src/config/CemuConfig.h | 3 +- src/config/LaunchSettings.cpp | 6 +- src/gui/CemuUpdateWindow.cpp | 4 +- src/gui/DownloadGraphicPacksWindow.cpp | 2 +- src/gui/GeneralSettings2.cpp | 56 ++++++++--- src/gui/GeneralSettings2.h | 2 +- src/resource/cemu.rc | 6 +- 19 files changed, 261 insertions(+), 135 deletions(-) delete mode 100644 .github/getversion.cpp create mode 100644 .github/workflows/determine_release_version.yml diff --git a/.github/getversion.cpp b/.github/getversion.cpp deleted file mode 100644 index 469a796e..00000000 --- a/.github/getversion.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include <stdio.h> -#include "./../src/Common/version.h" - -// output current Cemu version for CI workflow. Do not modify -int main() -{ - printf("%d.%d", EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR); - return 0; -} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9fb775e2..dd28ceb5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,10 +3,10 @@ name: Build Cemu on: workflow_call: inputs: - deploymode: + next_version_major: required: false type: string - experimentalversion: + next_version_minor: required: false type: string @@ -24,25 +24,17 @@ jobs: submodules: "recursive" fetch-depth: 0 - - name: Setup release mode parameters (for deploy) - if: ${{ inputs.deploymode == 'release' }} + - name: Setup release mode parameters run: | echo "BUILD_MODE=release" >> $GITHUB_ENV echo "BUILD_FLAGS=" >> $GITHUB_ENV echo "Build mode is release" - - name: Setup debug mode parameters (for continous build) - if: ${{ inputs.deploymode != 'release' }} + - name: Setup build flags for version + if: ${{ inputs.next_version_major != '' }} run: | - echo "BUILD_MODE=debug" >> $GITHUB_ENV - echo "BUILD_FLAGS=" >> $GITHUB_ENV - echo "Build mode is debug" - - - name: Setup version for experimental - if: ${{ inputs.experimentalversion != '' }} - run: | - echo "[INFO] Experimental version ${{ inputs.experimentalversion }}" - echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEXPERIMENTAL_VERSION=${{ inputs.experimentalversion }}" >> $GITHUB_ENV + echo "[INFO] Version ${{ inputs.next_version_major }}.${{ inputs.next_version_minor }}" + echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEMULATOR_VERSION_MAJOR=${{ inputs.next_version_major }} -DEMULATOR_VERSION_MINOR=${{ inputs.next_version_minor }}" >> $GITHUB_ENV - name: "Install system dependencies" run: | @@ -81,12 +73,10 @@ jobs: cmake --build build - name: Prepare artifact - if: ${{ inputs.deploymode == 'release' }} run: mv bin/Cemu_release bin/Cemu - name: Upload artifact uses: actions/upload-artifact@v4 - if: ${{ inputs.deploymode == 'release' }} with: name: cemu-bin-linux-x64 path: ./bin/Cemu @@ -128,24 +118,17 @@ jobs: with: submodules: "recursive" - - name: Setup release mode parameters (for deploy) - if: ${{ inputs.deploymode == 'release' }} + - name: Setup release mode parameters run: | echo "BUILD_MODE=release" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append echo "BUILD_FLAGS=" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append echo "Build mode is release" - - - name: Setup debug mode parameters (for continous build) - if: ${{ inputs.deploymode != 'release' }} + + - name: Setup build flags for version + if: ${{ inputs.next_version_major != '' }} run: | - echo "BUILD_MODE=debug" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append - echo "BUILD_FLAGS=" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append - echo "Build mode is debug" - - name: Setup version for experimental - if: ${{ inputs.experimentalversion != '' }} - run: | - echo "[INFO] Experimental version ${{ inputs.experimentalversion }}" - echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEXPERIMENTAL_VERSION=${{ inputs.experimentalversion }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append + echo "[INFO] Version ${{ inputs.next_version_major }}.${{ inputs.next_version_minor }}" + echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEMULATOR_VERSION_MAJOR=${{ inputs.next_version_major }} -DEMULATOR_VERSION_MINOR=${{ inputs.next_version_minor }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append - name: "Setup cmake" uses: jwlawson/actions-setup-cmake@v2 @@ -184,12 +167,10 @@ jobs: cmake --build . --config ${{ env.BUILD_MODE }} - name: Prepare artifact - if: ${{ inputs.deploymode == 'release' }} run: Rename-Item bin/Cemu_release.exe Cemu.exe - name: Upload artifact uses: actions/upload-artifact@v4 - if: ${{ inputs.deploymode == 'release' }} with: name: cemu-bin-windows-x64 path: ./bin/Cemu.exe @@ -202,24 +183,17 @@ jobs: with: submodules: "recursive" - - name: Setup release mode parameters (for deploy) - if: ${{ inputs.deploymode == 'release' }} + - name: Setup release mode parameters run: | echo "BUILD_MODE=release" >> $GITHUB_ENV echo "BUILD_FLAGS=" >> $GITHUB_ENV echo "Build mode is release" - - name: Setup debug mode parameters (for continous build) - if: ${{ inputs.deploymode != 'release' }} + + - name: Setup build flags for version + if: ${{ inputs.next_version_major != '' }} run: | - echo "BUILD_MODE=debug" >> $GITHUB_ENV - echo "BUILD_FLAGS=" >> $GITHUB_ENV - echo "Build mode is debug" - - - name: Setup version for experimental - if: ${{ inputs.experimentalversion != '' }} - run: | - echo "[INFO] Experimental version ${{ inputs.experimentalversion }}" - echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEXPERIMENTAL_VERSION=${{ inputs.experimentalversion }}" >> $GITHUB_ENV + echo "[INFO] Version ${{ inputs.next_version_major }}.${{ inputs.next_version_minor }}" + echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEMULATOR_VERSION_MAJOR=${{ inputs.next_version_major }} -DEMULATOR_VERSION_MINOR=${{ inputs.next_version_minor }}" >> $GITHUB_ENV - name: "Install system dependencies" run: | @@ -275,7 +249,6 @@ jobs: cmake --build build - name: Prepare artifact - if: ${{ inputs.deploymode == 'release' }} run: | mkdir bin/Cemu_app mv bin/Cemu_release.app bin/Cemu_app/Cemu.app @@ -289,7 +262,6 @@ jobs: - name: Upload artifact uses: actions/upload-artifact@v4 - if: ${{ inputs.deploymode == 'release' }} with: name: cemu-bin-macos-x64 path: ./bin/Cemu.dmg diff --git a/.github/workflows/build_check.yml b/.github/workflows/build_check.yml index 49ef79e9..5d24b0c6 100644 --- a/.github/workflows/build_check.yml +++ b/.github/workflows/build_check.yml @@ -16,6 +16,3 @@ on: jobs: build: uses: ./.github/workflows/build.yml - with: - deploymode: release - experimentalversion: 999999 diff --git a/.github/workflows/deploy_experimental_release.yml b/.github/workflows/deploy_experimental_release.yml index a8c5ec53..97e0c69e 100644 --- a/.github/workflows/deploy_experimental_release.yml +++ b/.github/workflows/deploy_experimental_release.yml @@ -1,20 +1,83 @@ name: Deploy experimental release on: workflow_dispatch: + inputs: + changelog0: + description: 'Enter the changelog lines for this release. Each line is a feature / bullet point. Do not use dash.' + required: true + type: string + changelog1: + description: 'Feature 2' + required: false + type: string + changelog2: + description: 'Feature 3' + required: false + type: string + changelog3: + description: 'Feature 4' + required: false + type: string + changelog4: + description: 'Feature 5' + required: false + type: string + changelog5: + description: 'Feature 6' + required: false + type: string + changelog6: + description: 'Feature 7' + required: false + type: string + changelog7: + description: 'Feature 8' + required: false + type: string + changelog8: + description: 'Feature 9' + required: false + type: string + changelog9: + description: 'Feature 10' + required: false + type: string jobs: + calculate-version: + name: Calculate Version + uses: ./.github/workflows/determine_release_version.yml call-release-build: uses: ./.github/workflows/build.yml + needs: calculate-version with: - deploymode: release - experimentalversion: ${{ github.run_number }} + next_version_major: ${{ needs.calculate-version.outputs.next_version_major }} + next_version_minor: ${{ needs.calculate-version.outputs.next_version_minor }} deploy: name: Deploy experimental release runs-on: ubuntu-22.04 - needs: call-release-build + needs: [call-release-build, calculate-version] steps: - - uses: actions/checkout@v3 - + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Generate changelog + id: generate_changelog + run: | + CHANGELOG="" + if [ -n "${{ github.event.inputs.changelog0 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog0 }}\n"; fi + if [ -n "${{ github.event.inputs.changelog1 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog1 }}\n"; fi + if [ -n "${{ github.event.inputs.changelog2 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog2 }}\n"; fi + if [ -n "${{ github.event.inputs.changelog3 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog3 }}\n"; fi + if [ -n "${{ github.event.inputs.changelog4 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog4 }}\n"; fi + if [ -n "${{ github.event.inputs.changelog5 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog5 }}\n"; fi + if [ -n "${{ github.event.inputs.changelog6 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog6 }}\n"; fi + if [ -n "${{ github.event.inputs.changelog7 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog7 }}\n"; fi + if [ -n "${{ github.event.inputs.changelog8 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog8 }}\n"; fi + if [ -n "${{ github.event.inputs.changelog9 }}" ]; then CHANGELOG="$CHANGELOG- ${{ github.event.inputs.changelog9 }}\n"; fi + echo -e "$CHANGELOG" + echo "RELEASE_BODY=$CHANGELOG" >> $GITHUB_ENV - uses: actions/download-artifact@v4 with: name: cemu-bin-linux-x64 @@ -40,15 +103,13 @@ jobs: mkdir upload sudo apt install zip - - name: Get version + - name: Set version dependent vars run: | - echo "Experimental version: ${{ github.run_number }}" - ls - gcc -o getversion .github/getversion.cpp - ./getversion - echo "Cemu CI version: $(./getversion)" - echo "CEMU_FOLDER_NAME=Cemu_$(./getversion)-${{ github.run_number }}" >> $GITHUB_ENV - echo "CEMU_VERSION=$(./getversion)-${{ github.run_number }}" >> $GITHUB_ENV + echo "Version: ${{ needs.calculate-version.outputs.next_version }}" + echo "CEMU_FOLDER_NAME=Cemu_${{ needs.calculate-version.outputs.next_version }}" + echo "CEMU_VERSION=${{ needs.calculate-version.outputs.next_version }}" + echo "CEMU_FOLDER_NAME=Cemu_${{ needs.calculate-version.outputs.next_version }}" >> $GITHUB_ENV + echo "CEMU_VERSION=${{ needs.calculate-version.outputs.next_version }}" >> $GITHUB_ENV - name: Create release from windows-bin run: | @@ -83,4 +144,8 @@ jobs: wget -O ghr.tar.gz https://github.com/tcnksm/ghr/releases/download/v0.15.0/ghr_v0.15.0_linux_amd64.tar.gz tar xvzf ghr.tar.gz; rm ghr.tar.gz echo "[INFO] Release tag: v${{ env.CEMU_VERSION }}" - ghr_v0.15.0_linux_amd64/ghr -prerelease -t ${{ secrets.GITHUB_TOKEN }} -n "Cemu ${{ env.CEMU_VERSION }} (Experimental)" -b "Cemu experimental release" "v${{ env.CEMU_VERSION }}" ./upload + CHANGELOG_UNESCAPED=$(printf "%s\n" "${{ env.RELEASE_BODY }}" | sed 's/\\n/\n/g') + RELEASE_BODY=$(printf "%s\n%s" \ + "**Changelog:**" \ + "$CHANGELOG_UNESCAPED") + ghr_v0.15.0_linux_amd64/ghr -draft -t ${{ secrets.GITHUB_TOKEN }} -n "Cemu ${{ env.CEMU_VERSION }}" -b "$RELEASE_BODY" "v${{ env.CEMU_VERSION }}" ./upload diff --git a/.github/workflows/determine_release_version.yml b/.github/workflows/determine_release_version.yml new file mode 100644 index 00000000..be606941 --- /dev/null +++ b/.github/workflows/determine_release_version.yml @@ -0,0 +1,74 @@ +name: Calculate Next Version from release history + +on: + workflow_dispatch: + workflow_call: + outputs: + next_version: + description: "The next semantic version" + value: ${{ jobs.calculate-version.outputs.next_version }} + next_version_major: + description: "The next semantic version (major)" + value: ${{ jobs.calculate-version.outputs.next_version_major }} + next_version_minor: + description: "The next semantic version (minor)" + value: ${{ jobs.calculate-version.outputs.next_version_minor }} + +jobs: + calculate-version: + runs-on: ubuntu-latest + outputs: + next_version: ${{ steps.calculate_next_version.outputs.next_version }} + next_version_major: ${{ steps.calculate_next_version.outputs.next_version_major }} + next_version_minor: ${{ steps.calculate_next_version.outputs.next_version_minor }} + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Get all releases + id: get_all_releases + run: | + # Fetch all releases and check for API errors + RESPONSE=$(curl -s -o response.json -w "%{http_code}" "https://api.github.com/repos/${{ github.repository }}/releases?per_page=100") + if [ "$RESPONSE" -ne 200 ]; then + echo "Failed to fetch releases. HTTP status: $RESPONSE" + cat response.json + exit 1 + fi + + # Extract and sort tags + ALL_TAGS=$(jq -r '.[].tag_name' response.json | grep -E '^v[0-9]+\.[0-9]+(-[0-9]+)?$' | sed 's/-.*//' | sort -V | tail -n 1) + + # Exit if no tags were found + if [ -z "$ALL_TAGS" ]; then + echo "No valid tags found." + exit 1 + fi + + echo "::set-output name=tag::$ALL_TAGS" + # echo "tag=$ALL_TAGS" >> $GITHUB_STATE + + - name: Calculate next semver minor + id: calculate_next_version + run: | + LATEST_VERSION=${{ steps.get_all_releases.outputs.tag }} + + # strip 'v' prefix and split into major.minor + LATEST_VERSION=${LATEST_VERSION//v/} + IFS='.' read -r -a VERSION_PARTS <<< "$LATEST_VERSION" + + MAJOR=${VERSION_PARTS[0]} + MINOR=${VERSION_PARTS[1]} + + # increment the minor version + MINOR=$((MINOR + 1)) + + NEXT_VERSION="${MAJOR}.${MINOR}" + + echo "Major: $MAJOR" + echo "Minor: $MINOR" + + echo "Next version: $NEXT_VERSION" + echo "::set-output name=next_version::$NEXT_VERSION" + echo "::set-output name=next_version_major::$MAJOR" + echo "::set-output name=next_version_minor::$MINOR" \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 48e18637..80ac6cf0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,18 +2,19 @@ cmake_minimum_required(VERSION 3.21.1) option(ENABLE_VCPKG "Enable the vcpkg package manager" ON) option(MACOS_BUNDLE "The executable when built on macOS will be created as an application bundle" OFF) -set(EXPERIMENTAL_VERSION "" CACHE STRING "") # used by CI script to set experimental version -if (EXPERIMENTAL_VERSION) - add_definitions(-DEMULATOR_VERSION_MINOR=${EXPERIMENTAL_VERSION}) - execute_process( - COMMAND git log --format=%h -1 - WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} - OUTPUT_VARIABLE GIT_HASH - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - add_definitions(-DEMULATOR_HASH=${GIT_HASH}) -endif() +# used by CI script to set version: +set(EMULATOR_VERSION_MAJOR "0" CACHE STRING "") +set(EMULATOR_VERSION_MINOR "0" CACHE STRING "") +set(EMULATOR_VERSION_PATCH "0" CACHE STRING "") + +execute_process( + COMMAND git log --format=%h -1 + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} + OUTPUT_VARIABLE GIT_HASH + OUTPUT_STRIP_TRAILING_WHITESPACE +) +add_definitions(-DEMULATOR_HASH=${GIT_HASH}) if (ENABLE_VCPKG) # check if vcpkg is shallow and unshallow it if necessary @@ -62,6 +63,10 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) add_compile_definitions($<$<CONFIG:Debug>:CEMU_DEBUG_ASSERT>) # if build type is debug, set CEMU_DEBUG_ASSERT +add_definitions(-DEMULATOR_VERSION_MAJOR=${EMULATOR_VERSION_MAJOR}) +add_definitions(-DEMULATOR_VERSION_MINOR=${EMULATOR_VERSION_MINOR}) +add_definitions(-DEMULATOR_VERSION_PATCH=${EMULATOR_VERSION_PATCH}) + set_property(GLOBAL PROPERTY USE_FOLDERS ON) # enable link time optimization for release builds diff --git a/src/Cafe/HW/Latte/Core/LatteShader.cpp b/src/Cafe/HW/Latte/Core/LatteShader.cpp index b59702cd..77f16468 100644 --- a/src/Cafe/HW/Latte/Core/LatteShader.cpp +++ b/src/Cafe/HW/Latte/Core/LatteShader.cpp @@ -524,7 +524,7 @@ void LatteSHRC_UpdateGSBaseHash(uint8* geometryShaderPtr, uint32 geometryShaderS // update hash from geometry shader data uint64 gsHash1 = 0; uint64 gsHash2 = 0; - _calculateShaderProgramHash((uint32*)geometryShaderPtr, geometryShaderSize, &hashCacheVS, &gsHash1, &gsHash2); + _calculateShaderProgramHash((uint32*)geometryShaderPtr, geometryShaderSize, &hashCacheGS, &gsHash1, &gsHash2); // get geometry shader uint64 gsHash = gsHash1 + gsHash2; gsHash += (uint64)_activeVertexShader->ringParameterCount; diff --git a/src/Cafe/HW/Latte/Renderer/RendererShader.cpp b/src/Cafe/HW/Latte/Renderer/RendererShader.cpp index f66dc9f4..23c8d0ea 100644 --- a/src/Cafe/HW/Latte/Renderer/RendererShader.cpp +++ b/src/Cafe/HW/Latte/Renderer/RendererShader.cpp @@ -12,9 +12,9 @@ uint32 RendererShader::GeneratePrecompiledCacheId() v += (uint32)(*s); s++; } - v += (EMULATOR_VERSION_LEAD * 1000000u); - v += (EMULATOR_VERSION_MAJOR * 10000u); - v += (EMULATOR_VERSION_MINOR * 100u); + v += (EMULATOR_VERSION_MAJOR * 1000000u); + v += (EMULATOR_VERSION_MINOR * 10000u); + v += (EMULATOR_VERSION_PATCH * 100u); // settings that can influence shaders v += (uint32)g_current_game_profile->GetAccurateShaderMul() * 133; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index fb54a803..f464c7a3 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -125,7 +125,7 @@ std::vector<VulkanRenderer::DeviceInfo> VulkanRenderer::GetDevices() VkApplicationInfo app_info{}; app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; app_info.pApplicationName = EMULATOR_NAME; - app_info.applicationVersion = VK_MAKE_VERSION(EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR); + app_info.applicationVersion = VK_MAKE_VERSION(EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_PATCH); app_info.pEngineName = EMULATOR_NAME; app_info.engineVersion = app_info.applicationVersion; app_info.apiVersion = apiVersion; @@ -339,7 +339,7 @@ VulkanRenderer::VulkanRenderer() VkApplicationInfo app_info{}; app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; app_info.pApplicationName = EMULATOR_NAME; - app_info.applicationVersion = VK_MAKE_VERSION(EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR); + app_info.applicationVersion = VK_MAKE_VERSION(EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_PATCH); app_info.pEngineName = EMULATOR_NAME; app_info.engineVersion = app_info.applicationVersion; app_info.apiVersion = apiVersion; diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Spinlock.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Spinlock.cpp index 4c55c2b0..5201d441 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Spinlock.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Spinlock.cpp @@ -140,7 +140,7 @@ namespace coreinit // we are in single-core mode and the lock will never be released unless we let other threads resume work // to avoid an infinite loop we have no choice but to yield the thread even it is in an uninterruptible state if( !OSIsInterruptEnabled() ) - cemuLog_log(LogType::APIErrors, "OSUninterruptibleSpinLock_Acquire(): Lock is occupied which requires a wait but current thread is already in an uninterruptible state (Avoid cascaded OSDisableInterrupts and/or OSUninterruptibleSpinLock)"); + cemuLog_logOnce(LogType::APIErrors, "OSUninterruptibleSpinLock_Acquire(): Lock is occupied which requires a wait but current thread is already in an uninterruptible state (Avoid cascaded OSDisableInterrupts and/or OSUninterruptibleSpinLock)"); while (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread)) { OSYieldThread(); diff --git a/src/Common/version.h b/src/Common/version.h index 8c08f238..36925a52 100644 --- a/src/Common/version.h +++ b/src/Common/version.h @@ -1,36 +1,19 @@ #ifndef EMULATOR_NAME #define EMULATOR_NAME "Cemu" -#define EMULATOR_VERSION_LEAD 2 -#define EMULATOR_VERSION_MAJOR 0 -// the minor version is used for experimental builds to indicate the build index. Set by command line option from CI build script -// if zero, the version text will be constructed as LEAD.MAJOR, otherwise as LEAD.MAJOR-MINOR - -#if defined(EMULATOR_VERSION_MINOR) && EMULATOR_VERSION_MINOR == 0 #define EMULATOR_VERSION_SUFFIX "" -#else -#define EMULATOR_VERSION_SUFFIX " (experimental)" -#endif - -#ifndef EMULATOR_VERSION_MINOR -#define EMULATOR_VERSION_MINOR 0 -#endif #define _XSTRINGFY(s) _STRINGFY(s) #define _STRINGFY(s) #s -#if EMULATOR_VERSION_MINOR != 0 -#if defined(EMULATOR_HASH) && EMULATOR_VERSION_MINOR == 999999 -#define BUILD_VERSION_WITH_NAME_STRING (EMULATOR_NAME " " _XSTRINGFY(EMULATOR_VERSION_LEAD) "." _XSTRINGFY(EMULATOR_VERSION_MAJOR) "-" _XSTRINGFY(EMULATOR_HASH) EMULATOR_VERSION_SUFFIX) -#define BUILD_VERSION_STRING (_XSTRINGFY(EMULATOR_VERSION_LEAD) "." _XSTRINGFY(EMULATOR_VERSION_MAJOR) "-" _XSTRINGFY(EMULATOR_HASH) EMULATOR_VERSION_SUFFIX) +#if EMULATOR_VERSION_MAJOR != 0 +#define BUILD_VERSION_WITH_NAME_STRING (EMULATOR_NAME " " _XSTRINGFY(EMULATOR_VERSION_MAJOR) "." _XSTRINGFY(EMULATOR_VERSION_MINOR) EMULATOR_VERSION_SUFFIX) +#define BUILD_VERSION_STRING (_XSTRINGFY(EMULATOR_VERSION_MAJOR) "." _XSTRINGFY(EMULATOR_VERSION_MINOR) EMULATOR_VERSION_SUFFIX) #else -#define BUILD_VERSION_WITH_NAME_STRING (EMULATOR_NAME " " _XSTRINGFY(EMULATOR_VERSION_LEAD) "." _XSTRINGFY(EMULATOR_VERSION_MAJOR) "-" _XSTRINGFY(EMULATOR_VERSION_MINOR) EMULATOR_VERSION_SUFFIX) -#define BUILD_VERSION_STRING (_XSTRINGFY(EMULATOR_VERSION_LEAD) "." _XSTRINGFY(EMULATOR_VERSION_MAJOR) "-" _XSTRINGFY(EMULATOR_VERSION_MINOR) EMULATOR_VERSION_SUFFIX) -#endif -#else -#define BUILD_VERSION_STRING (_XSTRINGFY(EMULATOR_VERSION_LEAD) "." _XSTRINGFY(EMULATOR_VERSION_MAJOR) EMULATOR_VERSION_SUFFIX) -#define BUILD_VERSION_WITH_NAME_STRING (EMULATOR_NAME " " _XSTRINGFY(EMULATOR_VERSION_LEAD) "." _XSTRINGFY(EMULATOR_VERSION_MAJOR) EMULATOR_VERSION_SUFFIX) +// no version provided. Only show commit hash +#define BUILD_VERSION_STRING (_XSTRINGFY(EMULATOR_HASH) EMULATOR_VERSION_SUFFIX) +#define BUILD_VERSION_WITH_NAME_STRING (EMULATOR_NAME " " _XSTRINGFY(EMULATOR_HASH) EMULATOR_VERSION_SUFFIX) #endif #endif diff --git a/src/config/CemuConfig.cpp b/src/config/CemuConfig.cpp index 338392dd..e7920e84 100644 --- a/src/config/CemuConfig.cpp +++ b/src/config/CemuConfig.cpp @@ -38,6 +38,7 @@ void CemuConfig::Load(XMLConfigParser& parser) fullscreen_menubar = parser.get("fullscreen_menubar", false); feral_gamemode = parser.get("feral_gamemode", false); check_update = parser.get("check_update", check_update); + receive_untested_updates = parser.get("receive_untested_updates", check_update); save_screenshot = parser.get("save_screenshot", save_screenshot); did_show_vulkan_warning = parser.get("vk_warning", did_show_vulkan_warning); did_show_graphic_pack_download = parser.get("gp_download", did_show_graphic_pack_download); @@ -360,6 +361,7 @@ void CemuConfig::Save(XMLConfigParser& parser) config.set<bool>("fullscreen_menubar", fullscreen_menubar); config.set<bool>("feral_gamemode", feral_gamemode); config.set<bool>("check_update", check_update); + config.set<bool>("receive_untested_updates", receive_untested_updates); config.set<bool>("save_screenshot", save_screenshot); config.set<bool>("vk_warning", did_show_vulkan_warning); config.set<bool>("gp_download", did_show_graphic_pack_download); diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index 5db8f58c..e2fbb74c 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -413,7 +413,8 @@ struct CemuConfig Vector2i pad_size{ -1,-1 }; ConfigValue<bool> pad_maximized; - ConfigValue<bool> check_update{false}; + ConfigValue<bool> check_update{true}; + ConfigValue<bool> receive_untested_updates{false}; ConfigValue<bool> save_screenshot{true}; ConfigValue<bool> did_show_vulkan_warning{false}; diff --git a/src/config/LaunchSettings.cpp b/src/config/LaunchSettings.cpp index 1731f500..bf38b9cf 100644 --- a/src/config/LaunchSettings.cpp +++ b/src/config/LaunchSettings.cpp @@ -112,10 +112,10 @@ bool LaunchSettings::HandleCommandline(const std::vector<std::wstring>& args) { requireConsole(); std::string versionStr; -#if EMULATOR_VERSION_MINOR == 0 - versionStr = fmt::format("{}.{}{}", EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_SUFFIX); +#if EMULATOR_VERSION_PATCH == 0 + versionStr = fmt::format("{}.{}{}", EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_SUFFIX); #else - versionStr = fmt::format("{}.{}-{}{}", EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_SUFFIX); + versionStr = fmt::format("{}.{}-{}{}", EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_PATCH, EMULATOR_VERSION_SUFFIX); #endif std::cout << versionStr << std::endl; return false; // exit in main diff --git a/src/gui/CemuUpdateWindow.cpp b/src/gui/CemuUpdateWindow.cpp index 445c7c17..6a4e6885 100644 --- a/src/gui/CemuUpdateWindow.cpp +++ b/src/gui/CemuUpdateWindow.cpp @@ -116,9 +116,11 @@ bool CemuUpdateWindow::QueryUpdateInfo(std::string& downloadUrlOut, std::string& #elif BOOST_OS_MACOS urlStr.append("&platform=macos_bundle_x86"); #elif - #error Name for current platform is missing #endif + const auto& config = GetConfig(); + if(config.receive_untested_updates) + urlStr.append("&allowNewUpdates=1"); curl_easy_setopt(curl, CURLOPT_URL, urlStr.c_str()); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); diff --git a/src/gui/DownloadGraphicPacksWindow.cpp b/src/gui/DownloadGraphicPacksWindow.cpp index 03f102d2..9ea9e1dd 100644 --- a/src/gui/DownloadGraphicPacksWindow.cpp +++ b/src/gui/DownloadGraphicPacksWindow.cpp @@ -115,7 +115,7 @@ void DownloadGraphicPacksWindow::UpdateThread() curlDownloadFileState_t tempDownloadState; std::string queryUrl("https://cemu.info/api2/query_graphicpack_url.php?"); char temp[64]; - sprintf(temp, "version=%d.%d.%d", EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR); + sprintf(temp, "version=%d.%d.%d", EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_PATCH); 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) diff --git a/src/gui/GeneralSettings2.cpp b/src/gui/GeneralSettings2.cpp index 08395cd3..bd394479 100644 --- a/src/gui/GeneralSettings2.cpp +++ b/src/gui/GeneralSettings2.cpp @@ -141,49 +141,66 @@ wxPanel* GeneralSettings2::AddGeneralPage(wxNotebook* notebook) second_row->SetFlexibleDirection(wxBOTH); second_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); + sint32 checkboxCount = 0; + auto CountRowElement = [&]() + { + checkboxCount++; + if(checkboxCount != 2) + return; + second_row->AddSpacer(10); + checkboxCount = 0; + }; + + auto InsertEmptyRow = [&]() + { + while(checkboxCount != 0) + CountRowElement(); + second_row->AddSpacer(10); + second_row->AddSpacer(10); + second_row->AddSpacer(10); + }; + const int topflag = wxALIGN_CENTER_VERTICAL | wxALL; m_save_window_position_size = new wxCheckBox(box, wxID_ANY, _("Remember main window position")); m_save_window_position_size->SetToolTip(_("Restores the last known window position and size when starting Cemu")); second_row->Add(m_save_window_position_size, 0, topflag, 5); - second_row->AddSpacer(10); + CountRowElement(); + //second_row->AddSpacer(10); m_save_padwindow_position_size = new wxCheckBox(box, wxID_ANY, _("Remember pad window position")); m_save_padwindow_position_size->SetToolTip(_("Restores the last known pad window position and size when opening it")); second_row->Add(m_save_padwindow_position_size, 0, topflag, 5); + CountRowElement(); const int botflag = wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT | wxBOTTOM; m_discord_presence = new wxCheckBox(box, wxID_ANY, _("Discord Presence")); m_discord_presence->SetToolTip(_("Enables the Discord Rich Presence feature\nYou will also need to enable it in the Discord settings itself!")); second_row->Add(m_discord_presence, 0, botflag, 5); + CountRowElement(); #ifndef ENABLE_DISCORD_RPC m_discord_presence->Disable(); #endif - second_row->AddSpacer(10); + //second_row->AddSpacer(10); m_fullscreen_menubar = new wxCheckBox(box, wxID_ANY, _("Fullscreen menu bar")); m_fullscreen_menubar->SetToolTip(_("Displays the menu bar when Cemu is running in fullscreen mode and the mouse cursor is moved to the top")); second_row->Add(m_fullscreen_menubar, 0, botflag, 5); + CountRowElement(); - m_auto_update = new wxCheckBox(box, wxID_ANY, _("Automatically check for updates")); - m_auto_update->SetToolTip(_("Automatically checks for new cemu versions on startup")); - second_row->Add(m_auto_update, 0, botflag, 5); -#if BOOST_OS_LINUX - if (!std::getenv("APPIMAGE")) { - m_auto_update->Disable(); - } -#endif - second_row->AddSpacer(10); m_save_screenshot = new wxCheckBox(box, wxID_ANY, _("Save screenshot")); m_save_screenshot->SetToolTip(_("Pressing the screenshot key (F12) will save a screenshot directly to the screenshots folder")); second_row->Add(m_save_screenshot, 0, botflag, 5); + CountRowElement(); m_disable_screensaver = new wxCheckBox(box, wxID_ANY, _("Disable screen saver")); m_disable_screensaver->SetToolTip(_("Prevents the system from activating the screen saver or going to sleep while running a game.")); second_row->Add(m_disable_screensaver, 0, botflag, 5); + CountRowElement(); // Enable/disable feral interactive gamemode #if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE) m_feral_gamemode = new wxCheckBox(box, wxID_ANY, _("Enable Feral GameMode")); m_feral_gamemode->SetToolTip(_("Use FeralInteractive GameMode if installed.")); second_row->Add(m_feral_gamemode, 0, botflag, 5); + CountRowElement(); #endif // temporary workaround because feature crashes on macOS @@ -191,6 +208,22 @@ wxPanel* GeneralSettings2::AddGeneralPage(wxNotebook* notebook) m_disable_screensaver->Enable(false); #endif + // InsertEmptyRow(); + + m_auto_update = new wxCheckBox(box, wxID_ANY, _("Automatically check for updates")); + m_auto_update->SetToolTip(_("Automatically checks for new cemu versions on startup")); + second_row->Add(m_auto_update, 0, botflag, 5); + CountRowElement(); + + m_receive_untested_releases = new wxCheckBox(box, wxID_ANY, _("Receive untested updates")); + m_receive_untested_releases->SetToolTip(_("When checking for updates, include brand new and untested releases. These may contain bugs!")); + second_row->Add(m_receive_untested_releases, 0, botflag, 5); +#if BOOST_OS_LINUX + if (!std::getenv("APPIMAGE")) { + m_auto_update->Disable(); + } +#endif + box_sizer->Add(second_row, 0, wxEXPAND, 5); } @@ -1536,6 +1569,7 @@ void GeneralSettings2::ApplyConfig() m_fullscreen_menubar->SetValue(config.fullscreen_menubar); m_auto_update->SetValue(config.check_update); + m_receive_untested_releases->SetValue(config.receive_untested_updates); m_save_screenshot->SetValue(config.save_screenshot); m_disable_screensaver->SetValue(config.disable_screensaver); diff --git a/src/gui/GeneralSettings2.h b/src/gui/GeneralSettings2.h index a3429fa1..b1ab01e8 100644 --- a/src/gui/GeneralSettings2.h +++ b/src/gui/GeneralSettings2.h @@ -41,7 +41,7 @@ private: 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_auto_update, *m_receive_untested_releases, *m_save_screenshot; wxCheckBox* m_disable_screensaver; #if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE) wxCheckBox* m_feral_gamemode; diff --git a/src/resource/cemu.rc b/src/resource/cemu.rc index 860ca8fb..6f78bfc3 100644 --- a/src/resource/cemu.rc +++ b/src/resource/cemu.rc @@ -73,8 +73,8 @@ END #define str(s) #s VS_VERSION_INFO VERSIONINFO -FILEVERSION EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, 0 -PRODUCTVERSION EMULATOR_VERSION_LEAD, EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, 0 +FILEVERSION EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_PATCH, 0 +PRODUCTVERSION EMULATOR_VERSION_MAJOR, EMULATOR_VERSION_MINOR, EMULATOR_VERSION_PATCH, 0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -94,7 +94,7 @@ BEGIN VALUE "LegalCopyright", "Team Cemu" VALUE "OriginalFilename", "Cemu.exe" VALUE "ProductName", "Cemu" - VALUE "ProductVersion", xstr(EMULATOR_VERSION_LEAD) "." xstr(EMULATOR_VERSION_MAJOR) "." xstr(EMULATOR_VERSION_MINOR) EMULATOR_VERSION_SUFFIX "\0" + VALUE "ProductVersion", xstr(EMULATOR_VERSION_MAJOR) "." xstr(EMULATOR_VERSION_MINOR) "." xstr(EMULATOR_VERSION_PATCH) EMULATOR_VERSION_SUFFIX "\0" END END BLOCK "VarFileInfo" From 03484d214678cd5e442f196bbfc38b79c53509f9 Mon Sep 17 00:00:00 2001 From: Cemu-Language CI <github-actions@github.com> Date: Wed, 28 Aug 2024 09:05:50 +0000 Subject: [PATCH 177/299] Update translation files --- bin/resources/fr/cemu.mo | Bin 72178 -> 73978 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/bin/resources/fr/cemu.mo b/bin/resources/fr/cemu.mo index 8328991f01eb915b5ea722452ccefde985d6416c..f3f3b49871a8078edf991f90f995d71f14e80f94 100644 GIT binary patch delta 25041 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 delta 23379 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 From 9a53b19403d4fea5861bcdf115a673d801759245 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Wed, 28 Aug 2024 02:06:49 -0700 Subject: [PATCH 178/299] CI+build: Improve macOS builds (#1310) --- .github/workflows/build.yml | 20 +++++------- BUILD.md | 50 ++++++++++++++++++------------ CMakeLists.txt | 1 + dependencies/ih264d/CMakeLists.txt | 12 +++++-- src/asm/CMakeLists.txt | 12 +++++-- 5 files changed, 57 insertions(+), 38 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dd28ceb5..72bbcf52 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -176,7 +176,7 @@ jobs: path: ./bin/Cemu.exe build-macos: - runs-on: macos-12 + runs-on: macos-14 steps: - name: "Checkout repo" uses: actions/checkout@v4 @@ -198,17 +198,14 @@ jobs: - name: "Install system dependencies" run: | brew update - brew install llvm@15 ninja nasm automake libtool - brew install cmake ninja + brew install ninja nasm automake libtool - - name: "Build and install molten-vk" + - name: "Install molten-vk" run: | - git clone https://github.com/KhronosGroup/MoltenVK.git - cd MoltenVK - git checkout bf097edc74ec3b6dfafdcd5a38d3ce14b11952d6 - ./fetchDependencies --macos - make macos - make install + curl -L -O https://github.com/KhronosGroup/MoltenVK/releases/download/v1.2.9/MoltenVK-macos.tar + tar xf MoltenVK-macos.tar + sudo mkdir -p /usr/local/lib + sudo cp MoltenVK/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib /usr/local/lib - name: "Setup cmake" uses: jwlawson/actions-setup-cmake@v2 @@ -239,9 +236,8 @@ jobs: cd build cmake .. ${{ env.BUILD_FLAGS }} \ -DCMAKE_BUILD_TYPE=${{ env.BUILD_MODE }} \ + -DCMAKE_OSX_ARCHITECTURES=x86_64 \ -DMACOS_BUNDLE=ON \ - -DCMAKE_C_COMPILER=/usr/local/opt/llvm@15/bin/clang \ - -DCMAKE_CXX_COMPILER=/usr/local/opt/llvm@15/bin/clang++ \ -G Ninja - name: "Build Cemu" diff --git a/BUILD.md b/BUILD.md index 1e92527e..44d69c6c 100644 --- a/BUILD.md +++ b/BUILD.md @@ -16,11 +16,11 @@ - [Compiling Errors](#compiling-errors) - [Building Errors](#building-errors) - [macOS](#macos) - - [On Apple Silicon Macs, Rosetta 2 and the x86_64 version of Homebrew must be used](#on-apple-silicon-macs-rosetta-2-and-the-x86_64-version-of-homebrew-must-be-used) - [Installing brew](#installing-brew) - - [Installing Dependencies](#installing-dependencies) - - [Build Cemu using CMake and Clang](#build-cemu-using-cmake-and-clang) - - [Updating Cemu and source code](#updating-cemu-and-source-code) + - [Installing Tool Dependencies](#installing-tool-dependencies) + - [Installing Library Dependencies](#installing-library-dependencies) + - [Build Cemu using CMake](#build-cemu-using-cmake) +- [Updating Cemu and source code](#updating-cemu-and-source-code) ## Windows @@ -141,31 +141,41 @@ If you are getting a different error than any of the errors listed above, you ma ## macOS -To compile Cemu, a recent enough compiler and STL with C++20 support is required! LLVM 13 and -below, built in LLVM, and Xcode LLVM don't support the C++20 feature set required. The OpenGL graphics -API isn't support on macOS, Vulkan must be used. Additionally Vulkan must be used through the -Molten-VK compatibility layer - -### On Apple Silicon Macs, Rosetta 2 and the x86_64 version of Homebrew must be used - -You can skip this section if you have an Intel Mac. Every time you compile, you need to perform steps 2. - -1. `softwareupdate --install-rosetta` # Install Rosetta 2 if you don't have it. This only has to be done once -2. `arch -x86_64 zsh` # run an x64 shell +To compile Cemu, a recent enough compiler and STL with C++20 support is required! LLVM 13 and below +don't support the C++20 feature set required, so either install LLVM from Homebrew or make sure that +you have a recent enough version of Xcode. Xcode 15 is known to work. The OpenGL graphics API isn't +supported on macOS, so Vulkan must be used through the Molten-VK compatibility layer. ### Installing brew 1. `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"` -2. `eval "$(/usr/local/Homebrew/bin/brew shellenv)"` # set x86_64 brew env +2. Set up the Homebrew shell environment: + 1. **On an Intel Mac:** `eval "$(/usr/local/Homebrew/bin/brew shellenv)"` + 2. **On an Apple Silicon Mac:** eval `"$(/opt/homebrew/bin/brew shellenv)"` -### Installing Dependencies +### Installing Tool Dependencies -`brew install boost git cmake llvm ninja nasm molten-vk automake libtool` +The native versions of these can be used regardless of what type of Mac you have. + +`brew install git cmake ninja nasm automake libtool` + +### Installing Library Dependencies + +**On Apple Silicon Macs, Rosetta 2 and the x86_64 version of Homebrew must be used to install these dependencies:** +1. `softwareupdate --install-rosetta` # Install Rosetta 2 if you don't have it. This only has to be done once +2. `arch -x86_64 zsh` # run an x64 shell +3. `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"` +4. `eval "$(/usr/local/Homebrew/bin/brew shellenv)"` + +Then install the dependencies: + +`brew install boost molten-vk` + +### Build Cemu using CMake -### Build Cemu using CMake and Clang 1. `git clone --recursive https://github.com/cemu-project/Cemu` 2. `cd Cemu` -3. `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/local/opt/llvm/bin/clang -DCMAKE_CXX_COMPILER=/usr/local/opt/llvm/bin/clang++ -G Ninja` +3. `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_OSX_ARCHITECTURES=x86_64 -G Ninja` 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`. diff --git a/CMakeLists.txt b/CMakeLists.txt index 80ac6cf0..54e2012a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,6 +92,7 @@ endif() if (APPLE) enable_language(OBJC OBJCXX) + set(CMAKE_OSX_DEPLOYMENT_TARGET "12.0") endif() if (UNIX AND NOT APPLE) diff --git a/dependencies/ih264d/CMakeLists.txt b/dependencies/ih264d/CMakeLists.txt index d97d6dda..686a9d08 100644 --- a/dependencies/ih264d/CMakeLists.txt +++ b/dependencies/ih264d/CMakeLists.txt @@ -117,7 +117,13 @@ add_library (ih264d "decoder/ivd.h" ) -if (CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "amd64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64") +if (CMAKE_OSX_ARCHITECTURES) +set(IH264D_ARCHITECTURE ${CMAKE_OSX_ARCHITECTURES}) +else() +set(IH264D_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR}) +endif() + +if (IH264D_ARCHITECTURE STREQUAL "x86_64" OR IH264D_ARCHITECTURE STREQUAL "amd64" OR IH264D_ARCHITECTURE STREQUAL "AMD64") set(LIBAVCDEC_X86_INCLUDES "common/x86" "decoder/x86") include_directories("common/" "decoder/" ${LIBAVCDEC_X86_INCLUDES}) target_sources(ih264d PRIVATE @@ -140,7 +146,7 @@ target_sources(ih264d PRIVATE "decoder/x86/ih264d_function_selector_sse42.c" "decoder/x86/ih264d_function_selector_ssse3.c" ) -elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64") +elseif(IH264D_ARCHITECTURE STREQUAL "aarch64" OR IH264D_ARCHITECTURE STREQUAL "arm64") enable_language( C CXX ASM ) set(LIBAVCDEC_ARM_INCLUDES "common/armv8" "decoder/arm") include_directories("common/" "decoder/" ${LIBAVCDEC_ARM_INCLUDES}) @@ -178,7 +184,7 @@ target_sources(ih264d PRIVATE ) target_compile_options(ih264d PRIVATE -DARMV8) else() -message(FATAL_ERROR "ih264d unknown architecture: ${CMAKE_SYSTEM_PROCESSOR}") +message(FATAL_ERROR "ih264d unknown architecture: ${IH264D_ARCHITECTURE}") endif() if(MSVC) diff --git a/src/asm/CMakeLists.txt b/src/asm/CMakeLists.txt index 5d9f84c2..19a7ddd8 100644 --- a/src/asm/CMakeLists.txt +++ b/src/asm/CMakeLists.txt @@ -1,6 +1,12 @@ project(CemuAsm C) -if (CMAKE_SYSTEM_PROCESSOR MATCHES "(x86)|(X86)|(amd64)|(AMD64)") +if (CMAKE_OSX_ARCHITECTURES) + set(CEMU_ASM_ARCHITECTURE ${CMAKE_OSX_ARCHITECTURES}) +else() + set(CEMU_ASM_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR}) +endif() + +if (CEMU_ASM_ARCHITECTURE MATCHES "(x86)|(X86)|(amd64)|(AMD64)") if (WIN32) @@ -40,8 +46,8 @@ if (CMAKE_SYSTEM_PROCESSOR MATCHES "(x86)|(X86)|(amd64)|(AMD64)") endif() -elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "(aarch64)|(AARCH64)") +elseif(CEMU_ASM_ARCHITECTURE MATCHES "(aarch64)|(AARCH64)|(arm64)|(ARM64)") add_library(CemuAsm stub.cpp) else() - message(STATUS "CemuAsm - Unsupported arch: ${CMAKE_SYSTEM_PROCESSOR}") + message(STATUS "CemuAsm - Unsupported arch: ${CEMU_ASM_ARCHITECTURE}") endif() From b06990607d31fc204d82be2b062df4c7edd6e299 Mon Sep 17 00:00:00 2001 From: Joshua de Reeper <joshua@dereeper.co.nz> Date: Mon, 2 Sep 2024 15:20:16 +0100 Subject: [PATCH 179/299] nsysnet: Avoid crash on NULL timeout in select (#1324) --- src/Cafe/OS/libs/nsysnet/nsysnet.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Cafe/OS/libs/nsysnet/nsysnet.cpp b/src/Cafe/OS/libs/nsysnet/nsysnet.cpp index dd7c9189..5a0ddc59 100644 --- a/src/Cafe/OS/libs/nsysnet/nsysnet.cpp +++ b/src/Cafe/OS/libs/nsysnet/nsysnet.cpp @@ -1210,6 +1210,14 @@ void nsysnetExport_select(PPCInterpreter_t* hCPU) timeval tv = { 0 }; + if (timeOut == NULL) + { + // return immediately + cemuLog_log(LogType::Socket, "select returned immediately because of null timeout"); + osLib_returnFromFunction(hCPU, 0); + return; + } + uint64 msTimeout = (_swapEndianU32(timeOut->tv_usec) / 1000) + (_swapEndianU32(timeOut->tv_sec) * 1000); uint32 startTime = GetTickCount(); while (true) From 0d8fd7c0dc2ea6ff750f4ef5b193ebc6388d31d0 Mon Sep 17 00:00:00 2001 From: MoonlightWave-12 <123384363+MoonlightWave-12@users.noreply.github.com> Date: Mon, 2 Sep 2024 21:22:38 +0200 Subject: [PATCH 180/299] appimage: Do not copy `libstdc++.so.6` to `usr/lib/` (#1319) --- dist/linux/appimage.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/dist/linux/appimage.sh b/dist/linux/appimage.sh index e9081521..b66326d7 100755 --- a/dist/linux/appimage.sh +++ b/dist/linux/appimage.sh @@ -50,7 +50,6 @@ fi echo "Cemu Version Cemu-${GITVERSION}" rm AppDir/usr/lib/libwayland-client.so.0 -cp /lib/x86_64-linux-gnu/libstdc++.so.6 AppDir/usr/lib/ echo -e "export LC_ALL=C\nexport FONTCONFIG_PATH=/etc/fonts" >> AppDir/apprun-hooks/linuxdeploy-plugin-gtk.sh VERSION="${GITVERSION}" ./mkappimage.AppImage --appimage-extract-and-run "${GITHUB_WORKSPACE}"/AppDir From ba54d1540cf6103728abe606223b299d284a6df8 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 8 Sep 2024 17:21:20 +0200 Subject: [PATCH 181/299] Fix "Receive untested updates" option not being synced to config --- src/gui/GeneralSettings2.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/GeneralSettings2.cpp b/src/gui/GeneralSettings2.cpp index bd394479..eaada7cb 100644 --- a/src/gui/GeneralSettings2.cpp +++ b/src/gui/GeneralSettings2.cpp @@ -932,6 +932,7 @@ void GeneralSettings2::StoreConfig() config.fullscreen_menubar = m_fullscreen_menubar->IsChecked(); config.check_update = m_auto_update->IsChecked(); config.save_screenshot = m_save_screenshot->IsChecked(); + config.receive_untested_updates = m_receive_untested_releases->IsChecked(); #if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE) config.feral_gamemode = m_feral_gamemode->IsChecked(); #endif From 1a4d9660e756f52a01589add71df619361543a2f Mon Sep 17 00:00:00 2001 From: Cemu-Language CI <github-actions@github.com> Date: Sun, 8 Sep 2024 15:40:13 +0000 Subject: [PATCH 182/299] Update translation files --- bin/resources/ko/cemu.mo | Bin 70573 -> 71668 bytes bin/resources/zh/cemu.mo | Bin 58070 -> 60704 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/bin/resources/ko/cemu.mo b/bin/resources/ko/cemu.mo index b812df970a34b41a6d2407c9ec4771adadd6a5db..5ea5e1da1a39c3ac7d15b9b270c5a01a89a4ed07 100644 GIT binary patch delta 22630 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 delta 21570 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 diff --git a/bin/resources/zh/cemu.mo b/bin/resources/zh/cemu.mo index 69e06e572cbc240cd3fa396bb0fb323e9dfb62cd..3e6369713ad379e558631b4db68b02fde8dab833 100644 GIT binary patch delta 18541 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 delta 16136 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 From a05bdb172d198904a76b4b166b43fb31a94a0dd3 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Sun, 15 Sep 2024 20:23:11 +0200 Subject: [PATCH 183/299] Vulkan: Add explicit synchronization on frame boundaries (#1290) --- .../Latte/Renderer/Vulkan/SwapchainInfoVk.cpp | 34 ++++++++- .../Latte/Renderer/Vulkan/SwapchainInfoVk.h | 8 ++ src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h | 3 + .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 73 ++++++++++++++++++- .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 4 +- 5 files changed, 119 insertions(+), 3 deletions(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp index 75ff02ba..56a3ab12 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp @@ -146,8 +146,17 @@ void SwapchainInfoVk::Create() UnrecoverableError("Failed to create semaphore for swapchain acquire"); } + VkFenceCreateInfo fenceInfo = {}; + fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + result = vkCreateFence(m_logicalDevice, &fenceInfo, nullptr, &m_imageAvailableFence); + if (result != VK_SUCCESS) + UnrecoverableError("Failed to create fence for swapchain"); + m_acquireIndex = 0; hasDefinedSwapchainImage = false; + + m_queueDepth = 0; } void SwapchainInfoVk::Cleanup() @@ -177,6 +186,12 @@ void SwapchainInfoVk::Cleanup() m_swapchainFramebuffers.clear(); + if (m_imageAvailableFence) + { + WaitAvailableFence(); + vkDestroyFence(m_logicalDevice, m_imageAvailableFence, nullptr); + m_imageAvailableFence = nullptr; + } if (m_swapchain) { vkDestroySwapchainKHR(m_logicalDevice, m_swapchain, nullptr); @@ -189,6 +204,18 @@ bool SwapchainInfoVk::IsValid() const return m_swapchain && !m_acquireSemaphores.empty(); } +void SwapchainInfoVk::WaitAvailableFence() +{ + if(m_awaitableFence != VK_NULL_HANDLE) + vkWaitForFences(m_logicalDevice, 1, &m_awaitableFence, VK_TRUE, UINT64_MAX); + m_awaitableFence = VK_NULL_HANDLE; +} + +void SwapchainInfoVk::ResetAvailableFence() const +{ + vkResetFences(m_logicalDevice, 1, &m_imageAvailableFence); +} + VkSemaphore SwapchainInfoVk::ConsumeAcquireSemaphore() { VkSemaphore ret = m_currentSemaphore; @@ -198,8 +225,10 @@ VkSemaphore SwapchainInfoVk::ConsumeAcquireSemaphore() bool SwapchainInfoVk::AcquireImage() { + ResetAvailableFence(); + VkSemaphore acquireSemaphore = m_acquireSemaphores[m_acquireIndex]; - VkResult result = vkAcquireNextImageKHR(m_logicalDevice, m_swapchain, 1'000'000'000, acquireSemaphore, nullptr, &swapchainImageIndex); + VkResult result = vkAcquireNextImageKHR(m_logicalDevice, m_swapchain, 1'000'000'000, acquireSemaphore, m_imageAvailableFence, &swapchainImageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) m_shouldRecreate = true; if (result == VK_TIMEOUT) @@ -216,6 +245,7 @@ bool SwapchainInfoVk::AcquireImage() return false; } m_currentSemaphore = acquireSemaphore; + m_awaitableFence = m_imageAvailableFence; m_acquireIndex = (m_acquireIndex + 1) % m_swapchainImages.size(); return true; @@ -319,6 +349,7 @@ VkExtent2D SwapchainInfoVk::ChooseSwapExtent(const VkSurfaceCapabilitiesKHR& cap VkPresentModeKHR SwapchainInfoVk::ChoosePresentMode(const std::vector<VkPresentModeKHR>& modes) { + m_maxQueued = 0; const auto vsyncState = (VSync)GetConfig().vsync.GetValue(); if (vsyncState == VSync::MAILBOX) { @@ -345,6 +376,7 @@ VkPresentModeKHR SwapchainInfoVk::ChoosePresentMode(const std::vector<VkPresentM return VK_PRESENT_MODE_FIFO_KHR; } + m_maxQueued = 1; return VK_PRESENT_MODE_FIFO_KHR; } diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.h b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.h index ceffab41..7023f291 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.h @@ -26,6 +26,9 @@ struct SwapchainInfoVk bool IsValid() const; + void WaitAvailableFence(); + void ResetAvailableFence() const; + bool AcquireImage(); // retrieve semaphore of last acquire for submitting a wait operation // only one wait operation must be submitted per acquire (which submits a single signal operation) @@ -68,6 +71,9 @@ struct SwapchainInfoVk VkSwapchainKHR m_swapchain{}; Vector2i m_desiredExtent{}; uint32 swapchainImageIndex = (uint32)-1; + uint64 m_presentId = 1; + uint64 m_queueDepth = 0; // number of frames with pending presentation requests + uint64 m_maxQueued = 0; // the maximum number of frames with presentation requests. // swapchain image ringbuffer (indexed by swapchainImageIndex) @@ -81,6 +87,8 @@ struct SwapchainInfoVk private: uint32 m_acquireIndex = 0; std::vector<VkSemaphore> m_acquireSemaphores; // indexed by m_acquireIndex + VkFence m_imageAvailableFence{}; + VkFence m_awaitableFence = VK_NULL_HANDLE; VkSemaphore m_currentSemaphore = VK_NULL_HANDLE; std::array<uint32, 2> m_swapchainQueueFamilyIndices; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h index 0489bb4e..6bde2a0b 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h @@ -188,6 +188,9 @@ VKFUNC_DEVICE(vkCmdPipelineBarrier2KHR); VKFUNC_DEVICE(vkCmdBeginRenderingKHR); VKFUNC_DEVICE(vkCmdEndRenderingKHR); +// khr_present_wait +VKFUNC_DEVICE(vkWaitForPresentKHR); + // transform feedback extension VKFUNC_DEVICE(vkCmdBindTransformFeedbackBuffersEXT); VKFUNC_DEVICE(vkCmdBeginTransformFeedbackEXT); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index f464c7a3..12d1d975 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -47,7 +47,9 @@ const std::vector<const char*> kOptionalDeviceExtensions = VK_EXT_FILTER_CUBIC_EXTENSION_NAME, // not supported by any device yet VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME, VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME, - VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME + VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME, + VK_KHR_PRESENT_WAIT_EXTENSION_NAME, + VK_KHR_PRESENT_ID_EXTENSION_NAME }; const std::vector<const char*> kRequiredDeviceExtensions = @@ -252,12 +254,24 @@ void VulkanRenderer::GetDeviceFeatures() pcc.pNext = prevStruct; prevStruct = &pcc; + VkPhysicalDevicePresentIdFeaturesKHR pidf{}; + pidf.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_FEATURES_KHR; + pidf.pNext = prevStruct; + prevStruct = &pidf; + + VkPhysicalDevicePresentWaitFeaturesKHR pwf{}; + pwf.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_FEATURES_KHR; + pwf.pNext = prevStruct; + prevStruct = &pwf; + VkPhysicalDeviceFeatures2 physicalDeviceFeatures2{}; physicalDeviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; physicalDeviceFeatures2.pNext = prevStruct; vkGetPhysicalDeviceFeatures2(m_physicalDevice, &physicalDeviceFeatures2); + cemuLog_log(LogType::Force, "Vulkan: present_wait extension: {}", (pwf.presentWait && pidf.presentId) ? "supported" : "unsupported"); + /* Get Vulkan device properties and limits */ VkPhysicalDeviceFloatControlsPropertiesKHR pfcp{}; prevStruct = nullptr; @@ -490,6 +504,24 @@ VulkanRenderer::VulkanRenderer() customBorderColorFeature.customBorderColors = VK_TRUE; customBorderColorFeature.customBorderColorWithoutFormat = VK_TRUE; } + // enable VK_KHR_present_id + VkPhysicalDevicePresentIdFeaturesKHR presentIdFeature{}; + if(m_featureControl.deviceExtensions.present_wait) + { + presentIdFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_FEATURES_KHR; + presentIdFeature.pNext = deviceExtensionFeatures; + deviceExtensionFeatures = &presentIdFeature; + presentIdFeature.presentId = VK_TRUE; + } + // enable VK_KHR_present_wait + VkPhysicalDevicePresentWaitFeaturesKHR presentWaitFeature{}; + if(m_featureControl.deviceExtensions.present_wait) + { + presentWaitFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_FEATURES_KHR; + presentWaitFeature.pNext = deviceExtensionFeatures; + deviceExtensionFeatures = &presentWaitFeature; + presentWaitFeature.presentWait = VK_TRUE; + } std::vector<const char*> used_extensions; VkDeviceCreateInfo createInfo = CreateDeviceCreateInfo(queueCreateInfos, deviceFeatures, deviceExtensionFeatures, used_extensions); @@ -1047,6 +1079,10 @@ VkDeviceCreateInfo VulkanRenderer::CreateDeviceCreateInfo(const std::vector<VkDe used_extensions.emplace_back(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); if (m_featureControl.deviceExtensions.shader_float_controls) used_extensions.emplace_back(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME); + if (m_featureControl.deviceExtensions.present_wait) + used_extensions.emplace_back(VK_KHR_PRESENT_ID_EXTENSION_NAME); + if (m_featureControl.deviceExtensions.present_wait) + used_extensions.emplace_back(VK_KHR_PRESENT_WAIT_EXTENSION_NAME); VkDeviceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; @@ -1144,6 +1180,7 @@ bool VulkanRenderer::CheckDeviceExtensionSupport(const VkPhysicalDevice device, info.deviceExtensions.shader_float_controls = isExtensionAvailable(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME); info.deviceExtensions.dynamic_rendering = false; // isExtensionAvailable(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); // dynamic rendering doesn't provide any benefits for us right now. Driver implementations are very unoptimized as of Feb 2022 + info.deviceExtensions.present_wait = isExtensionAvailable(VK_KHR_PRESENT_WAIT_EXTENSION_NAME) && isExtensionAvailable(VK_KHR_PRESENT_ID_EXTENSION_NAME); // check for framedebuggers info.debugMarkersSupported = false; @@ -2695,11 +2732,21 @@ void VulkanRenderer::SwapBuffer(bool mainWindow) ClearColorImageRaw(chainInfo.m_swapchainImages[chainInfo.swapchainImageIndex], 0, 0, clearColor, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); } + const size_t currentFrameCmdBufferID = GetCurrentCommandBufferId(); + VkSemaphore presentSemaphore = chainInfo.m_presentSemaphores[chainInfo.swapchainImageIndex]; SubmitCommandBuffer(presentSemaphore); // submit all command and signal semaphore cemu_assert_debug(m_numSubmittedCmdBuffers > 0); + // wait for the previous frame to finish rendering + WaitCommandBufferFinished(m_commandBufferIDOfPrevFrame); + m_commandBufferIDOfPrevFrame = currentFrameCmdBufferID; + + chainInfo.WaitAvailableFence(); + + VkPresentIdKHR presentId = {}; + VkPresentInfoKHR presentInfo = {}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.swapchainCount = 1; @@ -2709,6 +2756,24 @@ void VulkanRenderer::SwapBuffer(bool mainWindow) presentInfo.waitSemaphoreCount = 1; presentInfo.pWaitSemaphores = &presentSemaphore; + // if present_wait is available and enabled, add frame markers to present requests + // and limit the number of queued present operations + if (m_featureControl.deviceExtensions.present_wait && chainInfo.m_maxQueued > 0) + { + presentId.sType = VK_STRUCTURE_TYPE_PRESENT_ID_KHR; + presentId.swapchainCount = 1; + presentId.pPresentIds = &chainInfo.m_presentId; + + presentInfo.pNext = &presentId; + + if(chainInfo.m_queueDepth >= chainInfo.m_maxQueued) + { + uint64 waitFrameId = chainInfo.m_presentId - chainInfo.m_queueDepth; + vkWaitForPresentKHR(m_logicalDevice, chainInfo.m_swapchain, waitFrameId, 40'000'000); + chainInfo.m_queueDepth--; + } + } + VkResult result = vkQueuePresentKHR(m_presentQueue, &presentInfo); if (result < 0 && result != VK_ERROR_OUT_OF_DATE_KHR) { @@ -2717,6 +2782,12 @@ void VulkanRenderer::SwapBuffer(bool mainWindow) if(result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) chainInfo.m_shouldRecreate = true; + if(result >= 0) + { + chainInfo.m_queueDepth++; + chainInfo.m_presentId++; + } + chainInfo.hasDefinedSwapchainImage = false; chainInfo.swapchainImageIndex = -1; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index 6df53da4..ce97b5e9 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -450,6 +450,7 @@ private: bool synchronization2 = false; // VK_KHR_synchronization2 bool dynamic_rendering = false; // VK_KHR_dynamic_rendering bool shader_float_controls = false; // VK_KHR_shader_float_controls + bool present_wait = false; // VK_KHR_present_wait }deviceExtensions; struct @@ -457,7 +458,7 @@ private: bool shaderRoundingModeRTEFloat32{ false }; }shaderFloatControls; // from VK_KHR_shader_float_controls - struct + struct { bool debug_utils = false; // VK_EXT_DEBUG_UTILS }instanceExtensions; @@ -635,6 +636,7 @@ private: size_t m_commandBufferIndex = 0; // current buffer being filled size_t m_commandBufferSyncIndex = 0; // latest buffer that finished execution (updated on submit) + size_t m_commandBufferIDOfPrevFrame = 0; std::array<VkFence, kCommandBufferPoolSize> m_cmd_buffer_fences; std::array<VkCommandBuffer, kCommandBufferPoolSize> m_commandBuffers; std::array<VkSemaphore, kCommandBufferPoolSize> m_commandBufferSemaphores; From adffd53dbdc86f54803e79dcf06004e688e3b19d Mon Sep 17 00:00:00 2001 From: Andrea Toska <toskaandrea@gmail.com> Date: Mon, 16 Sep 2024 12:40:38 +0200 Subject: [PATCH 184/299] boss: Fix BOSS not honoring the proxy_server setting (#1344) --- src/Cafe/IOSU/legacy/iosu_boss.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Cafe/IOSU/legacy/iosu_boss.cpp b/src/Cafe/IOSU/legacy/iosu_boss.cpp index 760e5b66..7ab25f68 100644 --- a/src/Cafe/IOSU/legacy/iosu_boss.cpp +++ b/src/Cafe/IOSU/legacy/iosu_boss.cpp @@ -137,6 +137,10 @@ namespace iosu this->task_settings.taskType = settings->taskType; curl = std::shared_ptr<CURL>(curl_easy_init(), curl_easy_cleanup); + if(GetConfig().proxy_server.GetValue() != "") + { + curl_easy_setopt(curl.get(), CURLOPT_PROXY, GetConfig().proxy_server.GetValue().c_str()); + } } }; From 8508c625407e80a5a7fcb9cf02c5355d018ff64b Mon Sep 17 00:00:00 2001 From: capitalistspz <keipitalists@proton.me> Date: Tue, 17 Sep 2024 01:00:26 +0100 Subject: [PATCH 185/299] Various smaller code improvements (#1343) --- CMakeLists.txt | 2 +- src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.cpp | 4 +- src/Cafe/OS/libs/nsyshid/Infinity.cpp | 6 +- src/Cafe/OS/libs/ntag/ntag.cpp | 2 +- src/Cafe/OS/libs/snd_core/ax_out.cpp | 8 +- src/Common/MemPtr.h | 160 ++++++++++++------- src/Common/precompiled.h | 48 +++--- src/gui/CemuApp.cpp | 3 + src/util/CMakeLists.txt | 1 - src/util/ThreadPool/ThreadPool.cpp | 0 src/util/containers/robin_hood.h | 2 +- src/util/crypto/crc32.cpp | 55 ++----- src/util/crypto/crc32.h | 4 +- 13 files changed, 153 insertions(+), 142 deletions(-) delete mode 100644 src/util/ThreadPool/ThreadPool.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 54e2012a..5b5cff6c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -222,7 +222,7 @@ if (ENABLE_CUBEB) option(BUILD_TOOLS "" OFF) option(BUNDLE_SPEEX "" OFF) set(USE_WINMM OFF CACHE BOOL "") - add_subdirectory("dependencies/cubeb" EXCLUDE_FROM_ALL) + add_subdirectory("dependencies/cubeb" EXCLUDE_FROM_ALL SYSTEM) set_property(TARGET cubeb PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>") add_library(cubeb::cubeb ALIAS cubeb) endif() diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.cpp index 309394e6..c87cbd39 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.cpp +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_OfflineDB.cpp @@ -112,7 +112,7 @@ namespace nn nnResult _Async_OfflineDB_DownloadPostDataListParam_DownloadPostDataList(coreinit::OSEvent* event, DownloadedTopicData* downloadedTopicData, DownloadedPostData* downloadedPostData, uint32be* postCountOut, uint32 maxCount, DownloadPostDataListParam* param) { - scope_exit _se([&](){coreinit::OSSignalEvent(event);}); + stdx::scope_exit _se([&](){coreinit::OSSignalEvent(event);}); uint64 titleId = CafeSystem::GetForegroundTitleId(); @@ -184,7 +184,7 @@ namespace nn nnResult _Async_OfflineDB_DownloadPostDataListParam_DownloadExternalImageData(coreinit::OSEvent* event, DownloadedDataBase* _this, void* imageDataOut, uint32be* imageSizeOut, uint32 maxSize) { - scope_exit _se([&](){coreinit::OSSignalEvent(event);}); + stdx::scope_exit _se([&](){coreinit::OSSignalEvent(event);}); if (!_this->TestFlags(_this, DownloadedDataBase::FLAGS::HAS_EXTERNAL_IMAGE)) return OLV_RESULT_MISSING_DATA; diff --git a/src/Cafe/OS/libs/nsyshid/Infinity.cpp b/src/Cafe/OS/libs/nsyshid/Infinity.cpp index ab44ef4a..ac793109 100644 --- a/src/Cafe/OS/libs/nsyshid/Infinity.cpp +++ b/src/Cafe/OS/libs/nsyshid/Infinity.cpp @@ -1017,11 +1017,7 @@ namespace nsyshid std::array<uint8, 16> InfinityUSB::GenerateInfinityFigureKey(const std::vector<uint8>& sha1Data) { std::array<uint8, 20> digest = {}; - SHA_CTX ctx; - SHA1_Init(&ctx); - SHA1_Update(&ctx, sha1Data.data(), sha1Data.size()); - SHA1_Final(digest.data(), &ctx); - OPENSSL_cleanse(&ctx, sizeof(ctx)); + SHA1(sha1Data.data(), sha1Data.size(), digest.data()); // Infinity AES keys are the first 16 bytes of the SHA1 Digest, every set of 4 bytes need to be // reversed due to endianness std::array<uint8, 16> key = {}; diff --git a/src/Cafe/OS/libs/ntag/ntag.cpp b/src/Cafe/OS/libs/ntag/ntag.cpp index 24617791..7c95a1a1 100644 --- a/src/Cafe/OS/libs/ntag/ntag.cpp +++ b/src/Cafe/OS/libs/ntag/ntag.cpp @@ -509,7 +509,7 @@ namespace ntag noftHeader->writeCount = _swapEndianU16(_swapEndianU16(noftHeader->writeCount) + 1); } - memcpy(decryptedBuffer + 0x20, noftHeader, sizeof(noftHeader)); + memcpy(decryptedBuffer + 0x20, noftHeader, sizeof(NTAGNoftHeader)); memcpy(decryptedBuffer + _swapEndianU16(rwHeader->offset), data, dataSize); // Encrypt diff --git a/src/Cafe/OS/libs/snd_core/ax_out.cpp b/src/Cafe/OS/libs/snd_core/ax_out.cpp index 68b05165..40b9c643 100644 --- a/src/Cafe/OS/libs/snd_core/ax_out.cpp +++ b/src/Cafe/OS/libs/snd_core/ax_out.cpp @@ -522,10 +522,10 @@ namespace snd_core // called periodically to check for AX updates void AXOut_update() { - constexpr auto kTimeout = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::milliseconds(((IAudioAPI::kBlockCount * 3) / 4) * (AX_FRAMES_PER_GROUP * 3))); - constexpr auto kWaitDuration = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::milliseconds(3)); - constexpr auto kWaitDurationFast = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::microseconds(2900)); - constexpr auto kWaitDurationMinimum = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::microseconds(1700)); + constexpr static auto kTimeout = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::milliseconds(((IAudioAPI::kBlockCount * 3) / 4) * (AX_FRAMES_PER_GROUP * 3))); + constexpr static auto kWaitDuration = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::milliseconds(3)); + constexpr static auto kWaitDurationFast = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::microseconds(2900)); + constexpr static auto kWaitDurationMinimum = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::microseconds(1700)); // if we haven't buffered any blocks, we will wait less time than usual bool additional_blocks_required = false; diff --git a/src/Common/MemPtr.h b/src/Common/MemPtr.h index 142da7e4..7825e4d5 100644 --- a/src/Common/MemPtr.h +++ b/src/Common/MemPtr.h @@ -4,7 +4,7 @@ using MPTR = uint32; // generic address in PowerPC memory space -#define MPTR_NULL (0) +#define MPTR_NULL (0) using VAddr = uint32; // virtual address using PAddr = uint32; // physical address @@ -14,137 +14,175 @@ extern uint8* PPCInterpreterGetStackPointer(); extern uint8* PPCInterpreter_PushAndReturnStackPointer(sint32 offset); extern void PPCInterpreterModifyStackPointer(sint32 offset); -class MEMPTRBase {}; +class MEMPTRBase +{ +}; -template <typename T> +template<typename T> class MEMPTR : MEMPTRBase { -public: - constexpr MEMPTR() - : m_value(0) { } + public: + constexpr MEMPTR() noexcept + : m_value(0) {} - explicit constexpr MEMPTR(uint32 offset) - : m_value(offset) { } + explicit constexpr MEMPTR(uint32 offset) noexcept + : m_value(offset) {} - explicit constexpr MEMPTR(const uint32be& offset) - : m_value(offset) { } + explicit constexpr MEMPTR(const uint32be& offset) noexcept + : m_value(offset) {} - constexpr MEMPTR(std::nullptr_t) - : m_value(0) { } + constexpr MEMPTR(std::nullptr_t) noexcept + : m_value(0) {} - MEMPTR(T* ptr) + MEMPTR(T* ptr) noexcept { if (ptr == nullptr) m_value = 0; else - { - cemu_assert_debug((uint8*)ptr >= memory_base && (uint8*)ptr <= memory_base + 0x100000000); - m_value = (uint32)((uintptr_t)ptr - (uintptr_t)memory_base); - } + { + cemu_assert_debug((uint8*)ptr >= memory_base && (uint8*)ptr <= memory_base + 0x100000000); + m_value = (uint32)((uintptr_t)ptr - (uintptr_t)memory_base); + } } - constexpr MEMPTR(const MEMPTR& memptr) - : m_value(memptr.m_value) { } + constexpr MEMPTR(const MEMPTR&) noexcept = default; - constexpr MEMPTR& operator=(const MEMPTR& memptr) - { - m_value = memptr.m_value; - return *this; - } + constexpr MEMPTR& operator=(const MEMPTR&) noexcept = default; - constexpr MEMPTR& operator=(const uint32& offset) + constexpr MEMPTR& operator=(const uint32& offset) noexcept { m_value = offset; return *this; } - constexpr MEMPTR& operator=(const std::nullptr_t rhs) + constexpr MEMPTR& operator=(std::nullptr_t) noexcept { m_value = 0; return *this; } - MEMPTR& operator=(T* ptr) + MEMPTR& operator=(T* ptr) noexcept { - if (ptr == nullptr) + if (ptr == nullptr) m_value = 0; else - { - cemu_assert_debug((uint8*)ptr >= memory_base && (uint8*)ptr <= memory_base + 0x100000000); - m_value = (uint32)((uintptr_t)ptr - (uintptr_t)memory_base); - } + { + cemu_assert_debug((uint8*)ptr >= memory_base && (uint8*)ptr <= memory_base + 0x100000000); + m_value = (uint32)((uintptr_t)ptr - (uintptr_t)memory_base); + } return *this; } - bool atomic_compare_exchange(T* comparePtr, T* newPtr) + bool atomic_compare_exchange(T* comparePtr, T* newPtr) noexcept { MEMPTR<T> mp_compare = comparePtr; MEMPTR<T> mp_new = newPtr; - std::atomic<uint32be>* thisValueAtomic = (std::atomic<uint32be>*)&m_value; + auto* thisValueAtomic = reinterpret_cast<std::atomic<uint32be>*>(&m_value); return thisValueAtomic->compare_exchange_strong(mp_compare.m_value, mp_new.m_value); } - explicit constexpr operator bool() const noexcept { return m_value != 0; } - - constexpr operator T*() const noexcept { return GetPtr(); } // allow implicit cast to wrapped pointer type + explicit constexpr operator bool() const noexcept + { + return m_value != 0; + } + // allow implicit cast to wrapped pointer type + constexpr operator T*() const noexcept + { + return GetPtr(); + } - template <typename X> - explicit operator MEMPTR<X>() const { return MEMPTR<X>(this->m_value); } + template<typename X> + explicit operator MEMPTR<X>() const noexcept + { + return MEMPTR<X>(this->m_value); + } - MEMPTR operator+(const MEMPTR& ptr) { return MEMPTR(this->GetMPTR() + ptr.GetMPTR()); } - MEMPTR operator-(const MEMPTR& ptr) { return MEMPTR(this->GetMPTR() - ptr.GetMPTR()); } + MEMPTR operator+(const MEMPTR& ptr) noexcept + { + return MEMPTR(this->GetMPTR() + ptr.GetMPTR()); + } + MEMPTR operator-(const MEMPTR& ptr) noexcept + { + return MEMPTR(this->GetMPTR() - ptr.GetMPTR()); + } - MEMPTR operator+(sint32 v) + MEMPTR operator+(sint32 v) noexcept { // pointer arithmetic return MEMPTR(this->GetMPTR() + v * 4); } - MEMPTR operator-(sint32 v) + MEMPTR operator-(sint32 v) noexcept { // pointer arithmetic return MEMPTR(this->GetMPTR() - v * 4); } - MEMPTR& operator+=(sint32 v) + MEMPTR& operator+=(sint32 v) noexcept { m_value += v * sizeof(T); return *this; } - template <class Q = T> - typename std::enable_if<!std::is_same<Q, void>::value, Q>::type& - operator*() const { return *GetPtr(); } + template<typename Q = T> + std::enable_if_t<!std::is_same_v<Q, void>, Q>& operator*() const noexcept + { + return *GetPtr(); + } - T* operator->() const { return GetPtr(); } + constexpr T* operator->() const noexcept + { + return GetPtr(); + } - template <class Q = T> - typename std::enable_if<!std::is_same<Q, void>::value, Q>::type& - operator[](int index) { return GetPtr()[index]; } + template<typename Q = T> + std::enable_if_t<!std::is_same_v<Q, void>, Q>& operator[](int index) noexcept + { + return GetPtr()[index]; + } - T* GetPtr() const { return (T*)(m_value == 0 ? nullptr : memory_base + (uint32)m_value); } + T* GetPtr() const noexcept + { + return (T*)(m_value == 0 ? nullptr : memory_base + (uint32)m_value); + } - template <typename C> - C* GetPtr() const { return (C*)(GetPtr()); } + template<typename C> + C* GetPtr() const noexcept + { + return static_cast<C*>(GetPtr()); + } - constexpr uint32 GetMPTR() const { return m_value.value(); } - constexpr const uint32be& GetBEValue() const { return m_value; } + [[nodiscard]] constexpr uint32 GetMPTR() const noexcept + { + return m_value.value(); + } + [[nodiscard]] constexpr const uint32be& GetBEValue() const noexcept + { + return m_value; + } - constexpr bool IsNull() const { return m_value == 0; } + [[nodiscard]] constexpr bool IsNull() const noexcept + { + return m_value == 0; + } -private: + private: uint32be m_value; }; static_assert(sizeof(MEMPTR<void*>) == sizeof(uint32be)); +static_assert(std::is_trivially_copyable_v<MEMPTR<void*>>); #include "StackAllocator.h" #include "SysAllocator.h" -template <typename T> +template<typename T> struct fmt::formatter<MEMPTR<T>> : formatter<string_view> { - template <typename FormatContext> - auto format(const MEMPTR<T>& v, FormatContext& ctx) const -> format_context::iterator { return fmt::format_to(ctx.out(), "{:#x}", v.GetMPTR()); } + template<typename FormatContext> + auto format(const MEMPTR<T>& v, FormatContext& ctx) const -> format_context::iterator + { + return fmt::format_to(ctx.out(), "{:#x}", v.GetMPTR()); + } }; diff --git a/src/Common/precompiled.h b/src/Common/precompiled.h index 61707519..d4df4343 100644 --- a/src/Common/precompiled.h +++ b/src/Common/precompiled.h @@ -394,16 +394,10 @@ void vectorRemoveByIndex(std::vector<T>& vec, const size_t index) vec.erase(vec.begin() + index); } -template<typename T1, typename T2> -int match_any_of(T1 value, T2 compareTo) +template<typename T1, typename... Types> +bool match_any_of(T1&& value, Types&&... others) { - return value == compareTo; -} - -template<typename T1, typename T2, typename... Types> -bool match_any_of(T1 value, T2 compareTo, Types&&... others) -{ - return value == compareTo || match_any_of(value, others...); + return ((value == others) || ...); } // we cache the frequency in a static variable @@ -501,13 +495,6 @@ bool future_is_ready(std::future<T>& f) #endif } -// replace with std::scope_exit once available -struct scope_exit -{ - std::function<void()> f_; - explicit scope_exit(std::function<void()> f) noexcept : f_(std::move(f)) {} - ~scope_exit() { if (f_) f_(); } -}; // helper function to cast raw pointers to std::atomic // this is technically not legal but works on most platforms as long as alignment restrictions are met and the implementation of atomic doesnt come with additional members @@ -515,6 +502,8 @@ struct scope_exit template<typename T> std::atomic<T>* _rawPtrToAtomic(T* ptr) { + static_assert(sizeof(T) == sizeof(std::atomic<T>)); + cemu_assert_debug((reinterpret_cast<std::uintptr_t>(ptr) % alignof(std::atomic<T>)) == 0); return reinterpret_cast<std::atomic<T>*>(ptr); } @@ -578,13 +567,34 @@ struct fmt::formatter<betype<T>> : fmt::formatter<T> } }; -// useful C++23 stuff that isn't yet widely supported - -// std::to_underlying +// useful future C++ stuff namespace stdx { + // std::to_underlying template <typename EnumT, typename = std::enable_if_t < std::is_enum<EnumT>{} >> constexpr std::underlying_type_t<EnumT> to_underlying(EnumT e) noexcept { return static_cast<std::underlying_type_t<EnumT>>(e); }; + + // std::scope_exit + template <typename Fn> + class scope_exit + { + Fn m_func; + bool m_released = false; + public: + explicit scope_exit(Fn&& f) noexcept + : m_func(std::forward<Fn>(f)) + {} + ~scope_exit() + { + if (!m_released) m_func(); + } + scope_exit(scope_exit&& other) noexcept + : m_func(std::move(other.m_func)), m_released(std::exchange(other.m_released, true)) + {} + scope_exit(const scope_exit&) = delete; + scope_exit& operator=(scope_exit) = delete; + void release() { m_released = true;} + }; } \ No newline at end of file diff --git a/src/gui/CemuApp.cpp b/src/gui/CemuApp.cpp index f91c1e3a..50ff3b89 100644 --- a/src/gui/CemuApp.cpp +++ b/src/gui/CemuApp.cpp @@ -15,6 +15,9 @@ #if BOOST_OS_LINUX && HAS_WAYLAND #include "gui/helpers/wxWayland.h" #endif +#if __WXGTK__ +#include <glib.h> +#endif #include <wx/image.h> #include <wx/filename.h> diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 5af88176..5ac5ebfd 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -50,7 +50,6 @@ add_library(CemuUtil MemMapper/MemMapper.h SystemInfo/SystemInfo.cpp SystemInfo/SystemInfo.h - ThreadPool/ThreadPool.cpp ThreadPool/ThreadPool.h tinyxml2/tinyxml2.cpp tinyxml2/tinyxml2.h diff --git a/src/util/ThreadPool/ThreadPool.cpp b/src/util/ThreadPool/ThreadPool.cpp deleted file mode 100644 index e69de29b..00000000 diff --git a/src/util/containers/robin_hood.h b/src/util/containers/robin_hood.h index 577521b1..4f76519f 100644 --- a/src/util/containers/robin_hood.h +++ b/src/util/containers/robin_hood.h @@ -194,7 +194,7 @@ namespace robin_hood { // workaround missing "is_trivially_copyable" in g++ < 5.0 // See https://stackoverflow.com/a/31798726/48181 -#if defined(__GNUC__) && __GNUC__ < 5 +#if defined(__GNUC__) && __GNUC__ < 5 && !defined(__clang__) # define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) __has_trivial_copy(__VA_ARGS__) #else # define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) std::is_trivially_copyable<__VA_ARGS__>::value diff --git a/src/util/crypto/crc32.cpp b/src/util/crypto/crc32.cpp index 52eb8a88..a5b37a5e 100644 --- a/src/util/crypto/crc32.cpp +++ b/src/util/crypto/crc32.cpp @@ -1,29 +1,6 @@ #include "crc32.h" -#if defined(_MSC_VER) || defined(__MINGW32__) -#define __LITTLE_ENDIAN 1234 -#define __BIG_ENDIAN 4321 -#define __BYTE_ORDER __LITTLE_ENDIAN - -#include <xmmintrin.h> -#ifdef __MINGW32__ -#define PREFETCH(location) __builtin_prefetch(location) -#else -#define PREFETCH(location) _mm_prefetch(location, _MM_HINT_T0) -#endif -#else -// defines __BYTE_ORDER as __LITTLE_ENDIAN or __BIG_ENDIAN -#include <sys/param.h> - -#ifdef __GNUC__ -#define PREFETCH(location) __builtin_prefetch(location) -#else - // no prefetching -#define PREFETCH(location) ; -#endif -#endif - -unsigned int Crc32Lookup[8][256] = +constexpr uint32 Crc32Lookup[8][256] = { { 0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535,0x9E6495A3, @@ -301,20 +278,7 @@ unsigned int Crc32Lookup[8][256] = } }; -/// swap endianess -static inline uint32_t swap(uint32_t x) -{ -#if defined(__GNUC__) || defined(__clang__) - return __builtin_bswap32(x); -#else - return (x >> 24) | - ((x >> 8) & 0x0000FF00) | - ((x << 8) & 0x00FF0000) | - (x << 24); -#endif -} - -unsigned int crc32_calc_slice_by_8(unsigned int previousCrc32, const void* data, int length) +uint32 crc32_calc_slice_by_8(uint32 previousCrc32, const void* data, size_t length) { uint32_t crc = ~previousCrc32; // same as previousCrc32 ^ 0xFFFFFFFF const uint32_t* current = (const uint32_t*)data; @@ -323,7 +287,7 @@ unsigned int crc32_calc_slice_by_8(unsigned int previousCrc32, const void* data, while (length >= 8) { if constexpr (std::endian::native == std::endian::big){ - uint32_t one = *current++ ^ swap(crc); + uint32_t one = *current++ ^ _swapEndianU32(crc); uint32_t two = *current++; crc = Crc32Lookup[0][two & 0xFF] ^ Crc32Lookup[1][(two >> 8) & 0xFF] ^ @@ -348,13 +312,14 @@ unsigned int crc32_calc_slice_by_8(unsigned int previousCrc32, const void* data, Crc32Lookup[7][one & 0xFF]; } else { - cemu_assert(false); + static_assert(std::endian::native == std::endian::big || std::endian::native == std::endian::little, + "Platform byte-order is unsupported"); } length -= 8; } - const uint8_t* currentChar = (const uint8_t*)current; + const uint8* currentChar = (const uint8*)current; // remaining 1 to 7 bytes (standard algorithm) while (length-- != 0) crc = (crc >> 8) ^ Crc32Lookup[0][(crc & 0xFF) ^ *currentChar++]; @@ -362,20 +327,20 @@ unsigned int crc32_calc_slice_by_8(unsigned int previousCrc32, const void* data, return ~crc; // same as crc ^ 0xFFFFFFFF } -unsigned int crc32_calc(unsigned int c, const void* data, int length) +uint32 crc32_calc(uint32 c, const void* data, size_t length) { if (length >= 16) { return crc32_calc_slice_by_8(c, data, length); } - unsigned char* p = (unsigned char*)data; + const uint8* p = (const uint8*)data; if (length == 0) return c; c ^= 0xFFFFFFFF; while (length) { - unsigned char temp = *p; - temp ^= (unsigned char)c; + uint8 temp = *p; + temp ^= (uint8)c; c = (c >> 8) ^ Crc32Lookup[0][temp]; // next length--; diff --git a/src/util/crypto/crc32.h b/src/util/crypto/crc32.h index b8002261..2ab37376 100644 --- a/src/util/crypto/crc32.h +++ b/src/util/crypto/crc32.h @@ -1,8 +1,8 @@ #pragma once -unsigned int crc32_calc(unsigned int c, const void* data, int length); +uint32 crc32_calc(uint32 c, const void* data, size_t length); -inline unsigned int crc32_calc(const void* data, int length) +inline uint32 crc32_calc(const void* data, size_t length) { return crc32_calc(0, data, length); } \ No newline at end of file From 6dc73f5d797082c25a68ad162377077547948d26 Mon Sep 17 00:00:00 2001 From: Alexandre Bouvier <contact@amb.tf> Date: Thu, 3 Oct 2024 06:48:25 +0000 Subject: [PATCH 186/299] Add support for fmt 11 (#1366) --- src/Cemu/Logging/CemuLogging.h | 4 ++++ src/config/CemuConfig.h | 10 +++++----- src/gui/helpers/wxHelpers.h | 2 +- src/input/emulated/EmulatedController.h | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Cemu/Logging/CemuLogging.h b/src/Cemu/Logging/CemuLogging.h index a671ce51..5b2e5fa4 100644 --- a/src/Cemu/Logging/CemuLogging.h +++ b/src/Cemu/Logging/CemuLogging.h @@ -91,7 +91,11 @@ bool cemuLog_log(LogType type, std::basic_string<T> formatStr, TArgs&&... args) 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; diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index e2fbb74c..2f22cd76 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -194,7 +194,7 @@ ENABLE_ENUM_ITERATORS(CrashDump, CrashDump::Disabled, CrashDump::Enabled); template <> struct fmt::formatter<PrecompiledShaderOption> : formatter<string_view> { template <typename FormatContext> - auto format(const PrecompiledShaderOption c, FormatContext &ctx) { + auto format(const PrecompiledShaderOption c, FormatContext &ctx) const { string_view name; switch (c) { @@ -209,7 +209,7 @@ struct fmt::formatter<PrecompiledShaderOption> : formatter<string_view> { template <> struct fmt::formatter<AccurateShaderMulOption> : formatter<string_view> { template <typename FormatContext> - auto format(const AccurateShaderMulOption c, FormatContext &ctx) { + auto format(const AccurateShaderMulOption c, FormatContext &ctx) const { string_view name; switch (c) { @@ -223,7 +223,7 @@ struct fmt::formatter<AccurateShaderMulOption> : formatter<string_view> { template <> struct fmt::formatter<CPUMode> : formatter<string_view> { template <typename FormatContext> - auto format(const CPUMode c, FormatContext &ctx) { + auto format(const CPUMode c, FormatContext &ctx) const { string_view name; switch (c) { @@ -240,7 +240,7 @@ struct fmt::formatter<CPUMode> : formatter<string_view> { template <> struct fmt::formatter<CPUModeLegacy> : formatter<string_view> { template <typename FormatContext> - auto format(const CPUModeLegacy c, FormatContext &ctx) { + auto format(const CPUModeLegacy c, FormatContext &ctx) const { string_view name; switch (c) { @@ -257,7 +257,7 @@ struct fmt::formatter<CPUModeLegacy> : formatter<string_view> { template <> struct fmt::formatter<CafeConsoleRegion> : formatter<string_view> { template <typename FormatContext> - auto format(const CafeConsoleRegion v, FormatContext &ctx) { + auto format(const CafeConsoleRegion v, FormatContext &ctx) const { string_view name; switch (v) { diff --git a/src/gui/helpers/wxHelpers.h b/src/gui/helpers/wxHelpers.h index fa135cf4..9e00bf48 100644 --- a/src/gui/helpers/wxHelpers.h +++ b/src/gui/helpers/wxHelpers.h @@ -8,7 +8,7 @@ template <> struct fmt::formatter<wxString> : formatter<string_view> { template <typename FormatContext> - auto format(const wxString& str, FormatContext& ctx) + auto format(const wxString& str, FormatContext& ctx) const { return formatter<string_view>::format(str.c_str().AsChar(), ctx); } diff --git a/src/input/emulated/EmulatedController.h b/src/input/emulated/EmulatedController.h index 907be07e..c5adf81e 100644 --- a/src/input/emulated/EmulatedController.h +++ b/src/input/emulated/EmulatedController.h @@ -127,7 +127,7 @@ using EmulatedControllerPtr = std::shared_ptr<EmulatedController>; template <> struct fmt::formatter<EmulatedController::Type> : formatter<string_view> { template <typename FormatContext> - auto format(EmulatedController::Type v, FormatContext& ctx) { + auto format(EmulatedController::Type v, FormatContext& ctx) const { switch (v) { case EmulatedController::Type::VPAD: return formatter<string_view>::format("Wii U Gamepad", ctx); From 3acd0c4f2ca328abe80f2d35fb20136e738c84ae Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Mon, 14 Oct 2024 14:03:36 +0200 Subject: [PATCH 187/299] Vulkan: Protect against uniform var ringbuffer overflow (#1378) --- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 8 +- .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 4 +- .../Renderer/Vulkan/VulkanRendererCore.cpp | 99 +++++++++++++------ 3 files changed, 75 insertions(+), 36 deletions(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 12d1d975..9ad2c5ca 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -1892,6 +1892,7 @@ void VulkanRenderer::ProcessFinishedCommandBuffers() if (fenceStatus == VK_SUCCESS) { ProcessDestructionQueue(); + m_uniformVarBufferReadIndex = m_cmdBufferUniformRingbufIndices[m_commandBufferSyncIndex]; m_commandBufferSyncIndex = (m_commandBufferSyncIndex + 1) % m_commandBuffers.size(); memoryManager->cleanupBuffers(m_countCommandBufferFinished); m_countCommandBufferFinished++; @@ -1985,6 +1986,7 @@ void VulkanRenderer::SubmitCommandBuffer(VkSemaphore signalSemaphore, VkSemaphor cemuLog_logDebug(LogType::Force, "Vulkan: Waiting for available command buffer..."); WaitForNextFinishedCommandBuffer(); } + m_cmdBufferUniformRingbufIndices[nextCmdBufferIndex] = m_cmdBufferUniformRingbufIndices[m_commandBufferIndex]; m_commandBufferIndex = nextCmdBufferIndex; @@ -3562,13 +3564,13 @@ void VulkanRenderer::buffer_bindUniformBuffer(LatteConst::ShaderType shaderType, switch (shaderType) { case LatteConst::ShaderType::Vertex: - dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX].unformBufferOffset[bufferIndex] = offset; + dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX].uniformBufferOffset[bufferIndex] = offset; break; case LatteConst::ShaderType::Geometry: - dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY].unformBufferOffset[bufferIndex] = offset; + dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY].uniformBufferOffset[bufferIndex] = offset; break; case LatteConst::ShaderType::Pixel: - dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT].unformBufferOffset[bufferIndex] = offset; + dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT].uniformBufferOffset[bufferIndex] = offset; break; default: cemu_assert_debug(false); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index ce97b5e9..52c1c6ed 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -591,6 +591,7 @@ private: bool m_uniformVarBufferMemoryIsCoherent{false}; uint8* m_uniformVarBufferPtr = nullptr; uint32 m_uniformVarBufferWriteIndex = 0; + uint32 m_uniformVarBufferReadIndex = 0; // transform feedback ringbuffer VkBuffer m_xfbRingBuffer = VK_NULL_HANDLE; @@ -637,6 +638,7 @@ private: size_t m_commandBufferIndex = 0; // current buffer being filled size_t m_commandBufferSyncIndex = 0; // latest buffer that finished execution (updated on submit) size_t m_commandBufferIDOfPrevFrame = 0; + std::array<size_t, kCommandBufferPoolSize> m_cmdBufferUniformRingbufIndices {}; // index in the uniform ringbuffer std::array<VkFence, kCommandBufferPoolSize> m_cmd_buffer_fences; std::array<VkCommandBuffer, kCommandBufferPoolSize> m_commandBuffers; std::array<VkSemaphore, kCommandBufferPoolSize> m_commandBufferSemaphores; @@ -659,7 +661,7 @@ private: uint32 uniformVarBufferOffset[VulkanRendererConst::SHADER_STAGE_INDEX_COUNT]; struct { - uint32 unformBufferOffset[LATTE_NUM_MAX_UNIFORM_BUFFERS]; + uint32 uniformBufferOffset[LATTE_NUM_MAX_UNIFORM_BUFFERS]; }shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_COUNT]; }dynamicOffsetInfo{}; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp index 6500f7d3..3a684072 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp @@ -375,24 +375,20 @@ float s_vkUniformData[512 * 4]; void VulkanRenderer::uniformData_updateUniformVars(uint32 shaderStageIndex, LatteDecompilerShader* shader) { - auto GET_UNIFORM_DATA_PTR = [&](size_t index) { return s_vkUniformData + (index / 4); }; + auto GET_UNIFORM_DATA_PTR = [](size_t index) { return s_vkUniformData + (index / 4); }; sint32 shaderAluConst; - sint32 shaderUniformRegisterOffset; switch (shader->shaderType) { case LatteConst::ShaderType::Vertex: shaderAluConst = 0x400; - shaderUniformRegisterOffset = mmSQ_VTX_UNIFORM_BLOCK_START; break; case LatteConst::ShaderType::Pixel: shaderAluConst = 0; - shaderUniformRegisterOffset = mmSQ_PS_UNIFORM_BLOCK_START; break; case LatteConst::ShaderType::Geometry: shaderAluConst = 0; // geometry shader has no ALU const - shaderUniformRegisterOffset = mmSQ_GS_UNIFORM_BLOCK_START; break; default: UNREACHABLE; @@ -445,7 +441,7 @@ void VulkanRenderer::uniformData_updateUniformVars(uint32 shaderStageIndex, Latt } if (shader->uniform.loc_verticesPerInstance >= 0) { - *(int*)(s_vkUniformData + ((size_t)shader->uniform.loc_verticesPerInstance / 4)) = m_streamoutState.verticesPerInstance; + *(int*)GET_UNIFORM_DATA_PTR(shader->uniform.loc_verticesPerInstance) = m_streamoutState.verticesPerInstance; for (sint32 b = 0; b < LATTE_NUM_STREAMOUT_BUFFER; b++) { if (shader->uniform.loc_streamoutBufferBase[b] >= 0) @@ -455,26 +451,63 @@ void VulkanRenderer::uniformData_updateUniformVars(uint32 shaderStageIndex, Latt } } // upload - if ((m_uniformVarBufferWriteIndex + shader->uniform.uniformRangeSize + 1024) > UNIFORMVAR_RINGBUFFER_SIZE) + const uint32 bufferAlignmentM1 = std::max(m_featureControl.limits.minUniformBufferOffsetAlignment, m_featureControl.limits.nonCoherentAtomSize) - 1; + const uint32 uniformSize = (shader->uniform.uniformRangeSize + bufferAlignmentM1) & ~bufferAlignmentM1; + + auto waitWhileCondition = [&](std::function<bool()> condition) { + while (condition()) + { + if (m_commandBufferSyncIndex == m_commandBufferIndex) + { + if (m_cmdBufferUniformRingbufIndices[m_commandBufferIndex] != m_uniformVarBufferReadIndex) + { + draw_endRenderPass(); + SubmitCommandBuffer(); + } + else + { + // submitting work would not change readIndex, so there's no way for conditions based on it to change + cemuLog_log(LogType::Force, "draw call overflowed and corrupted uniform ringbuffer. expect visual corruption"); + cemu_assert_suspicious(); + break; + } + } + WaitForNextFinishedCommandBuffer(); + } + }; + + // wrap around if it doesnt fit consecutively + if (m_uniformVarBufferWriteIndex + uniformSize > UNIFORMVAR_RINGBUFFER_SIZE) { + waitWhileCondition([&]() { + return m_uniformVarBufferReadIndex > m_uniformVarBufferWriteIndex || m_uniformVarBufferReadIndex == 0; + }); m_uniformVarBufferWriteIndex = 0; } - uint32 bufferAlignmentM1 = std::max(m_featureControl.limits.minUniformBufferOffsetAlignment, m_featureControl.limits.nonCoherentAtomSize) - 1; + + auto ringBufRemaining = [&]() { + ssize_t ringBufferUsedBytes = (ssize_t)m_uniformVarBufferWriteIndex - m_uniformVarBufferReadIndex; + if (ringBufferUsedBytes < 0) + ringBufferUsedBytes += UNIFORMVAR_RINGBUFFER_SIZE; + return UNIFORMVAR_RINGBUFFER_SIZE - 1 - ringBufferUsedBytes; + }; + waitWhileCondition([&]() { + return ringBufRemaining() < uniformSize; + }); + const uint32 uniformOffset = m_uniformVarBufferWriteIndex; memcpy(m_uniformVarBufferPtr + uniformOffset, s_vkUniformData, shader->uniform.uniformRangeSize); - m_uniformVarBufferWriteIndex += shader->uniform.uniformRangeSize; - m_uniformVarBufferWriteIndex = (m_uniformVarBufferWriteIndex + bufferAlignmentM1) & ~bufferAlignmentM1; + m_uniformVarBufferWriteIndex += uniformSize; // update dynamic offset dynamicOffsetInfo.uniformVarBufferOffset[shaderStageIndex] = uniformOffset; // flush if not coherent if (!m_uniformVarBufferMemoryIsCoherent) { - uint32 nonCoherentAtomSizeM1 = m_featureControl.limits.nonCoherentAtomSize - 1; VkMappedMemoryRange flushedRange{}; flushedRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; flushedRange.memory = m_uniformVarBufferMemory; flushedRange.offset = uniformOffset; - flushedRange.size = (shader->uniform.uniformRangeSize + nonCoherentAtomSizeM1) & ~nonCoherentAtomSizeM1; + flushedRange.size = uniformSize; vkFlushMappedMemoryRanges(m_logicalDevice, 1, &flushedRange); } } @@ -494,7 +527,7 @@ void VulkanRenderer::draw_prepareDynamicOffsetsForDescriptorSet(uint32 shaderSta { for (auto& itr : pipeline_info->dynamicOffsetInfo.list_uniformBuffers[shaderStageIndex]) { - dynamicOffsets[numDynOffsets] = dynamicOffsetInfo.shaderUB[shaderStageIndex].unformBufferOffset[itr]; + dynamicOffsets[numDynOffsets] = dynamicOffsetInfo.shaderUB[shaderStageIndex].uniformBufferOffset[itr]; numDynOffsets++; } } @@ -1357,6 +1390,24 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 return; } + // prepare streamout + m_streamoutState.verticesPerInstance = count; + LatteStreamout_PrepareDrawcall(count, instanceCount); + + // update uniform vars + LatteDecompilerShader* vertexShader = LatteSHRC_GetActiveVertexShader(); + LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader(); + LatteDecompilerShader* geometryShader = LatteSHRC_GetActiveGeometryShader(); + + if (vertexShader) + uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, vertexShader); + if (pixelShader) + uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, pixelShader); + if (geometryShader) + uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY, geometryShader); + // store where the read pointer should go after command buffer execution + m_cmdBufferUniformRingbufIndices[m_commandBufferIndex] = m_uniformVarBufferWriteIndex; + // process index data const LattePrimitiveMode primitiveMode = static_cast<LattePrimitiveMode>(LatteGPUState.contextRegister[mmVGT_PRIMITIVE_TYPE]); @@ -1410,22 +1461,6 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 LatteBufferCache_Sync(indexMin + baseVertex, indexMax + baseVertex, baseInstance, instanceCount); } - // prepare streamout - m_streamoutState.verticesPerInstance = count; - LatteStreamout_PrepareDrawcall(count, instanceCount); - - // update uniform vars - LatteDecompilerShader* vertexShader = LatteSHRC_GetActiveVertexShader(); - LatteDecompilerShader* pixelShader = LatteSHRC_GetActivePixelShader(); - LatteDecompilerShader* geometryShader = LatteSHRC_GetActiveGeometryShader(); - - if (vertexShader) - uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, vertexShader); - if (pixelShader) - uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, pixelShader); - if (geometryShader) - uniformData_updateUniformVars(VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY, geometryShader); - PipelineInfo* pipeline_info; if (!isFirst) @@ -1613,13 +1648,13 @@ void VulkanRenderer::draw_updateUniformBuffersDirectAccess(LatteDecompilerShader switch (shaderType) { case LatteConst::ShaderType::Vertex: - dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX].unformBufferOffset[bufferIndex] = physicalAddr - m_importedMemBaseAddress; + dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX].uniformBufferOffset[bufferIndex] = physicalAddr - m_importedMemBaseAddress; break; case LatteConst::ShaderType::Geometry: - dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY].unformBufferOffset[bufferIndex] = physicalAddr - m_importedMemBaseAddress; + dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY].uniformBufferOffset[bufferIndex] = physicalAddr - m_importedMemBaseAddress; break; case LatteConst::ShaderType::Pixel: - dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT].unformBufferOffset[bufferIndex] = physicalAddr - m_importedMemBaseAddress; + dynamicOffsetInfo.shaderUB[VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT].uniformBufferOffset[bufferIndex] = physicalAddr - m_importedMemBaseAddress; break; default: UNREACHABLE; From d6575455eedf8fc4dbe4bd1b8101361f4257d0c4 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Thu, 17 Oct 2024 22:24:01 +0200 Subject: [PATCH 188/299] Linux: Fix crash on invalid command-line arguments use std::cout instead of wxMessageBox which does not work when wxWidgets has not been initialised yet --- src/config/LaunchSettings.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/config/LaunchSettings.cpp b/src/config/LaunchSettings.cpp index bf38b9cf..32a069c6 100644 --- a/src/config/LaunchSettings.cpp +++ b/src/config/LaunchSettings.cpp @@ -199,7 +199,11 @@ bool LaunchSettings::HandleCommandline(const std::vector<std::wstring>& args) std::string errorMsg; errorMsg.append("Error while trying to parse command line parameter:\n"); errorMsg.append(ex.what()); +#if BOOST_OS_WINDOWS wxMessageBox(errorMsg, "Parameter error", wxICON_ERROR); +#else + std::cout << errorMsg << std::endl; +#endif return false; } From f9a4b2dbb1ae8de07223a3d988b1605b941f35a8 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Sat, 19 Oct 2024 01:56:56 +0200 Subject: [PATCH 189/299] input: Add option to make `show screen` button a toggle (#1383) --- src/Cafe/CafeSystem.cpp | 2 +- src/Cafe/HW/Latte/Core/Latte.h | 2 +- src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp | 47 ++++++++------------ src/gui/input/panels/VPADInputPanel.cpp | 24 +++++++++- src/gui/input/panels/VPADInputPanel.h | 3 ++ src/input/emulated/VPADController.cpp | 11 +++++ src/input/emulated/VPADController.h | 6 +++ 7 files changed, 64 insertions(+), 31 deletions(-) diff --git a/src/Cafe/CafeSystem.cpp b/src/Cafe/CafeSystem.cpp index 958a5a57..51de3550 100644 --- a/src/Cafe/CafeSystem.cpp +++ b/src/Cafe/CafeSystem.cpp @@ -396,7 +396,7 @@ void cemu_initForGame() // replace any known function signatures with our HLE implementations and patch bugs in the games GamePatch_scan(); } - LatteGPUState.alwaysDisplayDRC = ActiveSettings::DisplayDRCEnabled(); + LatteGPUState.isDRCPrimary = ActiveSettings::DisplayDRCEnabled(); InfoLog_PrintActiveSettings(); Latte_Start(); // check for debugger entrypoint bp diff --git a/src/Cafe/HW/Latte/Core/Latte.h b/src/Cafe/HW/Latte/Core/Latte.h index e8cb2be4..e5e9dd5c 100644 --- a/src/Cafe/HW/Latte/Core/Latte.h +++ b/src/Cafe/HW/Latte/Core/Latte.h @@ -52,7 +52,7 @@ struct LatteGPUState_t uint32 gx2InitCalled; // incremented every time GX2Init() is called // OpenGL control uint32 glVendor; // GLVENDOR_* - bool alwaysDisplayDRC = false; + bool isDRCPrimary = false; // temporary (replace with proper solution later) bool tvBufferUsesSRGB; bool drcBufferUsesSRGB; diff --git a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp index 60124c02..ca6a2a4d 100644 --- a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp +++ b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp @@ -989,8 +989,6 @@ void LatteRenderTarget_copyToBackbuffer(LatteTextureView* textureView, bool isPa g_renderer->ImguiEnd(); } -bool ctrlTabHotkeyPressed = false; - void LatteRenderTarget_itHLECopyColorBufferToScanBuffer(MPTR colorBufferPtr, uint32 colorBufferWidth, uint32 colorBufferHeight, uint32 colorBufferSliceIndex, uint32 colorBufferFormat, uint32 colorBufferPitch, Latte::E_HWTILEMODE colorBufferTilemode, uint32 colorBufferSwizzle, uint32 renderTarget) { cemu_assert_debug(colorBufferSliceIndex == 0); // todo - support for non-zero slice @@ -1000,38 +998,31 @@ void LatteRenderTarget_itHLECopyColorBufferToScanBuffer(MPTR colorBufferPtr, uin return; } + auto getVPADScreenActive = [](size_t n) -> std::pair<bool, bool> { + auto controller = InputManager::instance().get_vpad_controller(n); + if (!controller) + return {false,false}; + auto pressed = controller->is_screen_active(); + auto toggle = controller->is_screen_active_toggle(); + return {pressed && !toggle, pressed && toggle}; + }; + const bool tabPressed = gui_isKeyDown(PlatformKeyCodes::TAB); const bool ctrlPressed = gui_isKeyDown(PlatformKeyCodes::LCONTROL); + const auto [vpad0Active, vpad0Toggle] = getVPADScreenActive(0); + const auto [vpad1Active, vpad1Toggle] = getVPADScreenActive(1); - bool showDRC = swkbd_hasKeyboardInputHook() == false && tabPressed; - bool& alwaysDisplayDRC = LatteGPUState.alwaysDisplayDRC; + const bool altScreenRequested = (!ctrlPressed && tabPressed) || vpad0Active || vpad1Active; + const bool togglePressed = (ctrlPressed && tabPressed) || vpad0Toggle || vpad1Toggle; + static bool togglePressedLast = false; - if (ctrlPressed && tabPressed) - { - if (ctrlTabHotkeyPressed == false) - { - alwaysDisplayDRC = !alwaysDisplayDRC; - ctrlTabHotkeyPressed = true; - } - } - else - ctrlTabHotkeyPressed = false; + bool& isDRCPrimary = LatteGPUState.isDRCPrimary; - if (alwaysDisplayDRC) - showDRC = !tabPressed; + if(togglePressed && !togglePressedLast) + isDRCPrimary = !isDRCPrimary; + togglePressedLast = togglePressed; - if (!showDRC) - { - auto controller = InputManager::instance().get_vpad_controller(0); - if (controller && controller->is_screen_active()) - showDRC = true; - if (!showDRC) - { - controller = InputManager::instance().get_vpad_controller(1); - if (controller && controller->is_screen_active()) - showDRC = true; - } - } + bool showDRC = swkbd_hasKeyboardInputHook() == false && (isDRCPrimary ^ altScreenRequested); if ((renderTarget & RENDER_TARGET_DRC) && g_renderer->IsPadWindowActive()) LatteRenderTarget_copyToBackbuffer(texView, true); diff --git a/src/gui/input/panels/VPADInputPanel.cpp b/src/gui/input/panels/VPADInputPanel.cpp index fbcdfde4..9e6d75d6 100644 --- a/src/gui/input/panels/VPADInputPanel.cpp +++ b/src/gui/input/panels/VPADInputPanel.cpp @@ -5,6 +5,7 @@ #include <wx/statline.h> #include <wx/textctrl.h> #include <wx/slider.h> +#include <wx/checkbox.h> #include "gui/helpers/wxControlObject.h" @@ -131,11 +132,23 @@ VPADInputPanel::VPADInputPanel(wxWindow* parent) } // Blow Mic - row = 9; + row = 8; add_button_row(main_sizer, row, column, VPADController::kButtonId_Mic, _("blow mic")); row++; add_button_row(main_sizer, row, column, VPADController::kButtonId_Screen, _("show screen")); + row++; + + auto toggleScreenText = new wxStaticText(this, wxID_ANY, _("toggle screen")); + main_sizer->Add(toggleScreenText, + wxGBPosition(row, column), + wxDefaultSpan, + wxALL | wxALIGN_CENTER_VERTICAL, 5); + m_togglePadViewCheckBox = new wxCheckBox(this, wxID_ANY, {}, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + wxString toggleScreenTT = _("Makes the \"show screen\" button toggle between the TV and gamepad screens"); + m_togglePadViewCheckBox->SetToolTip(toggleScreenTT); + toggleScreenText->SetToolTip(toggleScreenTT); + main_sizer->Add(m_togglePadViewCheckBox, wxGBPosition(row,column+1), wxDefaultSpan, wxALL | wxEXPAND, 5); ////////////////////////////////////////////////////////////////// @@ -168,6 +181,8 @@ void VPADInputPanel::on_timer(const EmulatedControllerPtr& emulated_controller, { InputPanel::on_timer(emulated_controller, controller_base); + static_cast<VPADController*>(emulated_controller.get())->set_screen_toggle(m_togglePadViewCheckBox->GetValue()); + if(emulated_controller) { const auto axis = emulated_controller->get_axis(); @@ -182,3 +197,10 @@ void VPADInputPanel::OnVolumeChange(wxCommandEvent& event) { } +void VPADInputPanel::load_controller(const EmulatedControllerPtr& controller) +{ + InputPanel::load_controller(controller); + + const bool isToggle = static_cast<VPADController*>(controller.get())->is_screen_active_toggle(); + m_togglePadViewCheckBox->SetValue(isToggle); +} diff --git a/src/gui/input/panels/VPADInputPanel.h b/src/gui/input/panels/VPADInputPanel.h index 3513bbf7..477385f4 100644 --- a/src/gui/input/panels/VPADInputPanel.h +++ b/src/gui/input/panels/VPADInputPanel.h @@ -4,6 +4,7 @@ #include "gui/input/panels/InputPanel.h" class wxInputDraw; +class wxCheckBox; class VPADInputPanel : public InputPanel { @@ -11,11 +12,13 @@ public: VPADInputPanel(wxWindow* parent); void on_timer(const EmulatedControllerPtr& emulated_controller, const ControllerPtr& controller) override; + virtual void load_controller(const EmulatedControllerPtr& controller) override; private: void OnVolumeChange(wxCommandEvent& event); wxInputDraw* m_left_draw, * m_right_draw; + wxCheckBox* m_togglePadViewCheckBox; void add_button_row(wxGridBagSizer *sizer, sint32 row, sint32 column, const VPADController::ButtonId &button_id); void add_button_row(wxGridBagSizer *sizer, sint32 row, sint32 column, const VPADController::ButtonId &button_id, const wxString &label); diff --git a/src/input/emulated/VPADController.cpp b/src/input/emulated/VPADController.cpp index aeb56395..f1ab1bc4 100644 --- a/src/input/emulated/VPADController.cpp +++ b/src/input/emulated/VPADController.cpp @@ -686,3 +686,14 @@ bool VPADController::set_default_mapping(const std::shared_ptr<ControllerBase>& return mapping_updated; } + +void VPADController::load(const pugi::xml_node& node) +{ + if (const auto value = node.child("toggle_display")) + m_screen_active_toggle = ConvertString<bool>(value.child_value()); +} + +void VPADController::save(pugi::xml_node& node) +{ + node.append_child("toggle_display").append_child(pugi::node_pcdata).set_value(fmt::format("{}", (int)m_screen_active_toggle).c_str()); +} diff --git a/src/input/emulated/VPADController.h b/src/input/emulated/VPADController.h index 6aef16ae..937062da 100644 --- a/src/input/emulated/VPADController.h +++ b/src/input/emulated/VPADController.h @@ -66,6 +66,8 @@ public: bool is_mic_active() { return m_mic_active; } bool is_screen_active() { return m_screen_active; } + bool is_screen_active_toggle() { return m_screen_active_toggle; } + void set_screen_toggle(bool toggle) {m_screen_active_toggle = toggle;} static std::string_view get_button_name(ButtonId id); @@ -86,9 +88,13 @@ public: bool set_default_mapping(const std::shared_ptr<ControllerBase>& controller) override; + void load(const pugi::xml_node& node) override; + void save(pugi::xml_node& node) override; + private: bool m_mic_active = false; bool m_screen_active = false; + bool m_screen_active_toggle = false; uint32be m_last_holdvalue = 0; std::chrono::high_resolution_clock::time_point m_last_hold_change{}, m_last_pulse{}; From 63e1289bb518562ae62033389610a1e772e63c8b Mon Sep 17 00:00:00 2001 From: capitalistspz <keipitalists@proton.me> Date: Fri, 25 Oct 2024 17:48:21 +0100 Subject: [PATCH 190/299] Windows: Save icons to Cemu user data directory (#1390) --- src/gui/components/wxGameList.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/gui/components/wxGameList.cpp b/src/gui/components/wxGameList.cpp index eedfde5d..6cbb5859 100644 --- a/src/gui/components/wxGameList.cpp +++ b/src/gui/components/wxGameList.cpp @@ -1392,7 +1392,6 @@ void wxGameList::CreateShortcut(GameInfo2& gameInfo) const auto outputPath = shortcutDialog.GetPath(); std::optional<fs::path> icon_path = std::nullopt; - [&]() { int iconIdx; int smallIconIdx; @@ -1402,15 +1401,13 @@ void wxGameList::CreateShortcut(GameInfo2& gameInfo) return; } const auto icon = m_image_list->GetIcon(iconIdx); - PWSTR localAppData; - const auto hres = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &localAppData); - wxBitmap bitmap{}; - auto folder = fs::path(localAppData) / "Cemu" / "icons"; - if (!SUCCEEDED(hres) || (!fs::exists(folder) && !fs::create_directories(folder))) + const auto folder = ActiveSettings::GetUserDataPath("icons"); + if (!fs::exists(folder) && !fs::create_directories(folder)) { cemuLog_log(LogType::Force, "Failed to create icon directory"); return; } + wxBitmap bitmap{}; if (!bitmap.CopyFromIcon(icon)) { cemuLog_log(LogType::Force, "Failed to copy icon"); @@ -1426,7 +1423,7 @@ void wxGameList::CreateShortcut(GameInfo2& gameInfo) icon_path = std::nullopt; cemuLog_log(LogType::Force, "Icon failed to save"); } - }(); + } IShellLinkW* shellLink; HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLink, reinterpret_cast<LPVOID*>(&shellLink)); From 459fd5d9bb4897381c4d7d326302ca707c7b818c Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Mon, 28 Oct 2024 09:37:30 +0100 Subject: [PATCH 191/299] input: Fix crash when closing add controller dialog before search completes (#1386) --- src/gui/input/InputAPIAddWindow.cpp | 43 ++++++++++++++++++++++------- src/gui/input/InputAPIAddWindow.h | 9 ++++++ 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/src/gui/input/InputAPIAddWindow.cpp b/src/gui/input/InputAPIAddWindow.cpp index a6d1f1a9..688ee14e 100644 --- a/src/gui/input/InputAPIAddWindow.cpp +++ b/src/gui/input/InputAPIAddWindow.cpp @@ -114,6 +114,11 @@ InputAPIAddWindow::InputAPIAddWindow(wxWindow* parent, const wxPoint& position, this->Bind(wxControllersRefreshed, &InputAPIAddWindow::on_controllers_refreshed, this); } +InputAPIAddWindow::~InputAPIAddWindow() +{ + discard_thread_result(); +} + void InputAPIAddWindow::on_add_button(wxCommandEvent& event) { const auto selection = m_input_api->GetSelection(); @@ -159,6 +164,8 @@ std::unique_ptr<ControllerProviderSettings> InputAPIAddWindow::get_settings() co void InputAPIAddWindow::on_api_selected(wxCommandEvent& event) { + discard_thread_result(); + if (m_input_api->GetSelection() == wxNOT_FOUND) return; @@ -239,19 +246,25 @@ void InputAPIAddWindow::on_controller_dropdown(wxCommandEvent& event) m_controller_list->Append(_("Searching for controllers..."), (wxClientData*)nullptr); m_controller_list->SetSelection(wxNOT_FOUND); - std::thread([this, provider, selected_uuid]() + m_search_thread_data = std::make_unique<AsyncThreadData>(); + std::thread([this, provider, selected_uuid](std::shared_ptr<AsyncThreadData> data) { 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(); + { + std::lock_guard lock{data->mutex}; + if(!data->discardResult) + { + 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; + } + } + }, m_search_thread_data).detach(); } void InputAPIAddWindow::on_controller_selected(wxCommandEvent& event) @@ -301,3 +314,13 @@ void InputAPIAddWindow::on_controllers_refreshed(wxCommandEvent& event) } } } + +void InputAPIAddWindow::discard_thread_result() +{ + m_search_running = false; + if(m_search_thread_data) + { + std::lock_guard lock{m_search_thread_data->mutex}; + m_search_thread_data->discardResult = true; + } +} diff --git a/src/gui/input/InputAPIAddWindow.h b/src/gui/input/InputAPIAddWindow.h index ebee0592..085dd623 100644 --- a/src/gui/input/InputAPIAddWindow.h +++ b/src/gui/input/InputAPIAddWindow.h @@ -19,6 +19,7 @@ class InputAPIAddWindow : public wxDialog { public: InputAPIAddWindow(wxWindow* parent, const wxPoint& position, const std::vector<ControllerPtr>& controllers); + ~InputAPIAddWindow(); bool is_valid() const { return m_type.has_value() && m_controller != nullptr; } InputAPI::Type get_type() const { return m_type.value(); } @@ -38,6 +39,8 @@ private: void on_controller_selected(wxCommandEvent& event); void on_controllers_refreshed(wxCommandEvent& event); + void discard_thread_result(); + wxChoice* m_input_api; wxComboBox* m_controller_list; wxButton* m_ok_button; @@ -50,4 +53,10 @@ private: std::vector<ControllerPtr> m_controllers; std::atomic_bool m_search_running = false; + struct AsyncThreadData + { + std::atomic_bool discardResult = false; + std::mutex mutex; + }; + std::shared_ptr<AsyncThreadData> m_search_thread_data; }; From 47001ad23349fae130d6faae9409117899eb11cc Mon Sep 17 00:00:00 2001 From: capitalistspz <keipitalists@proton.me> Date: Wed, 30 Oct 2024 22:10:32 +0000 Subject: [PATCH 192/299] Make `MEMPTR<T>` a little more `T*`-like (#1385) --- src/Common/MemPtr.h | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/Common/MemPtr.h b/src/Common/MemPtr.h index 7825e4d5..2dc92040 100644 --- a/src/Common/MemPtr.h +++ b/src/Common/MemPtr.h @@ -98,35 +98,36 @@ class MEMPTR : MEMPTRBase return MEMPTR<X>(this->m_value); } - MEMPTR operator+(const MEMPTR& ptr) noexcept + sint32 operator-(const MEMPTR& ptr) noexcept + requires(!std::is_void_v<T>) { - return MEMPTR(this->GetMPTR() + ptr.GetMPTR()); - } - MEMPTR operator-(const MEMPTR& ptr) noexcept - { - return MEMPTR(this->GetMPTR() - ptr.GetMPTR()); + return static_cast<sint32>(this->GetMPTR() - ptr.GetMPTR()); } MEMPTR operator+(sint32 v) noexcept + requires(!std::is_void_v<T>) { // pointer arithmetic - return MEMPTR(this->GetMPTR() + v * 4); + return MEMPTR(this->GetMPTR() + v * sizeof(T)); } MEMPTR operator-(sint32 v) noexcept + requires(!std::is_void_v<T>) { // pointer arithmetic - return MEMPTR(this->GetMPTR() - v * 4); + return MEMPTR(this->GetMPTR() - v * sizeof(T)); } MEMPTR& operator+=(sint32 v) noexcept + requires(!std::is_void_v<T>) { m_value += v * sizeof(T); return *this; } template<typename Q = T> - std::enable_if_t<!std::is_same_v<Q, void>, Q>& operator*() const noexcept + requires(!std::is_void_v<Q>) + Q& operator*() const noexcept { return *GetPtr(); } @@ -137,7 +138,8 @@ class MEMPTR : MEMPTRBase } template<typename Q = T> - std::enable_if_t<!std::is_same_v<Q, void>, Q>& operator[](int index) noexcept + requires(!std::is_void_v<Q>) + Q& operator[](int index) noexcept { return GetPtr()[index]; } From 1c49a8a1ba8fea6e656c1d535f35e316bc29da76 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Fri, 1 Nov 2024 22:47:19 +0100 Subject: [PATCH 193/299] nn_nfp: Implement GetNfpReadOnlyInfo and fix deactivate event Fixes Amiibos not being detected in MK8 --- src/Cafe/OS/libs/nn_nfp/nn_nfp.cpp | 102 +++++++++++++++++------------ 1 file changed, 60 insertions(+), 42 deletions(-) diff --git a/src/Cafe/OS/libs/nn_nfp/nn_nfp.cpp b/src/Cafe/OS/libs/nn_nfp/nn_nfp.cpp index 10d9e7cb..38e0c4fe 100644 --- a/src/Cafe/OS/libs/nn_nfp/nn_nfp.cpp +++ b/src/Cafe/OS/libs/nn_nfp/nn_nfp.cpp @@ -334,45 +334,63 @@ void nnNfpExport_MountRom(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); } -typedef struct +namespace nn::nfp { - /* +0x00 */ uint8 characterId[3]; - /* +0x03 */ uint8 amiiboSeries; - /* +0x04 */ uint16be number; - /* +0x06 */ uint8 nfpType; - /* +0x07 */ uint8 unused[0x2F]; -}nfpRomInfo_t; - -static_assert(offsetof(nfpRomInfo_t, amiiboSeries) == 0x3, "nfpRomInfo.seriesId has invalid offset"); -static_assert(offsetof(nfpRomInfo_t, number) == 0x4, "nfpRomInfo.number has invalid offset"); -static_assert(offsetof(nfpRomInfo_t, nfpType) == 0x6, "nfpRomInfo.nfpType has invalid offset"); -static_assert(sizeof(nfpRomInfo_t) == 0x36, "nfpRomInfo_t has invalid size"); - -void nnNfpExport_GetNfpRomInfo(PPCInterpreter_t* hCPU) -{ - cemuLog_log(LogType::NN_NFP, "GetNfpRomInfo(0x{:08x})", hCPU->gpr[3]); - ppcDefineParamStructPtr(romInfo, nfpRomInfo_t, 0); - - nnNfpLock(); - if (nfp_data.hasActiveAmiibo == false) + struct RomInfo { - nnNfpUnlock(); - osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0)); // todo: Return correct error code - return; + /* +0x00 */ uint8 characterId[3]; + /* +0x03 */ uint8 amiiboSeries; + /* +0x04 */ uint16be number; + /* +0x06 */ uint8 nfpType; + /* +0x07 */ uint8 unused[0x2F]; + }; + + static_assert(offsetof(RomInfo, amiiboSeries) == 0x3); + static_assert(offsetof(RomInfo, number) == 0x4); + static_assert(offsetof(RomInfo, nfpType) == 0x6); + static_assert(sizeof(RomInfo) == 0x36); + + using ReadOnlyInfo = RomInfo; // same layout + + void GetRomInfo(RomInfo* romInfo) + { + cemu_assert_debug(nfp_data.hasActiveAmiibo); + memset(romInfo, 0x00, sizeof(RomInfo)); + romInfo->characterId[0] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.gameAndCharacterId[0]; + romInfo->characterId[1] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.gameAndCharacterId[1]; + romInfo->characterId[2] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.characterVariation; // guessed + romInfo->amiiboSeries = nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboSeries; // guessed + romInfo->number = *(uint16be*)nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboModelNumber; // guessed + romInfo->nfpType = nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboFigureType; // guessed + memset(romInfo->unused, 0x00, sizeof(romInfo->unused)); } - memset(romInfo, 0x00, sizeof(nfpRomInfo_t)); - romInfo->characterId[0] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.gameAndCharacterId[0]; - romInfo->characterId[1] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.gameAndCharacterId[1]; - romInfo->characterId[2] = nfp_data.amiiboNFCData.amiiboIdentificationBlock.characterVariation; // guessed + nnResult GetNfpRomInfo(RomInfo* romInfo) + { + nnNfpLock(); + if (nfp_data.hasActiveAmiibo == false) + { + nnNfpUnlock(); + return BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0); // todo: Return correct error code + } + GetRomInfo(romInfo); + nnNfpUnlock(); + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0); + } - romInfo->amiiboSeries = nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboSeries; // guessed - romInfo->number = *(uint16be*)nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboModelNumber; // guessed - romInfo->nfpType = nfp_data.amiiboNFCData.amiiboIdentificationBlock.amiiboFigureType; // guessed - - nnNfpUnlock(); - osLib_returnFromFunction(hCPU, BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0)); -} + nnResult GetNfpReadOnlyInfo(ReadOnlyInfo* readOnlyInfo) + { + nnNfpLock(); + if (nfp_data.hasActiveAmiibo == false) + { + nnNfpUnlock(); + return BUILD_NN_RESULT(NN_RESULT_LEVEL_STATUS, NN_RESULT_MODULE_NN_NFP, 0); // todo: Return correct error code + } + GetRomInfo(readOnlyInfo); + nnNfpUnlock(); + return BUILD_NN_RESULT(NN_RESULT_LEVEL_SUCCESS, NN_RESULT_MODULE_NN_NFP, 0); + } +}; typedef struct { @@ -880,13 +898,13 @@ void nnNfp_update() if (amiiboElapsedTouchTime >= 1500) { nnNfp_unloadAmiibo(); + if (nfp_data.deactivateEvent) + { + coreinit::OSEvent* osEvent = (coreinit::OSEvent*)memory_getPointerFromVirtualOffset(nfp_data.deactivateEvent); + coreinit::OSSignalEvent(osEvent); + } } nnNfpUnlock(); - if (nfp_data.deactivateEvent) - { - coreinit::OSEvent* osEvent = (coreinit::OSEvent*)memory_getPointerFromVirtualOffset(nfp_data.deactivateEvent); - coreinit::OSSignalEvent(osEvent); - } } void nnNfpExport_GetNfpState(PPCInterpreter_t* hCPU) @@ -1001,8 +1019,6 @@ namespace nn::nfp osLib_addFunction("nn_nfp", "Mount__Q2_2nn3nfpFv", nnNfpExport_Mount); osLib_addFunction("nn_nfp", "MountRom__Q2_2nn3nfpFv", nnNfpExport_MountRom); osLib_addFunction("nn_nfp", "Unmount__Q2_2nn3nfpFv", nnNfpExport_Unmount); - - osLib_addFunction("nn_nfp", "GetNfpRomInfo__Q2_2nn3nfpFPQ3_2nn3nfp7RomInfo", nnNfpExport_GetNfpRomInfo); osLib_addFunction("nn_nfp", "GetNfpCommonInfo__Q2_2nn3nfpFPQ3_2nn3nfp10CommonInfo", nnNfpExport_GetNfpCommonInfo); osLib_addFunction("nn_nfp", "GetNfpRegisterInfo__Q2_2nn3nfpFPQ3_2nn3nfp12RegisterInfo", nnNfpExport_GetNfpRegisterInfo); @@ -1028,7 +1044,9 @@ namespace nn::nfp { nnNfp_load(); // legacy interface, update these to use cafeExportRegister / cafeExportRegisterFunc - cafeExportRegisterFunc(nn::nfp::GetErrorCode, "nn_nfp", "GetErrorCode__Q2_2nn3nfpFRCQ2_2nn6Result", LogType::Placeholder); + cafeExportRegisterFunc(nn::nfp::GetErrorCode, "nn_nfp", "GetErrorCode__Q2_2nn3nfpFRCQ2_2nn6Result", LogType::NN_NFP); + cafeExportRegisterFunc(nn::nfp::GetNfpRomInfo, "nn_nfp", "GetNfpRomInfo__Q2_2nn3nfpFPQ3_2nn3nfp7RomInfo", LogType::NN_NFP); + cafeExportRegisterFunc(nn::nfp::GetNfpReadOnlyInfo, "nn_nfp", "GetNfpReadOnlyInfo__Q2_2nn3nfpFPQ3_2nn3nfp12ReadOnlyInfo", LogType::NN_NFP); } } From 9941e00b545a9c99a8e62c8c33ebe790d38de26e Mon Sep 17 00:00:00 2001 From: SamoZ256 <96914946+SamoZ256@users.noreply.github.com> Date: Tue, 5 Nov 2024 22:22:00 +0100 Subject: [PATCH 194/299] macOS: Fix libusb path for bundle (#1403) --- src/CMakeLists.txt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7d64d91b..84d4fcad 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -101,12 +101,18 @@ if (MACOS_BUNDLE) COMMAND ${CMAKE_COMMAND} ARGS -E copy_directory "${CMAKE_SOURCE_DIR}/bin/${folder}" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/SharedSupport/${folder}") endforeach(folder) + if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set(LIBUSB_PATH "${CMAKE_BINARY_DIR}/vcpkg_installed/x64-osx/debug/lib/libusb-1.0.0.dylib") + else() + set(LIBUSB_PATH "${CMAKE_BINARY_DIR}/vcpkg_installed/x64-osx/lib/libusb-1.0.0.dylib") + endif() + add_custom_command (TARGET CemuBin POST_BUILD COMMAND ${CMAKE_COMMAND} ARGS -E copy "/usr/local/lib/libMoltenVK.dylib" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libMoltenVK.dylib" - COMMAND ${CMAKE_COMMAND} ARGS -E copy "${CMAKE_BINARY_DIR}/vcpkg_installed/x64-osx/lib/libusb-1.0.0.dylib" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libusb-1.0.0.dylib" + COMMAND ${CMAKE_COMMAND} ARGS -E copy "${LIBUSB_PATH}" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libusb-1.0.0.dylib" COMMAND ${CMAKE_COMMAND} ARGS -E copy "${CMAKE_SOURCE_DIR}/src/resource/update.sh" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/update.sh" COMMAND bash -c "install_name_tool -add_rpath @executable_path/../Frameworks ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}" - COMMAND bash -c "install_name_tool -change /Users/runner/work/Cemu/Cemu/build/vcpkg_installed/x64-osx/lib/libusb-1.0.0.dylib @executable_path/../Frameworks/libusb-1.0.0.dylib ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}") + COMMAND bash -c "install_name_tool -change ${LIBUSB_PATH} ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libusb-1.0.0.dylib ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}") endif() set_target_properties(CemuBin PROPERTIES From 813f9148b1afe75053102877e3db5733f701660e Mon Sep 17 00:00:00 2001 From: SamoZ256 <96914946+SamoZ256@users.noreply.github.com> Date: Thu, 7 Nov 2024 07:09:35 +0100 Subject: [PATCH 195/299] macOS: Fix absolute path to libusb dylib (#1405) --- src/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 84d4fcad..3792ab85 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -112,7 +112,7 @@ if (MACOS_BUNDLE) COMMAND ${CMAKE_COMMAND} ARGS -E copy "${LIBUSB_PATH}" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libusb-1.0.0.dylib" COMMAND ${CMAKE_COMMAND} ARGS -E copy "${CMAKE_SOURCE_DIR}/src/resource/update.sh" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/update.sh" COMMAND bash -c "install_name_tool -add_rpath @executable_path/../Frameworks ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}" - COMMAND bash -c "install_name_tool -change ${LIBUSB_PATH} ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libusb-1.0.0.dylib ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}") + COMMAND bash -c "install_name_tool -change ${LIBUSB_PATH} @executable_path/../Frameworks/libusb-1.0.0.dylib ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}") endif() set_target_properties(CemuBin PROPERTIES From 4ac1ab162a2a2734a4b1839e57ff367233e16853 Mon Sep 17 00:00:00 2001 From: capitalistspz <keipitalists@proton.me> Date: Sat, 9 Nov 2024 05:21:06 +0000 Subject: [PATCH 196/299] procui: swap `tickDelay` and `priority` args in callbacks (#1408) --- src/Cafe/OS/libs/proc_ui/proc_ui.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Cafe/OS/libs/proc_ui/proc_ui.cpp b/src/Cafe/OS/libs/proc_ui/proc_ui.cpp index dd9a460f..ff38abbb 100644 --- a/src/Cafe/OS/libs/proc_ui/proc_ui.cpp +++ b/src/Cafe/OS/libs/proc_ui/proc_ui.cpp @@ -427,7 +427,7 @@ namespace proc_ui } if(callbackType != ProcUICallbackId::AcquireForeground) priority = -priority; - AddCallbackInternal(funcPtr, userParam, priority, 0, s_CallbackTables[stdx::to_underlying(callbackType)][coreIndex]); + AddCallbackInternal(funcPtr, userParam, 0, priority, s_CallbackTables[stdx::to_underlying(callbackType)][coreIndex]); } void ProcUIRegisterCallback(ProcUICallbackId callbackType, void* funcPtr, void* userParam, sint32 priority) @@ -437,7 +437,7 @@ namespace proc_ui void ProcUIRegisterBackgroundCallback(void* funcPtr, void* userParam, uint64 tickDelay) { - AddCallbackInternal(funcPtr, userParam, 0, tickDelay, s_backgroundCallbackList); + AddCallbackInternal(funcPtr, userParam, tickDelay, 0, s_backgroundCallbackList); } void FreeCallbackChain(ProcUICallbackList& callbackList) From 2e829479d9f63dcfbd8ef67d456793a70f684b18 Mon Sep 17 00:00:00 2001 From: capitalistspz <keipitalists@proton.me> Date: Sat, 9 Nov 2024 05:22:13 +0000 Subject: [PATCH 197/299] nsyshid/libusb: correct error message formatting and print error string on open fail (#1407) --- src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp b/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp index 7548c998..ab355136 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp +++ b/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp @@ -15,7 +15,7 @@ namespace nsyshid::backend::libusb if (m_initReturnCode < 0) { m_ctx = nullptr; - cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb: failed to initialize libusb with return code %i", + cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb: failed to initialize libusb, return code: {}", m_initReturnCode); return; } @@ -35,7 +35,7 @@ namespace nsyshid::backend::libusb if (ret != LIBUSB_SUCCESS) { cemuLog_logDebug(LogType::Force, - "nsyshid::BackendLibusb: failed to register hotplug callback with return code %i", + "nsyshid::BackendLibusb: failed to register hotplug callback with return code {}", ret); } else @@ -415,7 +415,7 @@ namespace nsyshid::backend::libusb if (ret < 0) { cemuLog_log(LogType::Force, - "nsyshid::DeviceLibusb::open(): failed to get device descriptor; return code: %i", + "nsyshid::DeviceLibusb::open(): failed to get device descriptor, return code: {}", ret); libusb_free_device_list(devices, 1); return false; @@ -439,8 +439,8 @@ namespace nsyshid::backend::libusb { this->m_libusbHandle = nullptr; cemuLog_log(LogType::Force, - "nsyshid::DeviceLibusb::open(): failed to open device; return code: %i", - ret); + "nsyshid::DeviceLibusb::open(): failed to open device: {}", + libusb_strerror(ret)); libusb_free_device_list(devices, 1); return false; } From ca2e0a7c31dcaeac110dc4aa703a992b55c8155f Mon Sep 17 00:00:00 2001 From: Joshua de Reeper <joshua@dereeper.co.nz> Date: Mon, 11 Nov 2024 07:58:01 +0000 Subject: [PATCH 198/299] nsyshid: Add support for emulated Dimensions Toypad (#1371) --- src/Cafe/CMakeLists.txt | 2 + src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp | 9 + src/Cafe/OS/libs/nsyshid/Dimensions.cpp | 1162 +++++++++++++++++ src/Cafe/OS/libs/nsyshid/Dimensions.h | 108 ++ src/config/CemuConfig.cpp | 2 + src/config/CemuConfig.h | 1 + .../EmulatedUSBDeviceFrame.cpp | 410 +++++- .../EmulatedUSBDeviceFrame.h | 48 +- 8 files changed, 1690 insertions(+), 52 deletions(-) create mode 100644 src/Cafe/OS/libs/nsyshid/Dimensions.cpp create mode 100644 src/Cafe/OS/libs/nsyshid/Dimensions.h diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index 91d257b2..0901fece 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -465,6 +465,8 @@ add_library(CemuCafe OS/libs/nsyshid/BackendLibusb.h OS/libs/nsyshid/BackendWindowsHID.cpp OS/libs/nsyshid/BackendWindowsHID.h + OS/libs/nsyshid/Dimensions.cpp + OS/libs/nsyshid/Dimensions.h OS/libs/nsyshid/Infinity.cpp OS/libs/nsyshid/Infinity.h OS/libs/nsyshid/Skylander.cpp diff --git a/src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp b/src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp index 95eaf06a..533d349e 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp +++ b/src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp @@ -1,4 +1,6 @@ #include "BackendEmulated.h" + +#include "Dimensions.h" #include "Infinity.h" #include "Skylander.h" #include "config/CemuConfig.h" @@ -33,5 +35,12 @@ namespace nsyshid::backend::emulated auto device = std::make_shared<InfinityBaseDevice>(); AttachDevice(device); } + if (GetConfig().emulated_usb_devices.emulate_dimensions_toypad && !FindDeviceById(0x0E6F, 0x0241)) + { + cemuLog_logDebug(LogType::Force, "Attaching Emulated Toypad"); + // Add Dimensions Toypad + auto device = std::make_shared<DimensionsToypadDevice>(); + AttachDevice(device); + } } } // namespace nsyshid::backend::emulated \ No newline at end of file diff --git a/src/Cafe/OS/libs/nsyshid/Dimensions.cpp b/src/Cafe/OS/libs/nsyshid/Dimensions.cpp new file mode 100644 index 00000000..f328dde7 --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/Dimensions.cpp @@ -0,0 +1,1162 @@ +#include "Dimensions.h" + +#include "nsyshid.h" +#include "Backend.h" + +#include "Common/FileStream.h" + +#include <array> +#include <random> + +namespace nsyshid +{ + static constexpr std::array<uint8, 16> COMMAND_KEY = {0x55, 0xFE, 0xF6, 0xB0, 0x62, 0xBF, 0x0B, 0x41, + 0xC9, 0xB3, 0x7C, 0xB4, 0x97, 0x3E, 0x29, 0x7B}; + + static constexpr std::array<uint8, 17> CHAR_CONSTANT = {0xB7, 0xD5, 0xD7, 0xE6, 0xE7, 0xBA, 0x3C, 0xA8, + 0xD8, 0x75, 0x47, 0x68, 0xCF, 0x23, 0xE9, 0xFE, 0xAA}; + + static constexpr std::array<uint8, 25> PWD_CONSTANT = {0x28, 0x63, 0x29, 0x20, 0x43, 0x6F, 0x70, 0x79, + 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x4C, 0x45, + 0x47, 0x4F, 0x20, 0x32, 0x30, 0x31, 0x34, 0xAA, 0xAA}; + + DimensionsUSB g_dimensionstoypad; + + const std::map<const uint32, const char*> s_listMinis = { + {0, "Blank Tag"}, + {1, "Batman"}, + {2, "Gandalf"}, + {3, "Wyldstyle"}, + {4, "Aquaman"}, + {5, "Bad Cop"}, + {6, "Bane"}, + {7, "Bart Simpson"}, + {8, "Benny"}, + {9, "Chell"}, + {10, "Cole"}, + {11, "Cragger"}, + {12, "Cyborg"}, + {13, "Cyberman"}, + {14, "Doc Brown"}, + {15, "The Doctor"}, + {16, "Emmet"}, + {17, "Eris"}, + {18, "Gimli"}, + {19, "Gollum"}, + {20, "Harley Quinn"}, + {21, "Homer Simpson"}, + {22, "Jay"}, + {23, "Joker"}, + {24, "Kai"}, + {25, "ACU Trooper"}, + {26, "Gamer Kid"}, + {27, "Krusty the Clown"}, + {28, "Laval"}, + {29, "Legolas"}, + {30, "Lloyd"}, + {31, "Marty McFly"}, + {32, "Nya"}, + {33, "Owen Grady"}, + {34, "Peter Venkman"}, + {35, "Slimer"}, + {36, "Scooby-Doo"}, + {37, "Sensei Wu"}, + {38, "Shaggy"}, + {39, "Stay Puft"}, + {40, "Superman"}, + {41, "Unikitty"}, + {42, "Wicked Witch of the West"}, + {43, "Wonder Woman"}, + {44, "Zane"}, + {45, "Green Arrow"}, + {46, "Supergirl"}, + {47, "Abby Yates"}, + {48, "Finn the Human"}, + {49, "Ethan Hunt"}, + {50, "Lumpy Space Princess"}, + {51, "Jake the Dog"}, + {52, "Harry Potter"}, + {53, "Lord Voldemort"}, + {54, "Michael Knight"}, + {55, "B.A. Baracus"}, + {56, "Newt Scamander"}, + {57, "Sonic the Hedgehog"}, + {58, "Future Update (unreleased)"}, + {59, "Gizmo"}, + {60, "Stripe"}, + {61, "E.T."}, + {62, "Tina Goldstein"}, + {63, "Marceline the Vampire Queen"}, + {64, "Batgirl"}, + {65, "Robin"}, + {66, "Sloth"}, + {67, "Hermione Granger"}, + {68, "Chase McCain"}, + {69, "Excalibur Batman"}, + {70, "Raven"}, + {71, "Beast Boy"}, + {72, "Betelgeuse"}, + {73, "Lord Vortech (unreleased)"}, + {74, "Blossom"}, + {75, "Bubbles"}, + {76, "Buttercup"}, + {77, "Starfire"}, + {78, "World 15 (unreleased)"}, + {79, "World 16 (unreleased)"}, + {80, "World 17 (unreleased)"}, + {81, "World 18 (unreleased)"}, + {82, "World 19 (unreleased)"}, + {83, "World 20 (unreleased)"}, + {768, "Unknown 768"}, + {769, "Supergirl Red Lantern"}, + {770, "Unknown 770"}}; + + const std::map<const uint32, const char*> s_listTokens = { + {1000, "Police Car"}, + {1001, "Aerial Squad Car"}, + {1002, "Missile Striker"}, + {1003, "Gravity Sprinter"}, + {1004, "Street Shredder"}, + {1005, "Sky Clobberer"}, + {1006, "Batmobile"}, + {1007, "Batblaster"}, + {1008, "Sonic Batray"}, + {1009, "Benny's Spaceship"}, + {1010, "Lasercraft"}, + {1011, "The Annihilator"}, + {1012, "DeLorean Time Machine"}, + {1013, "Electric Time Machine"}, + {1014, "Ultra Time Machine"}, + {1015, "Hoverboard"}, + {1016, "Cyclone Board"}, + {1017, "Ultimate Hoverjet"}, + {1018, "Eagle Interceptor"}, + {1019, "Eagle Sky Blazer"}, + {1020, "Eagle Swoop Diver"}, + {1021, "Swamp Skimmer"}, + {1022, "Cragger's Fireship"}, + {1023, "Croc Command Sub"}, + {1024, "Cyber-Guard"}, + {1025, "Cyber-Wrecker"}, + {1026, "Laser Robot Walker"}, + {1027, "K-9"}, + {1028, "K-9 Ruff Rover"}, + {1029, "K-9 Laser Cutter"}, + {1030, "TARDIS"}, + {1031, "Laser-Pulse TARDIS"}, + {1032, "Energy-Burst TARDIS"}, + {1033, "Emmet's Excavator"}, + {1034, "Destroy Dozer"}, + {1035, "Destruct-o-Mech"}, + {1036, "Winged Monkey"}, + {1037, "Battle Monkey"}, + {1038, "Commander Monkey"}, + {1039, "Axe Chariot"}, + {1040, "Axe Hurler"}, + {1041, "Soaring Chariot"}, + {1042, "Shelob the Great"}, + {1043, "8-Legged Stalker"}, + {1044, "Poison Slinger"}, + {1045, "Homer's Car"}, + {1047, "The SubmaHomer"}, + {1046, "The Homercraft"}, + {1048, "Taunt-o-Vision"}, + {1050, "The MechaHomer"}, + {1049, "Blast Cam"}, + {1051, "Velociraptor"}, + {1053, "Venom Raptor"}, + {1052, "Spike Attack Raptor"}, + {1054, "Gyrosphere"}, + {1055, "Sonic Beam Gyrosphere"}, + {1056, " Gyrosphere"}, + {1057, "Clown Bike"}, + {1058, "Cannon Bike"}, + {1059, "Anti-Gravity Rocket Bike"}, + {1060, "Mighty Lion Rider"}, + {1061, "Lion Blazer"}, + {1062, "Fire Lion"}, + {1063, "Arrow Launcher"}, + {1064, "Seeking Shooter"}, + {1065, "Triple Ballista"}, + {1066, "Mystery Machine"}, + {1067, "Mystery Tow & Go"}, + {1068, "Mystery Monster"}, + {1069, "Boulder Bomber"}, + {1070, "Boulder Blaster"}, + {1071, "Cyclone Jet"}, + {1072, "Storm Fighter"}, + {1073, "Lightning Jet"}, + {1074, "Electro-Shooter"}, + {1075, "Blade Bike"}, + {1076, "Flight Fire Bike"}, + {1077, "Blades of Fire"}, + {1078, "Samurai Mech"}, + {1079, "Samurai Shooter"}, + {1080, "Soaring Samurai Mech"}, + {1081, "Companion Cube"}, + {1082, "Laser Deflector"}, + {1083, "Gold Heart Emitter"}, + {1084, "Sentry Turret"}, + {1085, "Turret Striker"}, + {1086, "Flight Turret Carrier"}, + {1087, "Scooby Snack"}, + {1088, "Scooby Fire Snack"}, + {1089, "Scooby Ghost Snack"}, + {1090, "Cloud Cuckoo Car"}, + {1091, "X-Stream Soaker"}, + {1092, "Rainbow Cannon"}, + {1093, "Invisible Jet"}, + {1094, "Laser Shooter"}, + {1095, "Torpedo Bomber"}, + {1096, "NinjaCopter"}, + {1097, "Glaciator"}, + {1098, "Freeze Fighter"}, + {1099, "Travelling Time Train"}, + {1100, "Flight Time Train"}, + {1101, "Missile Blast Time Train"}, + {1102, "Aqua Watercraft"}, + {1103, "Seven Seas Speeder"}, + {1104, "Trident of Fire"}, + {1105, "Drill Driver"}, + {1106, "Bane Dig 'n' Drill"}, + {1107, "Bane Drill 'n' Blast"}, + {1108, "Quinn Mobile"}, + {1109, "Quinn Ultra Racer"}, + {1110, "Missile Launcher"}, + {1111, "The Joker's Chopper"}, + {1112, "Mischievous Missile Blaster"}, + {1113, "Lock 'n' Laser Jet"}, + {1114, "Hover Pod"}, + {1115, "Krypton Striker"}, + {1116, "Super Stealth Pod"}, + {1117, "Dalek"}, + {1118, "Fire 'n' Ride Dalek"}, + {1119, "Silver Shooter Dalek"}, + {1120, "Ecto-1"}, + {1121, "Ecto-1 Blaster"}, + {1122, "Ecto-1 Water Diver"}, + {1123, "Ghost Trap"}, + {1124, "Ghost Stun 'n' Trap"}, + {1125, "Proton Zapper"}, + {1126, "Unknown"}, + {1127, "Unknown"}, + {1128, "Unknown"}, + {1129, "Unknown"}, + {1130, "Unknown"}, + {1131, "Unknown"}, + {1132, "Lloyd's Golden Dragon"}, + {1133, "Sword Projector Dragon"}, + {1134, "Unknown"}, + {1135, "Unknown"}, + {1136, "Unknown"}, + {1137, "Unknown"}, + {1138, "Unknown"}, + {1139, "Unknown"}, + {1140, "Unknown"}, + {1141, "Unknown"}, + {1142, "Unknown"}, + {1143, "Unknown"}, + {1144, "Mega Flight Dragon"}, + {1145, "Unknown"}, + {1146, "Unknown"}, + {1147, "Unknown"}, + {1148, "Unknown"}, + {1149, "Unknown"}, + {1150, "Unknown"}, + {1151, "Unknown"}, + {1152, "Unknown"}, + {1153, "Unknown"}, + {1154, "Unknown"}, + {1155, "Flying White Dragon"}, + {1156, "Golden Fire Dragon"}, + {1157, "Ultra Destruction Dragon"}, + {1158, "Arcade Machine"}, + {1159, "8-Bit Shooter"}, + {1160, "The Pixelator"}, + {1161, "G-6155 Spy Hunter"}, + {1162, "Interdiver"}, + {1163, "Aerial Spyhunter"}, + {1164, "Slime Shooter"}, + {1165, "Slime Exploder"}, + {1166, "Slime Streamer"}, + {1167, "Terror Dog"}, + {1168, "Terror Dog Destroyer"}, + {1169, "Soaring Terror Dog"}, + {1170, "Ancient Psychic Tandem War Elephant"}, + {1171, "Cosmic Squid"}, + {1172, "Psychic Submarine"}, + {1173, "BMO"}, + {1174, "DOGMO"}, + {1175, "SNAKEMO"}, + {1176, "Jakemobile"}, + {1177, "Snail Dude Jake"}, + {1178, "Hover Jake"}, + {1179, "Lumpy Car"}, + {1181, "Lumpy Land Whale"}, + {1180, "Lumpy Truck"}, + {1182, "Lunatic Amp"}, + {1183, "Shadow Scorpion"}, + {1184, "Heavy Metal Monster"}, + {1185, "B.A.'s Van"}, + {1186, "Fool Smasher"}, + {1187, "Pain Plane"}, + {1188, "Phone Home"}, + {1189, "Mobile Uplink"}, + {1190, "Super-Charged Satellite"}, + {1191, "Niffler"}, + {1192, "Sinister Scorpion"}, + {1193, "Vicious Vulture"}, + {1194, "Swooping Evil"}, + {1195, "Brutal Bloom"}, + {1196, "Crawling Creeper"}, + {1197, "Ecto-1 (2016)"}, + {1198, "Ectozer"}, + {1199, "PerfEcto"}, + {1200, "Flash 'n' Finish"}, + {1201, "Rampage Record Player"}, + {1202, "Stripe's Throne"}, + {1203, "R.C. Racer"}, + {1204, "Gadget-O-Matic"}, + {1205, "Scarlet Scorpion"}, + {1206, "Hogwarts Express"}, + {1208, "Steam Warrior"}, + {1207, "Soaring Steam Plane"}, + {1209, "Enchanted Car"}, + {1210, "Shark Sub"}, + {1211, "Monstrous Mouth"}, + {1212, "IMF Scrambler"}, + {1213, "Shock Cycle"}, + {1214, "IMF Covert Jet"}, + {1215, "IMF Sports Car"}, + {1216, "IMF Tank"}, + {1217, "IMF Splorer"}, + {1218, "Sonic Speedster"}, + {1219, "Blue Typhoon"}, + {1220, "Moto Bug"}, + {1221, "The Tornado"}, + {1222, "Crabmeat"}, + {1223, "Eggcatcher"}, + {1224, "K.I.T.T."}, + {1225, "Goliath Armored Semi"}, + {1226, "K.I.T.T. Jet"}, + {1227, "Police Helicopter"}, + {1228, "Police Hovercraft"}, + {1229, "Police Plane"}, + {1230, "Bionic Steed"}, + {1231, "Bat-Raptor"}, + {1232, "Ultrabat"}, + {1233, "Batwing"}, + {1234, "The Black Thunder"}, + {1235, "Bat-Tank"}, + {1236, "Skeleton Organ"}, + {1237, "Skeleton Jukebox"}, + {1238, "Skele-Turkey"}, + {1239, "One-Eyed Willy's Pirate Ship"}, + {1240, "Fanged Fortune"}, + {1241, "Inferno Cannon"}, + {1242, "Buckbeak"}, + {1243, "Giant Owl"}, + {1244, "Fierce Falcon"}, + {1245, "Saturn's Sandworm"}, + {1247, "Haunted Vacuum"}, + {1246, "Spooky Spider"}, + {1248, "PPG Smartphone"}, + {1249, "PPG Hotline"}, + {1250, "Powerpuff Mag-Net"}, + {1253, "Mega Blast Bot"}, + {1251, "Ka-Pow Cannon"}, + {1252, "Slammin' Guitar"}, + {1254, "Octi"}, + {1255, "Super Skunk"}, + {1256, "Sonic Squid"}, + {1257, "T-Car"}, + {1258, "T-Forklift"}, + {1259, "T-Plane"}, + {1260, "Spellbook of Azarath"}, + {1261, "Raven Wings"}, + {1262, "Giant Hand"}, + {1263, "Titan Robot"}, + {1264, "T-Rocket"}, + {1265, "Robot Retriever"}}; + + DimensionsToypadDevice::DimensionsToypadDevice() + : Device(0x0E6F, 0x0241, 1, 2, 0) + { + m_IsOpened = false; + } + + bool DimensionsToypadDevice::Open() + { + if (!IsOpened()) + { + m_IsOpened = true; + } + return true; + } + + void DimensionsToypadDevice::Close() + { + if (IsOpened()) + { + m_IsOpened = false; + } + } + + bool DimensionsToypadDevice::IsOpened() + { + return m_IsOpened; + } + + Device::ReadResult DimensionsToypadDevice::Read(ReadMessage* message) + { + memcpy(message->data, g_dimensionstoypad.GetStatus().data(), message->length); + message->bytesRead = message->length; + return Device::ReadResult::Success; + } + + Device::WriteResult DimensionsToypadDevice::Write(WriteMessage* message) + { + if (message->length != 32) + return Device::WriteResult::Error; + + g_dimensionstoypad.SendCommand(std::span<const uint8, 32>{message->data, 32}); + message->bytesWritten = message->length; + return Device::WriteResult::Success; + } + + bool DimensionsToypadDevice::GetDescriptor(uint8 descType, + uint8 descIndex, + uint8 lang, + uint8* output, + uint32 outputMaxLength) + { + uint8 configurationDescriptor[0x29]; + + uint8* currentWritePtr; + + // configuration descriptor + currentWritePtr = configurationDescriptor + 0; + *(uint8*)(currentWritePtr + 0) = 9; // bLength + *(uint8*)(currentWritePtr + 1) = 2; // bDescriptorType + *(uint16be*)(currentWritePtr + 2) = 0x0029; // wTotalLength + *(uint8*)(currentWritePtr + 4) = 1; // bNumInterfaces + *(uint8*)(currentWritePtr + 5) = 1; // bConfigurationValue + *(uint8*)(currentWritePtr + 6) = 0; // iConfiguration + *(uint8*)(currentWritePtr + 7) = 0x80; // bmAttributes + *(uint8*)(currentWritePtr + 8) = 0xFA; // MaxPower + currentWritePtr = currentWritePtr + 9; + // configuration descriptor + *(uint8*)(currentWritePtr + 0) = 9; // bLength + *(uint8*)(currentWritePtr + 1) = 0x04; // bDescriptorType + *(uint8*)(currentWritePtr + 2) = 0; // bInterfaceNumber + *(uint8*)(currentWritePtr + 3) = 0; // bAlternateSetting + *(uint8*)(currentWritePtr + 4) = 2; // bNumEndpoints + *(uint8*)(currentWritePtr + 5) = 3; // bInterfaceClass + *(uint8*)(currentWritePtr + 6) = 0; // bInterfaceSubClass + *(uint8*)(currentWritePtr + 7) = 0; // bInterfaceProtocol + *(uint8*)(currentWritePtr + 8) = 0; // iInterface + currentWritePtr = currentWritePtr + 9; + // configuration descriptor + *(uint8*)(currentWritePtr + 0) = 9; // bLength + *(uint8*)(currentWritePtr + 1) = 0x21; // bDescriptorType + *(uint16be*)(currentWritePtr + 2) = 0x0111; // bcdHID + *(uint8*)(currentWritePtr + 4) = 0x00; // bCountryCode + *(uint8*)(currentWritePtr + 5) = 0x01; // bNumDescriptors + *(uint8*)(currentWritePtr + 6) = 0x22; // bDescriptorType + *(uint16be*)(currentWritePtr + 7) = 0x001D; // wDescriptorLength + currentWritePtr = currentWritePtr + 9; + // endpoint descriptor 1 + *(uint8*)(currentWritePtr + 0) = 7; // bLength + *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType + *(uint8*)(currentWritePtr + 2) = 0x81; // bEndpointAddress + *(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes + *(uint16be*)(currentWritePtr + 4) = 0x40; // wMaxPacketSize + *(uint8*)(currentWritePtr + 6) = 0x01; // bInterval + currentWritePtr = currentWritePtr + 7; + // endpoint descriptor 2 + *(uint8*)(currentWritePtr + 0) = 7; // bLength + *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType + *(uint8*)(currentWritePtr + 1) = 0x02; // bEndpointAddress + *(uint8*)(currentWritePtr + 2) = 0x03; // bmAttributes + *(uint16be*)(currentWritePtr + 3) = 0x40; // wMaxPacketSize + *(uint8*)(currentWritePtr + 5) = 0x01; // bInterval + currentWritePtr = currentWritePtr + 7; + + cemu_assert_debug((currentWritePtr - configurationDescriptor) == 0x29); + + memcpy(output, configurationDescriptor, + std::min<uint32>(outputMaxLength, sizeof(configurationDescriptor))); + return true; + } + + bool DimensionsToypadDevice::SetProtocol(uint8 ifIndex, uint8 protocol) + { + cemuLog_log(LogType::Force, "Toypad Protocol"); + return true; + } + + bool DimensionsToypadDevice::SetReport(ReportMessage* message) + { + cemuLog_log(LogType::Force, "Toypad Report"); + return true; + } + + std::array<uint8, 32> DimensionsUSB::GetStatus() + { + std::array<uint8, 32> response = {}; + + bool responded = false; + do + { + if (!m_queries.empty()) + { + response = m_queries.front(); + m_queries.pop(); + responded = true; + } + else if (!m_figureAddedRemovedResponses.empty() && m_isAwake) + { + std::lock_guard lock(m_dimensionsMutex); + response = m_figureAddedRemovedResponses.front(); + m_figureAddedRemovedResponses.pop(); + responded = true; + } + else + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + } + while (responded == false); + return response; + } + + void DimensionsUSB::SendCommand(std::span<const uint8, 32> buf) + { + const uint8 command = buf[2]; + const uint8 sequence = buf[3]; + + std::array<uint8, 32> q_result{}; + + switch (command) + { + case 0xB0: // Wake + { + // Consistent device response to the wake command + q_result = {0x55, 0x0e, 0x01, 0x28, 0x63, 0x29, + 0x20, 0x4c, 0x45, 0x47, 0x4f, 0x20, + 0x32, 0x30, 0x31, 0x34, 0x46}; + break; + } + case 0xB1: // Seed + { + // Initialise a random number generator using the seed provided + g_dimensionstoypad.GenerateRandomNumber(std::span<const uint8, 8>{buf.begin() + 4, 8}, sequence, q_result); + break; + } + case 0xB3: // Challenge + { + // Get the next number in the sequence based on the RNG from 0xB1 command + g_dimensionstoypad.GetChallengeResponse(std::span<const uint8, 8>{buf.begin() + 4, 8}, sequence, q_result); + break; + } + case 0xC0: // Color + case 0xC1: // Get Pad Color + case 0xC2: // Fade + case 0xC3: // Flash + case 0xC4: // Fade Random + case 0xC6: // Fade All + case 0xC7: // Flash All + case 0xC8: // Color All + { + // Send a blank response to acknowledge color has been sent to toypad + q_result = {0x55, 0x01, sequence}; + q_result[3] = GenerateChecksum(q_result, 3); + break; + } + case 0xD2: // Read + { + // Read 4 pages from the figure at index (buf[4]), starting with page buf[5] + g_dimensionstoypad.QueryBlock(buf[4], buf[5], q_result, sequence); + break; + } + case 0xD3: // Write + { + // Write 4 bytes to page buf[5] to the figure at index buf[4] + g_dimensionstoypad.WriteBlock(buf[4], buf[5], std::span<const uint8, 4>{buf.begin() + 6, 4}, q_result, sequence); + break; + } + case 0xD4: // Model + { + // Get the model id of the figure at index buf[4] + g_dimensionstoypad.GetModel(std::span<const uint8, 8>{buf.begin() + 4, 8}, sequence, q_result); + break; + } + case 0xD0: // Tag List + case 0xE1: // PWD + case 0xE5: // Active + case 0xFF: // LEDS Query + { + // Further investigation required + cemuLog_log(LogType::Force, "Unimplemented LD Function: {:x}", command); + break; + } + default: + { + cemuLog_log(LogType::Force, "Unknown LD Function: {:x}", command); + break; + } + } + + m_queries.push(q_result); + } + + uint32 DimensionsUSB::LoadFigure(const std::array<uint8, 0x2D * 0x04>& buf, std::unique_ptr<FileStream> file, uint8 pad, uint8 index) + { + std::lock_guard lock(m_dimensionsMutex); + + const uint32 id = GetFigureId(buf); + + DimensionsMini& figure = GetFigureByIndex(index); + figure.dimFile = std::move(file); + figure.id = id; + figure.pad = pad; + figure.index = index + 1; + figure.data = buf; + // When a figure is added to the toypad, respond to the game with the pad they were added to, their index, + // the direction (0x00 in byte 6 for added) and their UID + std::array<uint8, 32> figureChangeResponse = {0x56, 0x0b, figure.pad, 0x00, figure.index, 0x00, buf[0], buf[1], buf[2], buf[4], buf[5], buf[6], buf[7]}; + figureChangeResponse[13] = GenerateChecksum(figureChangeResponse, 13); + m_figureAddedRemovedResponses.push(figureChangeResponse); + + return id; + } + + bool DimensionsUSB::RemoveFigure(uint8 pad, uint8 index, bool fullRemove) + { + std::lock_guard lock(m_dimensionsMutex); + + DimensionsMini& figure = GetFigureByIndex(index); + if (figure.index == 255) + return false; + + // When a figure is removed from the toypad, respond to the game with the pad they were removed from, their index, + // the direction (0x01 in byte 6 for removed) and their UID + if (fullRemove) + { + std::array<uint8, 32> figureChangeResponse = {0x56, 0x0b, figure.pad, 0x00, figure.index, 0x01, + figure.data[0], figure.data[1], figure.data[2], + figure.data[4], figure.data[5], figure.data[6], figure.data[7]}; + figureChangeResponse[13] = GenerateChecksum(figureChangeResponse, 13); + m_figureAddedRemovedResponses.push(figureChangeResponse); + figure.Save(); + figure.dimFile.reset(); + } + + figure.index = 255; + figure.pad = 255; + figure.id = 0; + + return true; + } + + bool DimensionsUSB::TempRemove(uint8 index) + { + std::lock_guard lock(m_dimensionsMutex); + + DimensionsMini& figure = GetFigureByIndex(index); + if (figure.index == 255) + return false; + + // Send a response to the game that the figure has been "Picked up" from existing slot, + // until either the movement is cancelled, or user chooses a space to move to + std::array<uint8, 32> figureChangeResponse = {0x56, 0x0b, figure.pad, 0x00, figure.index, 0x01, + figure.data[0], figure.data[1], figure.data[2], + figure.data[4], figure.data[5], figure.data[6], figure.data[7]}; + + figureChangeResponse[13] = GenerateChecksum(figureChangeResponse, 13); + m_figureAddedRemovedResponses.push(figureChangeResponse); + } + + bool DimensionsUSB::CancelRemove(uint8 index) + { + std::lock_guard lock(m_dimensionsMutex); + + DimensionsMini& figure = GetFigureByIndex(index); + if (figure.index == 255) + return false; + + // Cancel the previous movement of the figure + std::array<uint8, 32> figureChangeResponse = {0x56, 0x0b, figure.pad, 0x00, figure.index, 0x00, + figure.data[0], figure.data[1], figure.data[2], + figure.data[4], figure.data[5], figure.data[6], figure.data[7]}; + + figureChangeResponse[13] = GenerateChecksum(figureChangeResponse, 13); + m_figureAddedRemovedResponses.push(figureChangeResponse); + + return true; + } + + bool DimensionsUSB::CreateFigure(fs::path pathName, uint32 id) + { + FileStream* dimFile(FileStream::createFile2(pathName)); + if (!dimFile) + return false; + + std::array<uint8, 0x2D * 0x04> fileData{}; + RandomUID(fileData); + fileData[3] = id & 0xFF; + + std::array<uint8, 7> uid = {fileData[0], fileData[1], fileData[2], fileData[4], fileData[5], fileData[6], fileData[7]}; + + // Only characters are created with their ID encrypted and stored in pages 36 and 37, + // as well as a password stored in page 43. Blank tags have their information populated + // by the game when it calls the write_block command. + if (id != 0) + { + const std::array<uint8, 16> figureKey = GenerateFigureKey(fileData); + + std::array<uint8, 8> valueToEncrypt = {uint8(id & 0xFF), uint8((id >> 8) & 0xFF), uint8((id >> 16) & 0xFF), uint8((id >> 24) & 0xFF), + uint8(id & 0xFF), uint8((id >> 8) & 0xFF), uint8((id >> 16) & 0xFF), uint8((id >> 24) & 0xFF)}; + + std::array<uint8, 8> encrypted = Encrypt(valueToEncrypt, figureKey); + + std::memcpy(&fileData[36 * 4], &encrypted[0], 4); + std::memcpy(&fileData[37 * 4], &encrypted[4], 4); + + std::memcpy(&fileData[43 * 4], PWDGenerate(fileData).data(), 4); + } + else + { + // Page 38 is used as verification for blank tags + fileData[(38 * 4) + 1] = 1; + } + + if (fileData.size() != dimFile->writeData(fileData.data(), fileData.size())) + { + delete dimFile; + return false; + } + delete dimFile; + return true; + } + + bool DimensionsUSB::MoveFigure(uint8 pad, uint8 index, uint8 oldPad, uint8 oldIndex) + { + if (oldIndex == index) + { + // Don't bother removing and loading again, just send response to the game + CancelRemove(index); + return true; + } + + // When moving figures between spaces on the toypad, remove any figure from the space they are moving to, + // then remove them from their current space, then load them to the space they are moving to + RemoveFigure(pad, index, true); + + DimensionsMini& figure = GetFigureByIndex(oldIndex); + const std::array<uint8, 0x2D * 0x04> data = figure.data; + std::unique_ptr<FileStream> inFile = std::move(figure.dimFile); + + RemoveFigure(oldPad, oldIndex, false); + + LoadFigure(data, std::move(inFile), pad, index); + + return true; + } + + void DimensionsUSB::GenerateRandomNumber(std::span<const uint8, 8> buf, uint8 sequence, + std::array<uint8, 32>& replyBuf) + { + // Decrypt payload into an 8 byte array + std::array<uint8, 8> value = Decrypt(buf, std::nullopt); + // Seed is the first 4 bytes (little endian) of the decrypted payload + uint32 seed = (uint32&)value[0]; + // Confirmation is the second 4 bytes (big endian) of the decrypted payload + uint32 conf = (uint32be&)value[4]; + // Initialize rng using the seed from decrypted payload + InitializeRNG(seed); + // Encrypt 8 bytes, first 4 bytes is the decrypted confirmation from payload, 2nd 4 bytes are blank + std::array<uint8, 8> valueToEncrypt = {value[4], value[5], value[6], value[7], 0, 0, 0, 0}; + std::array<uint8, 8> encrypted = Encrypt(valueToEncrypt, std::nullopt); + replyBuf[0] = 0x55; + replyBuf[1] = 0x09; + replyBuf[2] = sequence; + // Copy encrypted value to response data + memcpy(&replyBuf[3], encrypted.data(), encrypted.size()); + replyBuf[11] = GenerateChecksum(replyBuf, 11); + } + + void DimensionsUSB::GetChallengeResponse(std::span<const uint8, 8> buf, uint8 sequence, + std::array<uint8, 32>& replyBuf) + { + // Decrypt payload into an 8 byte array + std::array<uint8, 8> value = Decrypt(buf, std::nullopt); + // Confirmation is the first 4 bytes of the decrypted payload + uint32 conf = (uint32be&)value[0]; + // Generate next random number based on RNG + uint32 nextRandom = GetNext(); + // Encrypt an 8 byte array, first 4 bytes are the next random number (little endian) + // followed by the confirmation from the decrypted payload + std::array<uint8, 8> valueToEncrypt = {uint8(nextRandom & 0xFF), uint8((nextRandom >> 8) & 0xFF), + uint8((nextRandom >> 16) & 0xFF), uint8((nextRandom >> 24) & 0xFF), + value[0], value[1], value[2], value[3]}; + std::array<uint8, 8> encrypted = Encrypt(valueToEncrypt, std::nullopt); + replyBuf[0] = 0x55; + replyBuf[1] = 0x09; + replyBuf[2] = sequence; + // Copy encrypted value to response data + memcpy(&replyBuf[3], encrypted.data(), encrypted.size()); + replyBuf[11] = GenerateChecksum(replyBuf, 11); + + if (!m_isAwake) + m_isAwake = true; + } + + void DimensionsUSB::InitializeRNG(uint32 seed) + { + m_randomA = 0xF1EA5EED; + m_randomB = seed; + m_randomC = seed; + m_randomD = seed; + + for (int i = 0; i < 42; i++) + { + GetNext(); + } + } + + uint32 DimensionsUSB::GetNext() + { + uint32 e = m_randomA - std::rotl(m_randomB, 21); + m_randomA = m_randomB ^ std::rotl(m_randomC, 19); + m_randomB = m_randomC + std::rotl(m_randomD, 6); + m_randomC = m_randomD + e; + m_randomD = e + m_randomA; + return m_randomD; + } + + std::array<uint8, 8> DimensionsUSB::Decrypt(std::span<const uint8, 8> buf, std::optional<std::array<uint8, 16>> key) + { + // Value to decrypt is separated in to two little endian 32 bit unsigned integers + uint32 dataOne = (uint32&)buf[0]; + uint32 dataTwo = (uint32&)buf[4]; + + // Use the key as 4 32 bit little endian unsigned integers + uint32 keyOne; + uint32 keyTwo; + uint32 keyThree; + uint32 keyFour; + + if (key) + { + keyOne = (uint32&)key.value()[0]; + keyTwo = (uint32&)key.value()[4]; + keyThree = (uint32&)key.value()[8]; + keyFour = (uint32&)key.value()[12]; + } + else + { + keyOne = (uint32&)COMMAND_KEY[0]; + keyTwo = (uint32&)COMMAND_KEY[4]; + keyThree = (uint32&)COMMAND_KEY[8]; + keyFour = (uint32&)COMMAND_KEY[12]; + } + + uint32 sum = 0xC6EF3720; + uint32 delta = 0x9E3779B9; + + for (int i = 0; i < 32; i++) + { + dataTwo -= (((dataOne << 4) + keyThree) ^ (dataOne + sum) ^ ((dataOne >> 5) + keyFour)); + dataOne -= (((dataTwo << 4) + keyOne) ^ (dataTwo + sum) ^ ((dataTwo >> 5) + keyTwo)); + sum -= delta; + } + + cemu_assert(sum == 0); + + std::array<uint8, 8> decrypted = {uint8(dataOne & 0xFF), uint8((dataOne >> 8) & 0xFF), + uint8((dataOne >> 16) & 0xFF), uint8((dataOne >> 24) & 0xFF), + uint8(dataTwo & 0xFF), uint8((dataTwo >> 8) & 0xFF), + uint8((dataTwo >> 16) & 0xFF), uint8((dataTwo >> 24) & 0xFF)}; + return decrypted; + } + std::array<uint8, 8> DimensionsUSB::Encrypt(std::span<const uint8, 8> buf, std::optional<std::array<uint8, 16>> key) + { + // Value to encrypt is separated in to two little endian 32 bit unsigned integers + uint32 dataOne = (uint32&)buf[0]; + uint32 dataTwo = (uint32&)buf[4]; + + // Use the key as 4 32 bit little endian unsigned integers + uint32 keyOne; + uint32 keyTwo; + uint32 keyThree; + uint32 keyFour; + + if (key) + { + keyOne = (uint32&)key.value()[0]; + keyTwo = (uint32&)key.value()[4]; + keyThree = (uint32&)key.value()[8]; + keyFour = (uint32&)key.value()[12]; + } + else + { + keyOne = (uint32&)COMMAND_KEY[0]; + keyTwo = (uint32&)COMMAND_KEY[4]; + keyThree = (uint32&)COMMAND_KEY[8]; + keyFour = (uint32&)COMMAND_KEY[12]; + } + + uint32 sum = 0; + uint32 delta = 0x9E3779B9; + + for (int i = 0; i < 32; i++) + { + sum += delta; + dataOne += (((dataTwo << 4) + keyOne) ^ (dataTwo + sum) ^ ((dataTwo >> 5) + keyTwo)); + dataTwo += (((dataOne << 4) + keyThree) ^ (dataOne + sum) ^ ((dataOne >> 5) + keyFour)); + } + + cemu_assert(sum == 0xC6EF3720); + + std::array<uint8, 8> encrypted = {uint8(dataOne & 0xFF), uint8((dataOne >> 8) & 0xFF), + uint8((dataOne >> 16) & 0xFF), uint8((dataOne >> 24) & 0xFF), + uint8(dataTwo & 0xFF), uint8((dataTwo >> 8) & 0xFF), + uint8((dataTwo >> 16) & 0xFF), uint8((dataTwo >> 24) & 0xFF)}; + return encrypted; + } + + std::array<uint8, 16> DimensionsUSB::GenerateFigureKey(const std::array<uint8, 0x2D * 0x04>& buf) + { + std::array<uint8, 7> uid = {buf[0], buf[1], buf[2], buf[4], buf[5], buf[6], buf[7]}; + + uint32 scrambleA = Scramble(uid, 3); + uint32 scrambleB = Scramble(uid, 4); + uint32 scrambleC = Scramble(uid, 5); + uint32 scrambleD = Scramble(uid, 6); + + return {uint8((scrambleA >> 24) & 0xFF), uint8((scrambleA >> 16) & 0xFF), + uint8((scrambleA >> 8) & 0xFF), uint8(scrambleA & 0xFF), + uint8((scrambleB >> 24) & 0xFF), uint8((scrambleB >> 16) & 0xFF), + uint8((scrambleB >> 8) & 0xFF), uint8(scrambleB & 0xFF), + uint8((scrambleC >> 24) & 0xFF), uint8((scrambleC >> 16) & 0xFF), + uint8((scrambleC >> 8) & 0xFF), uint8(scrambleC & 0xFF), + uint8((scrambleD >> 24) & 0xFF), uint8((scrambleD >> 16) & 0xFF), + uint8((scrambleD >> 8) & 0xFF), uint8(scrambleD & 0xFF)}; + } + + uint32 DimensionsUSB::Scramble(const std::array<uint8, 7>& uid, uint8 count) + { + std::vector<uint8> toScramble; + toScramble.reserve(uid.size() + CHAR_CONSTANT.size()); + for (uint8 x : uid) + { + toScramble.push_back(x); + } + for (uint8 c : CHAR_CONSTANT) + { + toScramble.push_back(c); + } + toScramble[(count * 4) - 1] = 0xaa; + + std::array<uint8, 4> randomized = DimensionsRandomize(toScramble, count); + + return (uint32be&)randomized[0]; + } + + std::array<uint8, 4> DimensionsUSB::PWDGenerate(const std::array<uint8, 0x2D * 0x04>& buf) + { + std::array<uint8, 7> uid = {buf[0], buf[1], buf[2], buf[4], buf[5], buf[6], buf[7]}; + + std::vector<uint8> pwdCalc = {PWD_CONSTANT.begin(), PWD_CONSTANT.end() - 1}; + for (uint8 i = 0; i < uid.size(); i++) + { + pwdCalc.insert(pwdCalc.begin() + i, uid[i]); + } + + return DimensionsRandomize(pwdCalc, 8); + } + + std::array<uint8, 4> DimensionsUSB::DimensionsRandomize(const std::vector<uint8> key, uint8 count) + { + uint32 scrambled = 0; + for (uint8 i = 0; i < count; i++) + { + const uint32 v4 = std::rotr(scrambled, 25); + const uint32 v5 = std::rotr(scrambled, 10); + const uint32 b = (uint32&)key[i * 4]; + scrambled = b + v4 + v5 - scrambled; + } + return {uint8(scrambled & 0xFF), uint8(scrambled >> 8 & 0xFF), uint8(scrambled >> 16 & 0xFF), uint8(scrambled >> 24 & 0xFF)}; + } + + uint32 DimensionsUSB::GetFigureId(const std::array<uint8, 0x2D * 0x04>& buf) + { + const std::array<uint8, 16> figureKey = GenerateFigureKey(buf); + + const std::span<const uint8, 8> modelNumber = std::span<const uint8, 8>{buf.begin() + (36 * 4), 8}; + + const std::array<uint8, 8> decrypted = Decrypt(modelNumber, figureKey); + + const uint32 figNum = (uint32&)decrypted[0]; + // Characters have their model number encrypted in page 36 + if (figNum < 1000) + { + return figNum; + } + // Vehicles/Gadgets have their model number written as little endian in page 36 + return (uint32&)modelNumber[0]; + } + + DimensionsUSB::DimensionsMini& + DimensionsUSB::GetFigureByIndex(uint8 index) + { + return m_figures[index]; + } + + void DimensionsUSB::QueryBlock(uint8 index, uint8 page, + std::array<uint8, 32>& replyBuf, + uint8 sequence) + { + std::lock_guard lock(m_dimensionsMutex); + + replyBuf[0] = 0x55; + replyBuf[1] = 0x12; + replyBuf[2] = sequence; + replyBuf[3] = 0x00; + + // Index from game begins at 1 rather than 0, so minus 1 here + if (const uint8 figureIndex = index - 1; figureIndex < 7) + { + const DimensionsMini& figure = GetFigureByIndex(figureIndex); + + // Query 4 pages of 4 bytes from the figure, copy this to the response + if (figure.index != 255 && (4 * page) < ((0x2D * 4) - 16)) + { + std::memcpy(&replyBuf[4], figure.data.data() + (4 * page), 16); + } + } + replyBuf[20] = GenerateChecksum(replyBuf, 20); + } + + void DimensionsUSB::WriteBlock(uint8 index, uint8 page, std::span<const uint8, 4> toWriteBuf, + std::array<uint8, 32>& replyBuf, uint8 sequence) + { + std::lock_guard lock(m_dimensionsMutex); + + replyBuf[0] = 0x55; + replyBuf[1] = 0x02; + replyBuf[2] = sequence; + replyBuf[3] = 0x00; + + // Index from game begins at 1 rather than 0, so minus 1 here + if (const uint8 figureIndex = index - 1; figureIndex < 7) + { + DimensionsMini& figure = GetFigureByIndex(figureIndex); + + // Copy 4 bytes to the page on the figure requested by the game + if (figure.index != 255 && page < 0x2D) + { + // Id is written to page 36 + if (page == 36) + { + figure.id = (uint32&)toWriteBuf[0]; + } + std::memcpy(figure.data.data() + (page * 4), toWriteBuf.data(), 4); + figure.Save(); + } + } + replyBuf[4] = GenerateChecksum(replyBuf, 4); + } + + void DimensionsUSB::GetModel(std::span<const uint8, 8> buf, uint8 sequence, + std::array<uint8, 32>& replyBuf) + { + // Decrypt payload to 8 byte array, byte 1 is the index, 4-7 are the confirmation + std::array<uint8, 8> value = Decrypt(buf, std::nullopt); + uint8 index = value[0]; + uint32 conf = (uint32be&)value[4]; + // Response is the figure's id (little endian) followed by the confirmation from payload + // Index from game begins at 1 rather than 0, so minus 1 here + std::array<uint8, 8> valueToEncrypt = {}; + if (const uint8 figureIndex = index - 1; figureIndex < 7) + { + const DimensionsMini& figure = GetFigureByIndex(figureIndex); + valueToEncrypt = {uint8(figure.id & 0xFF), uint8((figure.id >> 8) & 0xFF), + uint8((figure.id >> 16) & 0xFF), uint8((figure.id >> 24) & 0xFF), + value[4], value[5], value[6], value[7]}; + } + std::array<uint8, 8> encrypted = Encrypt(valueToEncrypt, std::nullopt); + replyBuf[0] = 0x55; + replyBuf[1] = 0x0a; + replyBuf[2] = sequence; + replyBuf[3] = 0x00; + memcpy(&replyBuf[4], encrypted.data(), encrypted.size()); + replyBuf[12] = GenerateChecksum(replyBuf, 12); + } + + void DimensionsUSB::RandomUID(std::array<uint8, 0x2D * 0x04>& uid_buffer) + { + uid_buffer[0] = 0x04; + uid_buffer[7] = 0x80; + + std::random_device rd; + std::mt19937 mt(rd()); + std::uniform_int_distribution<int> dist(0, 255); + + uid_buffer[1] = dist(mt); + uid_buffer[2] = dist(mt); + uid_buffer[4] = dist(mt); + uid_buffer[5] = dist(mt); + uid_buffer[6] = dist(mt); + } + + uint8 DimensionsUSB::GenerateChecksum(const std::array<uint8, 32>& data, + int num_of_bytes) const + { + int checksum = 0; + for (int i = 0; i < num_of_bytes; i++) + { + checksum += data[i]; + } + return (checksum & 0xFF); + } + + void DimensionsUSB::DimensionsMini::Save() + { + if (!dimFile) + return; + + dimFile->SetPosition(0); + dimFile->writeData(data.data(), data.size()); + } + + std::map<const uint32, const char*> DimensionsUSB::GetListMinifigs() + { + return s_listMinis; + } + + std::map<const uint32, const char*> DimensionsUSB::GetListTokens() + { + return s_listTokens; + } + + std::string DimensionsUSB::FindFigure(uint32 figNum) + { + for (const auto& it : GetListMinifigs()) + { + if (it.first == figNum) + { + return it.second; + } + } + for (const auto& it : GetListTokens()) + { + if (it.first == figNum) + { + return it.second; + } + } + return fmt::format("Unknown ({})", figNum); + } +} // namespace nsyshid \ No newline at end of file diff --git a/src/Cafe/OS/libs/nsyshid/Dimensions.h b/src/Cafe/OS/libs/nsyshid/Dimensions.h new file mode 100644 index 00000000..d5a2a529 --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/Dimensions.h @@ -0,0 +1,108 @@ +#include <mutex> + +#include "nsyshid.h" +#include "Backend.h" + +#include "Common/FileStream.h" + +namespace nsyshid +{ + class DimensionsToypadDevice final : public Device + { + public: + DimensionsToypadDevice(); + ~DimensionsToypadDevice() = default; + + bool Open() override; + + void Close() override; + + bool IsOpened() override; + + ReadResult Read(ReadMessage* message) override; + + WriteResult Write(WriteMessage* message) override; + + bool GetDescriptor(uint8 descType, + uint8 descIndex, + uint8 lang, + uint8* output, + uint32 outputMaxLength) override; + + bool SetProtocol(uint8 ifIndex, uint8 protocol) override; + + bool SetReport(ReportMessage* message) override; + + private: + bool m_IsOpened; + }; + + class DimensionsUSB + { + public: + struct DimensionsMini final + { + std::unique_ptr<FileStream> dimFile; + std::array<uint8, 0x2D * 0x04> data{}; + uint8 index = 255; + uint8 pad = 255; + uint32 id = 0; + void Save(); + }; + + void SendCommand(std::span<const uint8, 32> buf); + std::array<uint8, 32> GetStatus(); + + void GenerateRandomNumber(std::span<const uint8, 8> buf, uint8 sequence, + std::array<uint8, 32>& replyBuf); + void InitializeRNG(uint32 seed); + void GetChallengeResponse(std::span<const uint8, 8> buf, uint8 sequence, + std::array<uint8, 32>& replyBuf); + void QueryBlock(uint8 index, uint8 page, std::array<uint8, 32>& replyBuf, + uint8 sequence); + void WriteBlock(uint8 index, uint8 page, std::span<const uint8, 4> toWriteBuf, std::array<uint8, 32>& replyBuf, + uint8 sequence); + void GetModel(std::span<const uint8, 8> buf, uint8 sequence, + std::array<uint8, 32>& replyBuf); + + bool RemoveFigure(uint8 pad, uint8 index, bool fullRemove); + bool TempRemove(uint8 index); + bool CancelRemove(uint8 index); + uint32 LoadFigure(const std::array<uint8, 0x2D * 0x04>& buf, std::unique_ptr<FileStream> file, uint8 pad, uint8 index); + bool CreateFigure(fs::path pathName, uint32 id); + bool MoveFigure(uint8 pad, uint8 index, uint8 oldPad, uint8 oldIndex); + static std::map<const uint32, const char*> GetListMinifigs(); + static std::map<const uint32, const char*> GetListTokens(); + std::string FindFigure(uint32 figNum); + + protected: + std::mutex m_dimensionsMutex; + std::array<DimensionsMini, 7> m_figures{}; + + private: + void RandomUID(std::array<uint8, 0x2D * 0x04>& uidBuffer); + uint8 GenerateChecksum(const std::array<uint8, 32>& data, + int numOfBytes) const; + std::array<uint8, 8> Decrypt(std::span<const uint8, 8> buf, std::optional<std::array<uint8, 16>> key); + std::array<uint8, 8> Encrypt(std::span<const uint8, 8> buf, std::optional<std::array<uint8, 16>> key); + std::array<uint8, 16> GenerateFigureKey(const std::array<uint8, 0x2D * 0x04>& uid); + std::array<uint8, 4> PWDGenerate(const std::array<uint8, 0x2D * 0x04>& uid); + std::array<uint8, 4> DimensionsRandomize(const std::vector<uint8> key, uint8 count); + uint32 GetFigureId(const std::array<uint8, 0x2D * 0x04>& buf); + uint32 Scramble(const std::array<uint8, 7>& uid, uint8 count); + uint32 GetNext(); + DimensionsMini& GetFigureByIndex(uint8 index); + + uint32 m_randomA; + uint32 m_randomB; + uint32 m_randomC; + uint32 m_randomD; + + bool m_isAwake = false; + + std::queue<std::array<uint8, 32>> m_figureAddedRemovedResponses; + std::queue<std::array<uint8, 32>> m_queries; + }; + extern DimensionsUSB g_dimensionstoypad; + +} // namespace nsyshid \ No newline at end of file diff --git a/src/config/CemuConfig.cpp b/src/config/CemuConfig.cpp index e7920e84..f5ee7ab4 100644 --- a/src/config/CemuConfig.cpp +++ b/src/config/CemuConfig.cpp @@ -346,6 +346,7 @@ void CemuConfig::Load(XMLConfigParser& parser) auto usbdevices = parser.get("EmulatedUsbDevices"); emulated_usb_devices.emulate_skylander_portal = usbdevices.get("EmulateSkylanderPortal", emulated_usb_devices.emulate_skylander_portal); emulated_usb_devices.emulate_infinity_base = usbdevices.get("EmulateInfinityBase", emulated_usb_devices.emulate_infinity_base); + emulated_usb_devices.emulate_dimensions_toypad = usbdevices.get("EmulateDimensionsToypad", emulated_usb_devices.emulate_dimensions_toypad); } void CemuConfig::Save(XMLConfigParser& parser) @@ -545,6 +546,7 @@ void CemuConfig::Save(XMLConfigParser& parser) auto usbdevices = config.set("EmulatedUsbDevices"); usbdevices.set("EmulateSkylanderPortal", emulated_usb_devices.emulate_skylander_portal.GetValue()); usbdevices.set("EmulateInfinityBase", emulated_usb_devices.emulate_infinity_base.GetValue()); + usbdevices.set("EmulateDimensionsToypad", emulated_usb_devices.emulate_dimensions_toypad.GetValue()); } GameEntry* CemuConfig::GetGameEntryByTitleId(uint64 titleId) diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index 2f22cd76..be131266 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -521,6 +521,7 @@ struct CemuConfig { ConfigValue<bool> emulate_skylander_portal{false}; ConfigValue<bool> emulate_infinity_base{false}; + ConfigValue<bool> emulate_dimensions_toypad{false}; }emulated_usb_devices{}; private: diff --git a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp index 3a0f534a..c77ae081 100644 --- a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp +++ b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp @@ -1,4 +1,4 @@ -#include "gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h" +#include "EmulatedUSBDeviceFrame.h" #include <algorithm> @@ -8,14 +8,17 @@ #include "util/helpers/helpers.h" #include "Cafe/OS/libs/nsyshid/nsyshid.h" +#include "Cafe/OS/libs/nsyshid/Dimensions.h" #include "Common/FileStream.h" #include <wx/arrstr.h> #include <wx/button.h> +#include <wx/combobox.h> #include <wx/checkbox.h> #include <wx/combobox.h> #include <wx/filedlg.h> +#include <wx/log.h> #include <wx/msgdlg.h> #include <wx/notebook.h> #include <wx/panel.h> @@ -29,7 +32,6 @@ #include <wx/wfstream.h> #include "resource/embedded/resources.h" -#include "EmulatedUSBDeviceFrame.h" EmulatedUSBDeviceFrame::EmulatedUSBDeviceFrame(wxWindow* parent) : wxFrame(parent, wxID_ANY, _("Emulated USB Devices"), wxDefaultPosition, @@ -44,6 +46,7 @@ EmulatedUSBDeviceFrame::EmulatedUSBDeviceFrame(wxWindow* parent) notebook->AddPage(AddSkylanderPage(notebook), _("Skylanders Portal")); notebook->AddPage(AddInfinityPage(notebook), _("Infinity Base")); + notebook->AddPage(AddDimensionsPage(notebook), _("Dimensions Toypad")); sizer->Add(notebook, 1, wxEXPAND | wxALL, 2); @@ -120,8 +123,52 @@ wxPanel* EmulatedUSBDeviceFrame::AddInfinityPage(wxNotebook* notebook) return panel; } -wxBoxSizer* EmulatedUSBDeviceFrame::AddSkylanderRow(uint8 rowNumber, - wxStaticBox* box) +wxPanel* EmulatedUSBDeviceFrame::AddDimensionsPage(wxNotebook* notebook) +{ + auto* panel = new wxPanel(notebook); + auto* panel_sizer = new wxBoxSizer(wxVERTICAL); + auto* box = new wxStaticBox(panel, wxID_ANY, _("Dimensions Manager")); + auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); + + auto* row = new wxBoxSizer(wxHORIZONTAL); + + m_emulateToypad = + new wxCheckBox(box, wxID_ANY, _("Emulate Dimensions Toypad")); + m_emulateToypad->SetValue( + GetConfig().emulated_usb_devices.emulate_dimensions_toypad); + m_emulateToypad->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) { + GetConfig().emulated_usb_devices.emulate_dimensions_toypad = + m_emulateToypad->IsChecked(); + g_config.Save(); + }); + row->Add(m_emulateToypad, 1, wxEXPAND | wxALL, 2); + box_sizer->Add(row, 1, wxEXPAND | wxALL, 2); + auto* top_row = new wxBoxSizer(wxHORIZONTAL); + auto* bottom_row = new wxBoxSizer(wxHORIZONTAL); + + auto* dummy = new wxStaticText(box, wxID_ANY, ""); + + top_row->Add(AddDimensionPanel(2, 0, box), 1, wxEXPAND | wxALL, 2); + top_row->Add(dummy, 1, wxEXPAND | wxLEFT | wxRIGHT, 2); + top_row->Add(AddDimensionPanel(1, 1, box), 1, wxEXPAND | wxALL, 2); + top_row->Add(dummy, 1, wxEXPAND | wxLEFT | wxRIGHT, 2); + top_row->Add(AddDimensionPanel(3, 2, box), 1, wxEXPAND | wxALL, 2); + + bottom_row->Add(AddDimensionPanel(2, 3, box), 1, wxEXPAND | wxALL, 2); + bottom_row->Add(AddDimensionPanel(2, 4, box), 1, wxEXPAND | wxALL, 2); + bottom_row->Add(dummy, 1, wxEXPAND | wxLEFT | wxRIGHT, 0); + bottom_row->Add(AddDimensionPanel(3, 5, box), 1, wxEXPAND | wxALL, 2); + bottom_row->Add(AddDimensionPanel(3, 6, box), 1, wxEXPAND | wxALL, 2); + + box_sizer->Add(top_row, 1, wxEXPAND | wxALL, 2); + box_sizer->Add(bottom_row, 1, wxEXPAND | wxALL, 2); + panel_sizer->Add(box_sizer, 1, wxEXPAND | wxALL, 2); + panel->SetSizerAndFit(panel_sizer); + + return panel; +} + +wxBoxSizer* EmulatedUSBDeviceFrame::AddSkylanderRow(uint8 rowNumber, wxStaticBox* box) { auto* row = new wxBoxSizer(wxHORIZONTAL); @@ -184,6 +231,44 @@ wxBoxSizer* EmulatedUSBDeviceFrame::AddInfinityRow(wxString name, uint8 rowNumbe return row; } +wxBoxSizer* EmulatedUSBDeviceFrame::AddDimensionPanel(uint8 pad, uint8 index, wxStaticBox* box) +{ + auto* panel = new wxBoxSizer(wxVERTICAL); + + auto* combo_row = new wxBoxSizer(wxHORIZONTAL); + m_dimensionSlots[index] = new wxTextCtrl(box, wxID_ANY, _("None"), wxDefaultPosition, wxDefaultSize, + wxTE_READONLY); + combo_row->Add(m_dimensionSlots[index], 1, wxEXPAND | wxALL, 2); + auto* move_button = new wxButton(box, wxID_ANY, _("Move")); + move_button->Bind(wxEVT_BUTTON, [pad, index, this](wxCommandEvent&) { + MoveMinifig(pad, index); + }); + + combo_row->Add(move_button, 1, wxEXPAND | wxALL, 2); + + auto* button_row = new wxBoxSizer(wxHORIZONTAL); + auto* load_button = new wxButton(box, wxID_ANY, _("Load")); + load_button->Bind(wxEVT_BUTTON, [pad, index, this](wxCommandEvent&) { + LoadMinifig(pad, index); + }); + auto* clear_button = new wxButton(box, wxID_ANY, _("Clear")); + clear_button->Bind(wxEVT_BUTTON, [pad, index, this](wxCommandEvent&) { + ClearMinifig(pad, index); + }); + auto* create_button = new wxButton(box, wxID_ANY, _("Create")); + create_button->Bind(wxEVT_BUTTON, [pad, index, this](wxCommandEvent&) { + CreateMinifig(pad, index); + }); + button_row->Add(clear_button, 1, wxEXPAND | wxALL, 2); + button_row->Add(create_button, 1, wxEXPAND | wxALL, 2); + button_row->Add(load_button, 1, wxEXPAND | wxALL, 2); + + panel->Add(combo_row, 1, wxEXPAND | wxALL, 2); + panel->Add(button_row, 1, wxEXPAND | wxALL, 2); + + return panel; +} + void EmulatedUSBDeviceFrame::LoadSkylander(uint8 slot) { wxFileDialog openFileDialog(this, _("Open Skylander dump"), "", "", @@ -307,8 +392,8 @@ CreateSkylanderDialog::CreateSkylanderDialog(wxWindow* parent, uint8 slot) return; m_filePath = saveFileDialog.GetPath(); - - if(!nsyshid::g_skyportal.CreateSkylander(_utf8ToPath(m_filePath.utf8_string()), skyId, skyVar)) + + if (!nsyshid::g_skyportal.CreateSkylander(_utf8ToPath(m_filePath.utf8_string()), skyId, skyVar)) { wxMessageDialog errorMessage(this, "Failed to create file"); errorMessage.ShowModal(); @@ -351,6 +436,80 @@ wxString CreateSkylanderDialog::GetFilePath() const return m_filePath; } +void EmulatedUSBDeviceFrame::UpdateSkylanderEdits() +{ + for (auto i = 0; i < nsyshid::MAX_SKYLANDERS; i++) + { + std::string displayString; + if (auto sd = m_skySlots[i]) + { + auto [portalSlot, skyId, skyVar] = sd.value(); + displayString = nsyshid::g_skyportal.FindSkylander(skyId, skyVar); + } + else + { + displayString = "None"; + } + + m_skylanderSlots[i]->ChangeValue(displayString); + } +} + +void EmulatedUSBDeviceFrame::LoadFigure(uint8 slot) +{ + wxFileDialog openFileDialog(this, _("Open Infinity Figure dump"), "", "", + "BIN files (*.bin)|*.bin", + wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (openFileDialog.ShowModal() != wxID_OK || openFileDialog.GetPath().empty()) + { + wxMessageDialog errorMessage(this, "File Okay Error"); + errorMessage.ShowModal(); + return; + } + + LoadFigurePath(slot, openFileDialog.GetPath()); +} + +void EmulatedUSBDeviceFrame::LoadFigurePath(uint8 slot, wxString path) +{ + std::unique_ptr<FileStream> infFile(FileStream::openFile2(_utf8ToPath(path.utf8_string()), true)); + if (!infFile) + { + wxMessageDialog errorMessage(this, "File Open Error"); + errorMessage.ShowModal(); + return; + } + + std::array<uint8, nsyshid::INF_FIGURE_SIZE> fileData; + if (infFile->readData(fileData.data(), fileData.size()) != fileData.size()) + { + wxMessageDialog open_error(this, "Failed to read file! File was too small"); + open_error.ShowModal(); + return; + } + ClearFigure(slot); + + uint32 number = nsyshid::g_infinitybase.LoadFigure(fileData, std::move(infFile), slot); + m_infinitySlots[slot]->ChangeValue(nsyshid::g_infinitybase.FindFigure(number).second); +} + +void EmulatedUSBDeviceFrame::CreateFigure(uint8 slot) +{ + cemuLog_log(LogType::Force, "Create Figure: {}", slot); + CreateInfinityFigureDialog create_dlg(this, slot); + create_dlg.ShowModal(); + if (create_dlg.GetReturnCode() == 1) + { + LoadFigurePath(slot, create_dlg.GetFilePath()); + } +} + +void EmulatedUSBDeviceFrame::ClearFigure(uint8 slot) +{ + m_infinitySlots[slot]->ChangeValue("None"); + nsyshid::g_infinitybase.RemoveFigure(slot); +} + CreateInfinityFigureDialog::CreateInfinityFigureDialog(wxWindow* parent, uint8 slot) : wxDialog(parent, wxID_ANY, _("Infinity Figure Creator"), wxDefaultPosition, wxSize(500, 150)) { @@ -447,76 +606,231 @@ wxString CreateInfinityFigureDialog::GetFilePath() const return m_filePath; } -void EmulatedUSBDeviceFrame::LoadFigure(uint8 slot) +void EmulatedUSBDeviceFrame::LoadMinifig(uint8 pad, uint8 index) { - wxFileDialog openFileDialog(this, _("Open Infinity Figure dump"), "", "", - "BIN files (*.bin)|*.bin", + wxFileDialog openFileDialog(this, _("Load Dimensions Figure"), "", "", + "Dimensions files (*.bin)|*.bin", wxFD_OPEN | wxFD_FILE_MUST_EXIST); if (openFileDialog.ShowModal() != wxID_OK || openFileDialog.GetPath().empty()) + return; + + LoadMinifigPath(openFileDialog.GetPath(), pad, index); +} + +void EmulatedUSBDeviceFrame::LoadMinifigPath(wxString path_name, uint8 pad, uint8 index) +{ + std::unique_ptr<FileStream> dim_file(FileStream::openFile2(_utf8ToPath(path_name.utf8_string()), true)); + if (!dim_file) { - wxMessageDialog errorMessage(this, "File Okay Error"); + wxMessageDialog errorMessage(this, "Failed to open minifig file"); errorMessage.ShowModal(); return; } - LoadFigurePath(slot, openFileDialog.GetPath()); -} + std::array<uint8, 0x2D * 0x04> file_data; -void EmulatedUSBDeviceFrame::LoadFigurePath(uint8 slot, wxString path) -{ - std::unique_ptr<FileStream> infFile(FileStream::openFile2(_utf8ToPath(path.utf8_string()), true)); - if (!infFile) + if (dim_file->readData(file_data.data(), file_data.size()) != file_data.size()) { - wxMessageDialog errorMessage(this, "File Open Error"); + wxMessageDialog errorMessage(this, "Failed to read minifig file data"); errorMessage.ShowModal(); return; } - std::array<uint8, nsyshid::INF_FIGURE_SIZE> fileData; - if (infFile->readData(fileData.data(), fileData.size()) != fileData.size()) - { - wxMessageDialog open_error(this, "Failed to read file! File was too small"); - open_error.ShowModal(); - return; - } - ClearFigure(slot); + ClearMinifig(pad, index); - uint32 number = nsyshid::g_infinitybase.LoadFigure(fileData, std::move(infFile), slot); - m_infinitySlots[slot]->ChangeValue(nsyshid::g_infinitybase.FindFigure(number).second); + uint32 id = nsyshid::g_dimensionstoypad.LoadFigure(file_data, std::move(dim_file), pad, index); + m_dimensionSlots[index]->ChangeValue(nsyshid::g_dimensionstoypad.FindFigure(id)); + m_dimSlots[index] = id; } -void EmulatedUSBDeviceFrame::CreateFigure(uint8 slot) +void EmulatedUSBDeviceFrame::ClearMinifig(uint8 pad, uint8 index) { - cemuLog_log(LogType::Force, "Create Figure: {}", slot); - CreateInfinityFigureDialog create_dlg(this, slot); + nsyshid::g_dimensionstoypad.RemoveFigure(pad, index, true); + m_dimensionSlots[index]->ChangeValue("None"); + m_dimSlots[index] = std::nullopt; +} + +void EmulatedUSBDeviceFrame::CreateMinifig(uint8 pad, uint8 index) +{ + CreateDimensionFigureDialog create_dlg(this); create_dlg.ShowModal(); if (create_dlg.GetReturnCode() == 1) { - LoadFigurePath(slot, create_dlg.GetFilePath()); + LoadMinifigPath(create_dlg.GetFilePath(), pad, index); } } -void EmulatedUSBDeviceFrame::ClearFigure(uint8 slot) +void EmulatedUSBDeviceFrame::MoveMinifig(uint8 pad, uint8 index) { - m_infinitySlots[slot]->ChangeValue("None"); - nsyshid::g_infinitybase.RemoveFigure(slot); -} + if (!m_dimSlots[index]) + return; -void EmulatedUSBDeviceFrame::UpdateSkylanderEdits() -{ - for (auto i = 0; i < nsyshid::MAX_SKYLANDERS; i++) + MoveDimensionFigureDialog move_dlg(this, index); + nsyshid::g_dimensionstoypad.TempRemove(index); + move_dlg.ShowModal(); + if (move_dlg.GetReturnCode() == 1) { - std::string displayString; - if (auto sd = m_skySlots[i]) + nsyshid::g_dimensionstoypad.MoveFigure(move_dlg.GetNewPad(), move_dlg.GetNewIndex(), pad, index); + if (index != move_dlg.GetNewIndex()) { - auto [portalSlot, skyId, skyVar] = sd.value(); - displayString = nsyshid::g_skyportal.FindSkylander(skyId, skyVar); + m_dimSlots[move_dlg.GetNewIndex()] = m_dimSlots[index]; + m_dimensionSlots[move_dlg.GetNewIndex()]->ChangeValue(m_dimensionSlots[index]->GetValue()); + m_dimSlots[index] = std::nullopt; + m_dimensionSlots[index]->ChangeValue("None"); } - else - { - displayString = "None"; - } - - m_skylanderSlots[i]->ChangeValue(displayString); } + else + { + nsyshid::g_dimensionstoypad.CancelRemove(index); + } +} + +CreateDimensionFigureDialog::CreateDimensionFigureDialog(wxWindow* parent) + : wxDialog(parent, wxID_ANY, _("Dimensions Figure Creator"), wxDefaultPosition, wxSize(500, 200)) +{ + auto* sizer = new wxBoxSizer(wxVERTICAL); + + auto* comboRow = new wxBoxSizer(wxHORIZONTAL); + + auto* comboBox = new wxComboBox(this, wxID_ANY); + comboBox->Append("---Select---", reinterpret_cast<void*>(0xFFFFFFFF)); + wxArrayString filterlist; + for (const auto& it : nsyshid::g_dimensionstoypad.GetListMinifigs()) + { + const uint32 figure = it.first; + comboBox->Append(it.second, reinterpret_cast<void*>(figure)); + filterlist.Add(it.second); + } + comboBox->SetSelection(0); + bool enabled = comboBox->AutoComplete(filterlist); + comboRow->Add(comboBox, 1, wxEXPAND | wxALL, 2); + + auto* figNumRow = new wxBoxSizer(wxHORIZONTAL); + + wxIntegerValidator<uint32> validator; + + auto* labelFigNum = new wxStaticText(this, wxID_ANY, "Figure Number:"); + auto* editFigNum = new wxTextCtrl(this, wxID_ANY, _("0"), wxDefaultPosition, wxDefaultSize, 0, validator); + + figNumRow->Add(labelFigNum, 1, wxALL, 5); + figNumRow->Add(editFigNum, 1, wxALL, 5); + + auto* buttonRow = new wxBoxSizer(wxHORIZONTAL); + + auto* createButton = new wxButton(this, wxID_ANY, _("Create")); + createButton->Bind(wxEVT_BUTTON, [editFigNum, this](wxCommandEvent&) { + long longFigNum; + if (!editFigNum->GetValue().ToLong(&longFigNum) || longFigNum > 0xFFFF) + { + wxMessageDialog idError(this, "Error Converting Figure Number!", "Number Entered is Invalid"); + idError.ShowModal(); + this->EndModal(0); + } + uint16 figNum = longFigNum & 0xFFFF; + auto figure = nsyshid::g_dimensionstoypad.FindFigure(figNum); + wxString predefName = figure + ".bin"; + wxFileDialog + saveFileDialog(this, _("Create Dimensions Figure file"), "", predefName, + "BIN files (*.bin)|*.bin", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + + if (saveFileDialog.ShowModal() == wxID_CANCEL) + this->EndModal(0); + + m_filePath = saveFileDialog.GetPath(); + + nsyshid::g_dimensionstoypad.CreateFigure(_utf8ToPath(m_filePath.utf8_string()), figNum); + + this->EndModal(1); + }); + auto* cancelButton = new wxButton(this, wxID_ANY, _("Cancel")); + cancelButton->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { + this->EndModal(0); + }); + + comboBox->Bind(wxEVT_COMBOBOX, [comboBox, editFigNum, this](wxCommandEvent&) { + const uint64 fig_info = reinterpret_cast<uint64>(comboBox->GetClientData(comboBox->GetSelection())); + if (fig_info != 0xFFFF) + { + const uint16 figNum = fig_info & 0xFFFF; + + editFigNum->SetValue(wxString::Format(wxT("%i"), figNum)); + } + }); + + buttonRow->Add(createButton, 1, wxALL, 5); + buttonRow->Add(cancelButton, 1, wxALL, 5); + + sizer->Add(comboRow, 1, wxEXPAND | wxALL, 2); + sizer->Add(figNumRow, 1, wxEXPAND | wxALL, 2); + sizer->Add(buttonRow, 1, wxEXPAND | wxALL, 2); + + this->SetSizer(sizer); + this->Centre(wxBOTH); +} + +wxString CreateDimensionFigureDialog::GetFilePath() const +{ + return m_filePath; +} + +MoveDimensionFigureDialog::MoveDimensionFigureDialog(EmulatedUSBDeviceFrame* parent, uint8 currentIndex) + : wxDialog(parent, wxID_ANY, _("Dimensions Figure Mover"), wxDefaultPosition, wxSize(700, 300)) +{ + auto* sizer = new wxGridSizer(2, 5, 10, 10); + + std::array<std::optional<uint32>, 7> ids = parent->GetCurrentMinifigs(); + + sizer->Add(AddMinifigSlot(2, 0, currentIndex, ids[0]), 1, wxALL, 5); + sizer->Add(new wxStaticText(this, wxID_ANY, ""), 1, wxALL, 5); + sizer->Add(AddMinifigSlot(1, 1, currentIndex, ids[1]), 1, wxALL, 5); + sizer->Add(new wxStaticText(this, wxID_ANY, ""), 1, wxALL, 5); + sizer->Add(AddMinifigSlot(3, 2, currentIndex, ids[2]), 1, wxALL, 5); + + sizer->Add(AddMinifigSlot(2, 3, currentIndex, ids[3]), 1, wxALL, 5); + sizer->Add(AddMinifigSlot(2, 4, currentIndex, ids[4]), 1, wxALL, 5); + sizer->Add(new wxStaticText(this, wxID_ANY, ""), 1, wxALL, 5); + sizer->Add(AddMinifigSlot(3, 5, currentIndex, ids[5]), 1, wxALL, 5); + sizer->Add(AddMinifigSlot(3, 6, currentIndex, ids[6]), 1, wxALL, 5); + + this->SetSizer(sizer); + this->Centre(wxBOTH); +} + +wxBoxSizer* MoveDimensionFigureDialog::AddMinifigSlot(uint8 pad, uint8 index, uint8 currentIndex, std::optional<uint32> currentId) +{ + auto* panel = new wxBoxSizer(wxVERTICAL); + + auto* label = new wxStaticText(this, wxID_ANY, "None"); + if (currentId) + label->SetLabel(nsyshid::g_dimensionstoypad.FindFigure(currentId.value())); + + auto* moveButton = new wxButton(this, wxID_ANY, _("Move Here")); + if (index == currentIndex) + moveButton->SetLabelText("Pick up and Place"); + + moveButton->Bind(wxEVT_BUTTON, [pad, index, this](wxCommandEvent&) { + m_newPad = pad; + m_newIndex = index; + this->EndModal(1); + }); + + panel->Add(label, 1, wxALL, 5); + panel->Add(moveButton, 1, wxALL, 5); + + return panel; +} + +uint8 MoveDimensionFigureDialog::GetNewPad() const +{ + return m_newPad; +} + +uint8 MoveDimensionFigureDialog::GetNewIndex() const +{ + return m_newIndex; +} + +std::array<std::optional<uint32>, 7> EmulatedUSBDeviceFrame::GetCurrentMinifigs() +{ + return m_dimSlots; } \ No newline at end of file diff --git a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h index ae29a036..78c70a4a 100644 --- a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h +++ b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.h @@ -17,33 +17,47 @@ class wxStaticBox; class wxString; class wxTextCtrl; -class EmulatedUSBDeviceFrame : public wxFrame { +class EmulatedUSBDeviceFrame : public wxFrame +{ public: EmulatedUSBDeviceFrame(wxWindow* parent); ~EmulatedUSBDeviceFrame(); + std::array<std::optional<uint32>, 7> GetCurrentMinifigs(); private: wxCheckBox* m_emulatePortal; wxCheckBox* m_emulateBase; + wxCheckBox* m_emulateToypad; std::array<wxTextCtrl*, nsyshid::MAX_SKYLANDERS> m_skylanderSlots; std::array<wxTextCtrl*, nsyshid::MAX_FIGURES> m_infinitySlots; + std::array<wxTextCtrl*, 7> m_dimensionSlots; std::array<std::optional<std::tuple<uint8, uint16, uint16>>, nsyshid::MAX_SKYLANDERS> m_skySlots; + std::array<std::optional<uint32>, 7> m_dimSlots; wxPanel* AddSkylanderPage(wxNotebook* notebook); wxPanel* AddInfinityPage(wxNotebook* notebook); + wxPanel* AddDimensionsPage(wxNotebook* notebook); wxBoxSizer* AddSkylanderRow(uint8 row_number, wxStaticBox* box); wxBoxSizer* AddInfinityRow(wxString name, uint8 row_number, wxStaticBox* box); + wxBoxSizer* AddDimensionPanel(uint8 pad, uint8 index, wxStaticBox* box); void LoadSkylander(uint8 slot); void LoadSkylanderPath(uint8 slot, wxString path); void CreateSkylander(uint8 slot); void ClearSkylander(uint8 slot); + void UpdateSkylanderEdits(); void LoadFigure(uint8 slot); void LoadFigurePath(uint8 slot, wxString path); void CreateFigure(uint8 slot); void ClearFigure(uint8 slot); - void UpdateSkylanderEdits(); + void LoadMinifig(uint8 pad, uint8 index); + void LoadMinifigPath(wxString path_name, uint8 pad, uint8 index); + void CreateMinifig(uint8 pad, uint8 index); + void ClearMinifig(uint8 pad, uint8 index); + void MoveMinifig(uint8 pad, uint8 index); }; -class CreateSkylanderDialog : public wxDialog { + +class CreateSkylanderDialog : public wxDialog +{ public: explicit CreateSkylanderDialog(wxWindow* parent, uint8 slot); wxString GetFilePath() const; @@ -52,11 +66,37 @@ class CreateSkylanderDialog : public wxDialog { wxString m_filePath; }; -class CreateInfinityFigureDialog : public wxDialog { +class CreateInfinityFigureDialog : public wxDialog +{ public: explicit CreateInfinityFigureDialog(wxWindow* parent, uint8 slot); wxString GetFilePath() const; protected: wxString m_filePath; +}; + +class CreateDimensionFigureDialog : public wxDialog +{ + public: + explicit CreateDimensionFigureDialog(wxWindow* parent); + wxString GetFilePath() const; + + protected: + wxString m_filePath; +}; + +class MoveDimensionFigureDialog : public wxDialog +{ + public: + explicit MoveDimensionFigureDialog(EmulatedUSBDeviceFrame* parent, uint8 currentIndex); + uint8 GetNewPad() const; + uint8 GetNewIndex() const; + + protected: + uint8 m_newIndex = 0; + uint8 m_newPad = 0; + + private: + wxBoxSizer* AddMinifigSlot(uint8 pad, uint8 index, uint8 oldIndex, std::optional<uint32> currentId); }; \ No newline at end of file From a5717e1b11fe2a6c2c1be0afbd4908d765a777af Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Fri, 8 Nov 2024 01:07:53 +0100 Subject: [PATCH 199/299] FST: Refactoring to fix a read bug + verify all reads - Fixes a bug where corrupted data would be returned when reading files from unhashed sections with non-block aligned offset or size - Added hash checks for all reads where possible. This means that FST now can automatically catch corruptions when they are encountered while reading from the volume --- src/Cafe/Filesystem/FST/FST.cpp | 379 ++++++++++++++++++++++---------- src/Cafe/Filesystem/FST/FST.h | 38 +++- 2 files changed, 301 insertions(+), 116 deletions(-) diff --git a/src/Cafe/Filesystem/FST/FST.cpp b/src/Cafe/Filesystem/FST/FST.cpp index 570671d4..f1255778 100644 --- a/src/Cafe/Filesystem/FST/FST.cpp +++ b/src/Cafe/Filesystem/FST/FST.cpp @@ -3,8 +3,7 @@ #include "Cemu/ncrypto/ncrypto.h" #include "Cafe/Filesystem/WUD/wud.h" #include "util/crypto/aes128.h" -#include "openssl/evp.h" /* EVP_Digest */ -#include "openssl/sha.h" /* SHA1 / SHA256_DIGEST_LENGTH */ +#include "openssl/sha.h" /* SHA1 / SHA256 */ #include "fstUtil.h" #include "FST.h" @@ -141,7 +140,7 @@ struct DiscPartitionTableHeader static constexpr uint32 MAGIC_VALUE = 0xCCA6E67B; /* +0x00 */ uint32be magic; - /* +0x04 */ uint32be sectorSize; // must be 0x8000? + /* +0x04 */ uint32be blockSize; // must be 0x8000? /* +0x08 */ uint8 partitionTableHash[20]; // hash of the data range at +0x800 to end of sector (0x8000) /* +0x1C */ uint32be numPartitions; }; @@ -164,10 +163,10 @@ struct DiscPartitionHeader static constexpr uint32 MAGIC_VALUE = 0xCC93A4F5; /* +0x00 */ uint32be magic; - /* +0x04 */ uint32be sectorSize; // must match DISC_SECTOR_SIZE + /* +0x04 */ uint32be sectorSize; // must match DISC_SECTOR_SIZE for hashed blocks /* +0x08 */ uint32be ukn008; - /* +0x0C */ uint32be ukn00C; + /* +0x0C */ uint32be ukn00C; // h3 array size? /* +0x10 */ uint32be h3HashNum; /* +0x14 */ uint32be fstSize; // in bytes /* +0x18 */ uint32be fstSector; // relative to partition start @@ -178,13 +177,15 @@ struct DiscPartitionHeader /* +0x24 */ uint8 fstHashType; /* +0x25 */ uint8 fstEncryptionType; // purpose of this isn't really understood. Maybe it controls which key is being used? (1 -> disc key, 2 -> partition key) - /* +0x26 */ uint8 versionA; - /* +0x27 */ uint8 ukn027; // also a version field? + /* +0x26 */ uint8be versionA; + /* +0x27 */ uint8be ukn027; // also a version field? // there is an array at +0x40 ? Related to H3 list. Also related to value at +0x0C and h3HashNum + /* +0x28 */ uint8be _uknOrPadding028[0x18]; + /* +0x40 */ uint8be h3HashArray[32]; // dynamic size. Only present if fstHashType != 0 }; -static_assert(sizeof(DiscPartitionHeader) == 0x28); +static_assert(sizeof(DiscPartitionHeader) == 0x40+0x20); bool FSTVolume::FindDiscKey(const fs::path& path, NCrypto::AesKey& discTitleKey) { @@ -269,7 +270,7 @@ FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& d cemuLog_log(LogType::Force, "Disc image rejected because decryption failed"); return nullptr; } - if (partitionHeader->sectorSize != DISC_SECTOR_SIZE) + if (partitionHeader->blockSize != DISC_SECTOR_SIZE) { cemuLog_log(LogType::Force, "Disc image rejected because partition sector size is invalid"); return nullptr; @@ -336,6 +337,9 @@ FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& d cemu_assert_debug(partitionHeaderSI.fstEncryptionType == 1); // todo - check other fields? + if(partitionHeaderSI.fstHashType == 0 && partitionHeaderSI.h3HashNum != 0) + cemuLog_log(LogType::Force, "FST: Partition uses unhashed blocks but stores a non-zero amount of H3 hashes"); + // GM partition DiscPartitionHeader partitionHeaderGM{}; if (!readPartitionHeader(partitionHeaderGM, gmPartitionIndex)) @@ -349,9 +353,10 @@ FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& d // if decryption is necessary // load SI FST dataSource->SetBaseOffset((uint64)partitionArray[siPartitionIndex].partitionAddress * DISC_SECTOR_SIZE); - auto siFST = OpenFST(dataSource.get(), (uint64)partitionHeaderSI.fstSector * DISC_SECTOR_SIZE, partitionHeaderSI.fstSize, &discTitleKey, static_cast<FSTVolume::ClusterHashMode>(partitionHeaderSI.fstHashType)); + auto siFST = OpenFST(dataSource.get(), (uint64)partitionHeaderSI.fstSector * DISC_SECTOR_SIZE, partitionHeaderSI.fstSize, &discTitleKey, static_cast<FSTVolume::ClusterHashMode>(partitionHeaderSI.fstHashType), nullptr); if (!siFST) return nullptr; + cemu_assert_debug(!(siFST->HashIsDisabled() && partitionHeaderSI.h3HashNum != 0)); // if hash is disabled, no H3 data may be present // load ticket file for partition that we want to decrypt NCrypto::ETicketParser ticketParser; std::vector<uint8> ticketData = siFST->ExtractFile(fmt::format("{:02x}/title.tik", gmPartitionIndex)); @@ -360,16 +365,32 @@ FSTVolume* FSTVolume::OpenFromDiscImage(const fs::path& path, NCrypto::AesKey& d cemuLog_log(LogType::Force, "Disc image ticket file is invalid"); return nullptr; } +#if 0 + // each SI partition seems to contain a title.tmd that we could parse and which should have information about the associated GM partition + // but the console seems to ignore this file for disc images, at least when mounting, so we shouldn't rely on it either + std::vector<uint8> tmdData = siFST->ExtractFile(fmt::format("{:02x}/title.tmd", gmPartitionIndex)); + if (tmdData.empty()) + { + cemuLog_log(LogType::Force, "Disc image TMD file is missing"); + return nullptr; + } + // parse TMD + NCrypto::TMDParser tmdParser; + if (!tmdParser.parse(tmdData.data(), tmdData.size())) + { + cemuLog_log(LogType::Force, "Disc image TMD file is invalid"); + return nullptr; + } +#endif delete siFST; - NCrypto::AesKey gmTitleKey; ticketParser.GetTitleKey(gmTitleKey); - // load GM partition dataSource->SetBaseOffset((uint64)partitionArray[gmPartitionIndex].partitionAddress * DISC_SECTOR_SIZE); - FSTVolume* r = OpenFST(std::move(dataSource), (uint64)partitionHeaderGM.fstSector * DISC_SECTOR_SIZE, partitionHeaderGM.fstSize, &gmTitleKey, static_cast<FSTVolume::ClusterHashMode>(partitionHeaderGM.fstHashType)); + FSTVolume* r = OpenFST(std::move(dataSource), (uint64)partitionHeaderGM.fstSector * DISC_SECTOR_SIZE, partitionHeaderGM.fstSize, &gmTitleKey, static_cast<FSTVolume::ClusterHashMode>(partitionHeaderGM.fstHashType), nullptr); if (r) SET_FST_ERROR(OK); + cemu_assert_debug(!(r->HashIsDisabled() && partitionHeaderGM.h3HashNum != 0)); // if hash is disabled, no H3 data may be present return r; } @@ -426,15 +447,15 @@ FSTVolume* FSTVolume::OpenFromContentFolder(fs::path folderPath, ErrorCode* erro } // load FST // fstSize = size of first cluster? - FSTVolume* fstVolume = FSTVolume::OpenFST(std::move(dataSource), 0, fstSize, &titleKey, fstHashMode); + FSTVolume* fstVolume = FSTVolume::OpenFST(std::move(dataSource), 0, fstSize, &titleKey, fstHashMode, &tmdParser); if (fstVolume) SET_FST_ERROR(OK); return fstVolume; } -FSTVolume* FSTVolume::OpenFST(FSTDataSource* dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode) +FSTVolume* FSTVolume::OpenFST(FSTDataSource* dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode, NCrypto::TMDParser* optionalTMD) { - cemu_assert_debug(fstHashMode != ClusterHashMode::RAW || fstHashMode != ClusterHashMode::RAW2); + cemu_assert_debug(fstHashMode != ClusterHashMode::RAW || fstHashMode != ClusterHashMode::RAW_STREAM); if (fstSize < sizeof(FSTHeader)) return nullptr; constexpr uint64 FST_CLUSTER_OFFSET = 0; @@ -465,6 +486,34 @@ FSTVolume* FSTVolume::OpenFST(FSTDataSource* dataSource, uint64 fstOffset, uint3 clusterTable[i].offset = clusterDataTable[i].offset; clusterTable[i].size = clusterDataTable[i].size; clusterTable[i].hashMode = static_cast<FSTVolume::ClusterHashMode>((uint8)clusterDataTable[i].hashMode); + clusterTable[i].hasContentHash = false; // from the TMD file (H4?) + } + // if the TMD is available (when opening .app files) we can use the extra info from it to validate unhashed clusters + // each content entry in the TMD corresponds to one cluster used by the FST + if(optionalTMD) + { + if(numCluster != optionalTMD->GetContentList().size()) + { + cemuLog_log(LogType::Force, "FST: Number of clusters does not match TMD content list"); + return nullptr; + } + auto& contentList = optionalTMD->GetContentList(); + for(size_t i=0; i<contentList.size(); i++) + { + auto& cluster = clusterTable[i]; + auto& content = contentList[i]; + cluster.hasContentHash = true; + cluster.contentHashIsSHA1 = HAS_FLAG(contentList[i].contentFlags, NCrypto::TMDParser::TMDContentFlags::FLAG_SHA1); + cluster.contentSize = content.size; + static_assert(sizeof(content.hash32) == sizeof(cluster.contentHash32)); + memcpy(cluster.contentHash32, content.hash32, sizeof(cluster.contentHash32)); + // if unhashed mode, then initialize the hash context + if(cluster.hashMode == ClusterHashMode::RAW || cluster.hashMode == ClusterHashMode::RAW_STREAM) + { + cluster.singleHashCtx.reset(EVP_MD_CTX_new()); + EVP_DigestInit_ex(cluster.singleHashCtx.get(), cluster.contentHashIsSHA1 ? EVP_sha1() : EVP_sha256(), nullptr); + } + } } // preprocess FST table FSTHeader_FileEntry* fileTable = (FSTHeader_FileEntry*)(clusterDataTable + numCluster); @@ -491,16 +540,17 @@ FSTVolume* FSTVolume::OpenFST(FSTDataSource* dataSource, uint64 fstOffset, uint3 fstVolume->m_offsetFactor = fstHeader->offsetFactor; fstVolume->m_sectorSize = DISC_SECTOR_SIZE; fstVolume->m_partitionTitlekey = *partitionTitleKey; - std::swap(fstVolume->m_cluster, clusterTable); - std::swap(fstVolume->m_entries, fstEntries); - std::swap(fstVolume->m_nameStringTable, nameStringTable); + fstVolume->m_hashIsDisabled = fstHeader->hashIsDisabled != 0; + fstVolume->m_cluster = std::move(clusterTable); + fstVolume->m_entries = std::move(fstEntries); + fstVolume->m_nameStringTable = std::move(nameStringTable); return fstVolume; } -FSTVolume* FSTVolume::OpenFST(std::unique_ptr<FSTDataSource> dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode) +FSTVolume* FSTVolume::OpenFST(std::unique_ptr<FSTDataSource> dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode, NCrypto::TMDParser* optionalTMD) { FSTDataSource* ds = dataSource.release(); - FSTVolume* fstVolume = OpenFST(ds, fstOffset, fstSize, partitionTitleKey, fstHashMode); + FSTVolume* fstVolume = OpenFST(ds, fstOffset, fstSize, partitionTitleKey, fstHashMode, optionalTMD); if (!fstVolume) { delete ds; @@ -757,7 +807,7 @@ uint32 FSTVolume::ReadFile(FSTFileHandle& fileHandle, uint32 offset, uint32 size return 0; cemu_assert_debug(!HAS_FLAG(entry.GetFlags(), FSTEntry::FLAGS::FLAG_LINK)); FSTCluster& cluster = m_cluster[entry.fileInfo.clusterIndex]; - if (cluster.hashMode == ClusterHashMode::RAW || cluster.hashMode == ClusterHashMode::RAW2) + if (cluster.hashMode == ClusterHashMode::RAW || cluster.hashMode == ClusterHashMode::RAW_STREAM) return ReadFile_HashModeRaw(entry.fileInfo.clusterIndex, entry, offset, size, dataOut); else if (cluster.hashMode == ClusterHashMode::HASH_INTERLEAVED) return ReadFile_HashModeHashed(entry.fileInfo.clusterIndex, entry, offset, size, dataOut); @@ -765,87 +815,15 @@ uint32 FSTVolume::ReadFile(FSTFileHandle& fileHandle, uint32 offset, uint32 size return 0; } -uint32 FSTVolume::ReadFile_HashModeRaw(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut) -{ - const uint32 readSizeInput = readSize; - uint8* dataOutU8 = (uint8*)dataOut; - if (readOffset >= entry.fileInfo.fileSize) - return 0; - else if ((readOffset + readSize) >= entry.fileInfo.fileSize) - readSize = (entry.fileInfo.fileSize - readOffset); - - const FSTCluster& cluster = m_cluster[clusterIndex]; - uint64 clusterOffset = (uint64)cluster.offset * m_sectorSize; - uint64 absFileOffset = entry.fileInfo.fileOffset * m_offsetFactor + readOffset; - - // make sure the raw range we read is aligned to AES block size (16) - uint64 readAddrStart = absFileOffset & ~0xF; - uint64 readAddrEnd = (absFileOffset + readSize + 0xF) & ~0xF; - - bool usesInitialIV = readOffset < 16; - if (!usesInitialIV) - readAddrStart -= 16; // read previous AES block since we require it for the IV - uint32 prePadding = (uint32)(absFileOffset - readAddrStart); // number of extra bytes we read before readOffset (for AES alignment and IV calculation) - uint32 postPadding = (uint32)(readAddrEnd - (absFileOffset + readSize)); - - uint8 readBuffer[64 * 1024]; - // read first chunk - // if file read offset (readOffset) is within the first AES-block then use initial IV calculated from cluster index - // otherwise read previous AES-block is the IV (AES-CBC) - uint64 readAddrCurrent = readAddrStart; - uint32 rawBytesToRead = (uint32)std::min((readAddrEnd - readAddrStart), (uint64)sizeof(readBuffer)); - if (m_dataSource->readData(clusterIndex, clusterOffset, readAddrCurrent, readBuffer, rawBytesToRead) != rawBytesToRead) - { - cemuLog_log(LogType::Force, "FST read error in raw content"); - return 0; - } - readAddrCurrent += rawBytesToRead; - - uint8 iv[16]{}; - if (usesInitialIV) - { - // for the first AES block, the IV is initialized from cluster index - iv[0] = (uint8)(clusterIndex >> 8); - iv[1] = (uint8)(clusterIndex >> 0); - AES128_CBC_decrypt_updateIV(readBuffer, readBuffer, rawBytesToRead, m_partitionTitlekey.b, iv); - std::memcpy(dataOutU8, readBuffer + prePadding, rawBytesToRead - prePadding - postPadding); - dataOutU8 += (rawBytesToRead - prePadding - postPadding); - readSize -= (rawBytesToRead - prePadding - postPadding); - } - else - { - // IV is initialized from previous AES block (AES-CBC) - std::memcpy(iv, readBuffer, 16); - AES128_CBC_decrypt_updateIV(readBuffer + 16, readBuffer + 16, rawBytesToRead - 16, m_partitionTitlekey.b, iv); - std::memcpy(dataOutU8, readBuffer + prePadding, rawBytesToRead - prePadding - postPadding); - dataOutU8 += (rawBytesToRead - prePadding - postPadding); - readSize -= (rawBytesToRead - prePadding - postPadding); - } - - // read remaining chunks - while (readSize > 0) - { - uint32 bytesToRead = (uint32)std::min((uint32)sizeof(readBuffer), readSize); - uint32 alignedBytesToRead = (bytesToRead + 15) & ~0xF; - if (m_dataSource->readData(clusterIndex, clusterOffset, readAddrCurrent, readBuffer, alignedBytesToRead) != alignedBytesToRead) - { - cemuLog_log(LogType::Force, "FST read error in raw content"); - return 0; - } - AES128_CBC_decrypt_updateIV(readBuffer, readBuffer, alignedBytesToRead, m_partitionTitlekey.b, iv); - std::memcpy(dataOutU8, readBuffer, bytesToRead); - dataOutU8 += bytesToRead; - readSize -= bytesToRead; - readAddrCurrent += alignedBytesToRead; - } - - return readSizeInput - readSize; -} - constexpr size_t BLOCK_SIZE = 0x10000; constexpr size_t BLOCK_HASH_SIZE = 0x0400; constexpr size_t BLOCK_FILE_SIZE = 0xFC00; +struct FSTRawBlock +{ + std::vector<uint8> rawData; // unhashed block size depends on sector size field in partition header +}; + struct FSTHashedBlock { uint8 rawData[BLOCK_SIZE]; @@ -887,12 +865,160 @@ struct FSTHashedBlock static_assert(sizeof(FSTHashedBlock) == BLOCK_SIZE); +struct FSTCachedRawBlock +{ + FSTRawBlock blockData; + uint8 ivForNextBlock[16]; + uint64 lastAccess; +}; + struct FSTCachedHashedBlock { FSTHashedBlock blockData; uint64 lastAccess; }; +// Checks cache fill state and if necessary drops least recently accessed block from the cache. Optionally allows to recycle the released cache entry to cut down cost of memory allocation and clearing +void FSTVolume::TrimCacheIfRequired(FSTCachedRawBlock** droppedRawBlock, FSTCachedHashedBlock** droppedHashedBlock) +{ + // calculate size used by cache + size_t cacheSize = 0; + for (auto& itr : m_cacheDecryptedRawBlocks) + cacheSize += itr.second->blockData.rawData.size(); + for (auto& itr : m_cacheDecryptedHashedBlocks) + cacheSize += sizeof(FSTCachedHashedBlock) + sizeof(FSTHashedBlock); + // only trim if cache is full (larger than 2MB) + if (cacheSize < 2*1024*1024) // 2MB + return; + // scan both cache lists to find least recently accessed block to drop + auto dropRawItr = std::min_element(m_cacheDecryptedRawBlocks.begin(), m_cacheDecryptedRawBlocks.end(), [](const auto& a, const auto& b) -> bool + { return a.second->lastAccess < b.second->lastAccess; }); + auto dropHashedItr = std::min_element(m_cacheDecryptedHashedBlocks.begin(), m_cacheDecryptedHashedBlocks.end(), [](const auto& a, const auto& b) -> bool + { return a.second->lastAccess < b.second->lastAccess; }); + uint64 lastAccess = std::numeric_limits<uint64>::max(); + if(dropRawItr != m_cacheDecryptedRawBlocks.end()) + lastAccess = dropRawItr->second->lastAccess; + if(dropHashedItr != m_cacheDecryptedHashedBlocks.end()) + lastAccess = std::min<uint64>(lastAccess, dropHashedItr->second->lastAccess); + if(dropRawItr != m_cacheDecryptedRawBlocks.end() && dropRawItr->second->lastAccess == lastAccess) + { + if (droppedRawBlock) + *droppedRawBlock = dropRawItr->second; + else + delete dropRawItr->second; + m_cacheDecryptedRawBlocks.erase(dropRawItr); + return; + } + else if(dropHashedItr != m_cacheDecryptedHashedBlocks.end() && dropHashedItr->second->lastAccess == lastAccess) + { + if (droppedHashedBlock) + *droppedHashedBlock = dropHashedItr->second; + else + delete dropHashedItr->second; + m_cacheDecryptedHashedBlocks.erase(dropHashedItr); + } +} + +void FSTVolume::DetermineUnhashedBlockIV(uint32 clusterIndex, uint32 blockIndex, uint8 ivOut[16]) +{ + memset(ivOut, 0, sizeof(ivOut)); + if(blockIndex == 0) + { + ivOut[0] = (uint8)(clusterIndex >> 8); + ivOut[1] = (uint8)(clusterIndex >> 0); + } + else + { + // the last 16 encrypted bytes of the previous block are the IV (AES CBC) + // if the previous block is cached we can grab the IV from there. Otherwise we have to read the 16 bytes from the data source + uint32 prevBlockIndex = blockIndex - 1; + uint64 cacheBlockId = ((uint64)clusterIndex << (64 - 16)) | (uint64)prevBlockIndex; + auto itr = m_cacheDecryptedRawBlocks.find(cacheBlockId); + if (itr != m_cacheDecryptedRawBlocks.end()) + { + memcpy(ivOut, itr->second->ivForNextBlock, 16); + } + else + { + cemu_assert(m_sectorSize >= 16); + uint64 clusterOffset = (uint64)m_cluster[clusterIndex].offset * m_sectorSize; + uint8 prevIV[16]; + if (m_dataSource->readData(clusterIndex, clusterOffset, blockIndex * m_sectorSize - 16, prevIV, 16) != 16) + { + cemuLog_log(LogType::Force, "Failed to read IV for raw FST block"); + m_detectedCorruption = true; + return; + } + memcpy(ivOut, prevIV, 16); + } + } +} + +FSTCachedRawBlock* FSTVolume::GetDecryptedRawBlock(uint32 clusterIndex, uint32 blockIndex) +{ + FSTCluster& cluster = m_cluster[clusterIndex]; + uint64 clusterOffset = (uint64)cluster.offset * m_sectorSize; + // generate id for cache + uint64 cacheBlockId = ((uint64)clusterIndex << (64 - 16)) | (uint64)blockIndex; + // lookup block in cache + FSTCachedRawBlock* block = nullptr; + auto itr = m_cacheDecryptedRawBlocks.find(cacheBlockId); + if (itr != m_cacheDecryptedRawBlocks.end()) + { + block = itr->second; + block->lastAccess = ++m_cacheAccessCounter; + return block; + } + // if cache already full, drop least recently accessed block and recycle FSTCachedRawBlock object if possible + TrimCacheIfRequired(&block, nullptr); + if (!block) + block = new FSTCachedRawBlock(); + block->blockData.rawData.resize(m_sectorSize); + // block not cached, read new + block->lastAccess = ++m_cacheAccessCounter; + if (m_dataSource->readData(clusterIndex, clusterOffset, blockIndex * m_sectorSize, block->blockData.rawData.data(), m_sectorSize) != m_sectorSize) + { + cemuLog_log(LogType::Force, "Failed to read raw FST block"); + delete block; + m_detectedCorruption = true; + return nullptr; + } + // decrypt hash data + uint8 iv[16]{}; + DetermineUnhashedBlockIV(clusterIndex, blockIndex, iv); + memcpy(block->ivForNextBlock, block->blockData.rawData.data() + m_sectorSize - 16, 16); + AES128_CBC_decrypt(block->blockData.rawData.data(), block->blockData.rawData.data(), m_sectorSize, m_partitionTitlekey.b, iv); + // if this is the next block, then hash it + if(cluster.hasContentHash) + { + if(cluster.singleHashNumBlocksHashed == blockIndex) + { + cemu_assert_debug(!(cluster.contentSize % m_sectorSize)); // size should be multiple of sector size? Regardless, the hashing code below can handle non-aligned sizes + bool isLastBlock = blockIndex == (std::max<uint32>(cluster.contentSize / m_sectorSize, 1) - 1); + uint32 hashSize = m_sectorSize; + if(isLastBlock) + hashSize = cluster.contentSize - (uint64)blockIndex*m_sectorSize; + EVP_DigestUpdate(cluster.singleHashCtx.get(), block->blockData.rawData.data(), hashSize); + cluster.singleHashNumBlocksHashed++; + if(isLastBlock) + { + uint8 hash[32]; + EVP_DigestFinal_ex(cluster.singleHashCtx.get(), hash, nullptr); + if(memcmp(hash, cluster.contentHash32, cluster.contentHashIsSHA1 ? 20 : 32) != 0) + { + cemuLog_log(LogType::Force, "FST: Raw section hash mismatch"); + delete block; + m_detectedCorruption = true; + return nullptr; + } + } + } + } + // register in cache + m_cacheDecryptedRawBlocks.emplace(cacheBlockId, block); + return block; +} + FSTCachedHashedBlock* FSTVolume::GetDecryptedHashedBlock(uint32 clusterIndex, uint32 blockIndex) { const FSTCluster& cluster = m_cluster[clusterIndex]; @@ -908,22 +1034,17 @@ FSTCachedHashedBlock* FSTVolume::GetDecryptedHashedBlock(uint32 clusterIndex, ui block->lastAccess = ++m_cacheAccessCounter; return block; } - // if cache already full, drop least recently accessed block (but recycle the FSTHashedBlock* object) - if (m_cacheDecryptedHashedBlocks.size() >= 16) - { - auto dropItr = std::min_element(m_cacheDecryptedHashedBlocks.begin(), m_cacheDecryptedHashedBlocks.end(), [](const auto& a, const auto& b) -> bool - { return a.second->lastAccess < b.second->lastAccess; }); - block = dropItr->second; - m_cacheDecryptedHashedBlocks.erase(dropItr); - } - else + // if cache already full, drop least recently accessed block and recycle FSTCachedHashedBlock object if possible + TrimCacheIfRequired(nullptr, &block); + if (!block) block = new FSTCachedHashedBlock(); // block not cached, read new block->lastAccess = ++m_cacheAccessCounter; if (m_dataSource->readData(clusterIndex, clusterOffset, blockIndex * BLOCK_SIZE, block->blockData.rawData, BLOCK_SIZE) != BLOCK_SIZE) { - cemuLog_log(LogType::Force, "Failed to read FST block"); + cemuLog_log(LogType::Force, "Failed to read hashed FST block"); delete block; + m_detectedCorruption = true; return nullptr; } // decrypt hash data @@ -931,11 +1052,46 @@ FSTCachedHashedBlock* FSTVolume::GetDecryptedHashedBlock(uint32 clusterIndex, ui AES128_CBC_decrypt(block->blockData.getHashData(), block->blockData.getHashData(), BLOCK_HASH_SIZE, m_partitionTitlekey.b, iv); // decrypt file data AES128_CBC_decrypt(block->blockData.getFileData(), block->blockData.getFileData(), BLOCK_FILE_SIZE, m_partitionTitlekey.b, block->blockData.getH0Hash(blockIndex%16)); + // compare with H0 to verify data integrity + NCrypto::CHash160 h0; + SHA1(block->blockData.getFileData(), BLOCK_FILE_SIZE, h0.b); + uint32 h0Index = (blockIndex % 4096); + if (memcmp(h0.b, block->blockData.getH0Hash(h0Index & 0xF), sizeof(h0.b)) != 0) + { + cemuLog_log(LogType::Force, "FST: Hash H0 mismatch in hashed block (section {} index {})", clusterIndex, blockIndex); + delete block; + m_detectedCorruption = true; + return nullptr; + } // register in cache m_cacheDecryptedHashedBlocks.emplace(cacheBlockId, block); return block; } +uint32 FSTVolume::ReadFile_HashModeRaw(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut) +{ + uint8* dataOutU8 = (uint8*)dataOut; + if (readOffset >= entry.fileInfo.fileSize) + return 0; + else if ((readOffset + readSize) >= entry.fileInfo.fileSize) + readSize = (entry.fileInfo.fileSize - readOffset); + uint64 absFileOffset = entry.fileInfo.fileOffset * m_offsetFactor + readOffset; + uint32 remainingReadSize = readSize; + while (remainingReadSize > 0) + { + const FSTCachedRawBlock* rawBlock = this->GetDecryptedRawBlock(clusterIndex, absFileOffset/m_sectorSize); + if (!rawBlock) + break; + uint32 blockOffset = (uint32)(absFileOffset % m_sectorSize); + uint32 bytesToRead = std::min<uint32>(remainingReadSize, m_sectorSize - blockOffset); + std::memcpy(dataOutU8, rawBlock->blockData.rawData.data() + blockOffset, bytesToRead); + dataOutU8 += bytesToRead; + remainingReadSize -= bytesToRead; + absFileOffset += bytesToRead; + } + return readSize - remainingReadSize; +} + uint32 FSTVolume::ReadFile_HashModeHashed(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut) { /* @@ -966,7 +1122,6 @@ uint32 FSTVolume::ReadFile_HashModeHashed(uint32 clusterIndex, FSTEntry& entry, */ const FSTCluster& cluster = m_cluster[clusterIndex]; - uint64 clusterBaseOffset = (uint64)cluster.offset * m_sectorSize; uint64 fileReadOffset = entry.fileInfo.fileOffset * m_offsetFactor + readOffset; uint32 blockIndex = (uint32)(fileReadOffset / BLOCK_FILE_SIZE); uint32 bytesRemaining = readSize; @@ -1019,6 +1174,8 @@ bool FSTVolume::Next(FSTDirectoryIterator& directoryIterator, FSTFileHandle& fil FSTVolume::~FSTVolume() { + for (auto& itr : m_cacheDecryptedRawBlocks) + delete itr.second; for (auto& itr : m_cacheDecryptedHashedBlocks) delete itr.second; if (m_sourceIsOwned) @@ -1115,4 +1272,4 @@ bool FSTVerifier::VerifyHashedContentFile(FileStream* fileContent, const NCrypto void FSTVolumeTest() { FSTPathUnitTest(); -} \ No newline at end of file +} diff --git a/src/Cafe/Filesystem/FST/FST.h b/src/Cafe/Filesystem/FST/FST.h index 24fc39ea..601799ce 100644 --- a/src/Cafe/Filesystem/FST/FST.h +++ b/src/Cafe/Filesystem/FST/FST.h @@ -1,5 +1,6 @@ #pragma once #include "Cemu/ncrypto/ncrypto.h" +#include "openssl/evp.h" struct FSTFileHandle { @@ -45,6 +46,7 @@ public: ~FSTVolume(); uint32 GetFileCount() const; + bool HasCorruption() const { return m_detectedCorruption; } bool OpenFile(std::string_view path, FSTFileHandle& fileHandleOut, bool openOnlyFiles = false); @@ -86,15 +88,25 @@ private: enum class ClusterHashMode : uint8 { RAW = 0, // raw data + encryption, no hashing? - RAW2 = 1, // raw data + encryption, with hash stored in tmd? + RAW_STREAM = 1, // raw data + encryption, with hash stored in tmd? HASH_INTERLEAVED = 2, // hashes + raw interleaved in 0x10000 blocks (0x400 bytes of hashes at the beginning, followed by 0xFC00 bytes of data) }; struct FSTCluster { + FSTCluster() : singleHashCtx(nullptr, &EVP_MD_CTX_free) {} + uint32 offset; uint32 size; ClusterHashMode hashMode; + // extra data if TMD is available + bool hasContentHash; + uint8 contentHash32[32]; + bool contentHashIsSHA1; // if true then it's SHA1 (with extra bytes zeroed out), otherwise it's SHA256 + uint64 contentSize; // size of the content (in blocks) + // hash context for single hash mode (content hash must be available) + std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)> singleHashCtx; // unique_ptr to make this move-only + uint32 singleHashNumBlocksHashed{0}; }; struct FSTEntry @@ -164,17 +176,30 @@ private: bool m_sourceIsOwned{}; uint32 m_sectorSize{}; // for cluster offsets uint32 m_offsetFactor{}; // for file offsets + bool m_hashIsDisabled{}; // disables hash verification (for all clusters of this volume?) std::vector<FSTCluster> m_cluster; std::vector<FSTEntry> m_entries; std::vector<char> m_nameStringTable; NCrypto::AesKey m_partitionTitlekey; + bool m_detectedCorruption{false}; - /* Cache for decrypted hashed blocks */ + bool HashIsDisabled() const + { + return m_hashIsDisabled; + } + + /* Cache for decrypted raw and hashed blocks */ + std::unordered_map<uint64, struct FSTCachedRawBlock*> m_cacheDecryptedRawBlocks; std::unordered_map<uint64, struct FSTCachedHashedBlock*> m_cacheDecryptedHashedBlocks; uint64 m_cacheAccessCounter{}; + void DetermineUnhashedBlockIV(uint32 clusterIndex, uint32 blockIndex, uint8 ivOut[16]); + + struct FSTCachedRawBlock* GetDecryptedRawBlock(uint32 clusterIndex, uint32 blockIndex); struct FSTCachedHashedBlock* GetDecryptedHashedBlock(uint32 clusterIndex, uint32 blockIndex); + void TrimCacheIfRequired(struct FSTCachedRawBlock** droppedRawBlock, struct FSTCachedHashedBlock** droppedHashedBlock); + /* File reading */ uint32 ReadFile_HashModeRaw(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut); uint32 ReadFile_HashModeHashed(uint32 clusterIndex, FSTEntry& entry, uint32 readOffset, uint32 readSize, void* dataOut); @@ -185,7 +210,10 @@ private: /* +0x00 */ uint32be magic; /* +0x04 */ uint32be offsetFactor; /* +0x08 */ uint32be numCluster; - /* +0x0C */ uint32be ukn0C; + /* +0x0C */ uint8be hashIsDisabled; + /* +0x0D */ uint8be ukn0D; + /* +0x0E */ uint8be ukn0E; + /* +0x0F */ uint8be ukn0F; /* +0x10 */ uint32be ukn10; /* +0x14 */ uint32be ukn14; /* +0x18 */ uint32be ukn18; @@ -262,8 +290,8 @@ private: static_assert(sizeof(FSTHeader_FileEntry) == 0x10); - static FSTVolume* OpenFST(FSTDataSource* dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode); - static FSTVolume* OpenFST(std::unique_ptr<FSTDataSource> dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode); + static FSTVolume* OpenFST(FSTDataSource* dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode, NCrypto::TMDParser* optionalTMD); + static FSTVolume* OpenFST(std::unique_ptr<FSTDataSource> dataSource, uint64 fstOffset, uint32 fstSize, NCrypto::AesKey* partitionTitleKey, ClusterHashMode fstHashMode, NCrypto::TMDParser* optionalTMD); static bool ProcessFST(FSTHeader_FileEntry* fileTable, uint32 numFileEntries, uint32 numCluster, std::vector<char>& nameStringTable, std::vector<FSTEntry>& fstEntries); bool MatchFSTEntryName(FSTEntry& entry, std::string_view comparedName) From 66658351c1e274265ddc18643b761f7df1e21e11 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 10 Nov 2024 10:10:46 +0100 Subject: [PATCH 200/299] erreula: Rework implementation and fix bugs - ErrEula doesn't disappear on its own anymore. The expected behavior is for the game to call Disappear once a button has been selected. This fixes issues where the dialog would softlock in some games - Modernized code a bit - Added a subtle fade in/out effect --- src/Cafe/CafeSystem.cpp | 42 +-- src/Cafe/CafeSystem.h | 7 +- src/Cafe/OS/libs/coreinit/coreinit_Time.h | 7 +- src/Cafe/OS/libs/erreula/erreula.cpp | 423 ++++++++++++++-------- src/gui/MainWindow.cpp | 12 +- 5 files changed, 311 insertions(+), 180 deletions(-) diff --git a/src/Cafe/CafeSystem.cpp b/src/Cafe/CafeSystem.cpp index 51de3550..88e0ed3d 100644 --- a/src/Cafe/CafeSystem.cpp +++ b/src/Cafe/CafeSystem.cpp @@ -637,40 +637,40 @@ namespace CafeSystem fsc_unmount("/cemuBossStorage/", FSC_PRIORITY_BASE); } - STATUS_CODE LoadAndMountForegroundTitle(TitleId titleId) + PREPARE_STATUS_CODE LoadAndMountForegroundTitle(TitleId titleId) { cemuLog_log(LogType::Force, "Mounting title {:016x}", (uint64)titleId); sGameInfo_ForegroundTitle = CafeTitleList::GetGameInfo(titleId); if (!sGameInfo_ForegroundTitle.IsValid()) { cemuLog_log(LogType::Force, "Mounting failed: Game meta information is either missing, inaccessible or not valid (missing or invalid .xml files in code and meta folder)"); - return STATUS_CODE::UNABLE_TO_MOUNT; + return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT; } // check base TitleInfo& titleBase = sGameInfo_ForegroundTitle.GetBase(); if (!titleBase.IsValid()) - return STATUS_CODE::UNABLE_TO_MOUNT; + return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT; if(!titleBase.ParseXmlInfo()) - return STATUS_CODE::UNABLE_TO_MOUNT; + return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT; cemuLog_log(LogType::Force, "Base: {}", titleBase.GetPrintPath()); // mount base if (!titleBase.Mount("/vol/content", "content", FSC_PRIORITY_BASE) || !titleBase.Mount(GetInternalVirtualCodeFolder(), "code", FSC_PRIORITY_BASE)) { cemuLog_log(LogType::Force, "Mounting failed"); - return STATUS_CODE::UNABLE_TO_MOUNT; + return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT; } // check update TitleInfo& titleUpdate = sGameInfo_ForegroundTitle.GetUpdate(); if (titleUpdate.IsValid()) { if (!titleUpdate.ParseXmlInfo()) - return STATUS_CODE::UNABLE_TO_MOUNT; + return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT; cemuLog_log(LogType::Force, "Update: {}", titleUpdate.GetPrintPath()); // mount update if (!titleUpdate.Mount("/vol/content", "content", FSC_PRIORITY_PATCH) || !titleUpdate.Mount(GetInternalVirtualCodeFolder(), "code", FSC_PRIORITY_PATCH)) { cemuLog_log(LogType::Force, "Mounting failed"); - return STATUS_CODE::UNABLE_TO_MOUNT; + return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT; } } else @@ -682,20 +682,20 @@ namespace CafeSystem // todo - support for multi-title AOC TitleInfo& titleAOC = aocList[0]; if (!titleAOC.ParseXmlInfo()) - return STATUS_CODE::UNABLE_TO_MOUNT; + return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT; cemu_assert_debug(titleAOC.IsValid()); cemuLog_log(LogType::Force, "DLC: {}", titleAOC.GetPrintPath()); // mount AOC if (!titleAOC.Mount(fmt::format("/vol/aoc{:016x}", titleAOC.GetAppTitleId()), "content", FSC_PRIORITY_PATCH)) { cemuLog_log(LogType::Force, "Mounting failed"); - return STATUS_CODE::UNABLE_TO_MOUNT; + return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT; } } else cemuLog_log(LogType::Force, "DLC: Not present"); sForegroundTitleId = titleId; - return STATUS_CODE::SUCCESS; + return PREPARE_STATUS_CODE::SUCCESS; } void UnmountForegroundTitle() @@ -723,7 +723,7 @@ namespace CafeSystem } } - STATUS_CODE SetupExecutable() + PREPARE_STATUS_CODE SetupExecutable() { // set rpx path from cos.xml if available _pathToBaseExecutable = _pathToExecutable; @@ -755,7 +755,7 @@ namespace CafeSystem } } LoadMainExecutable(); - return STATUS_CODE::SUCCESS; + return PREPARE_STATUS_CODE::SUCCESS; } void SetupMemorySpace() @@ -769,7 +769,7 @@ namespace CafeSystem memory_unmapForCurrentTitle(); } - STATUS_CODE PrepareForegroundTitle(TitleId titleId) + PREPARE_STATUS_CODE PrepareForegroundTitle(TitleId titleId) { CafeTitleList::WaitForMandatoryScan(); sLaunchModeIsStandalone = false; @@ -780,21 +780,21 @@ namespace CafeSystem // mount mlc storage MountBaseDirectories(); // mount title folders - STATUS_CODE r = LoadAndMountForegroundTitle(titleId); - if (r != STATUS_CODE::SUCCESS) + PREPARE_STATUS_CODE r = LoadAndMountForegroundTitle(titleId); + if (r != PREPARE_STATUS_CODE::SUCCESS) return r; gameProfile_load(); // setup memory space and PPC recompiler SetupMemorySpace(); PPCRecompiler_init(); r = SetupExecutable(); // load RPX - if (r != STATUS_CODE::SUCCESS) + if (r != PREPARE_STATUS_CODE::SUCCESS) return r; InitVirtualMlcStorage(); - return STATUS_CODE::SUCCESS; + return PREPARE_STATUS_CODE::SUCCESS; } - STATUS_CODE PrepareForegroundTitleFromStandaloneRPX(const fs::path& path) + PREPARE_STATUS_CODE PrepareForegroundTitleFromStandaloneRPX(const fs::path& path) { sLaunchModeIsStandalone = true; cemuLog_log(LogType::Force, "Launching executable in standalone mode due to incorrect layout or missing meta files"); @@ -812,7 +812,7 @@ namespace CafeSystem if (!r) { cemuLog_log(LogType::Force, "Failed to mount {}", _pathToUtf8(contentPath)); - return STATUS_CODE::UNABLE_TO_MOUNT; + return PREPARE_STATUS_CODE::UNABLE_TO_MOUNT; } } } @@ -824,7 +824,7 @@ namespace CafeSystem // since a lot of systems (including save folder location) rely on a TitleId, we derive a placeholder id from the executable hash auto execData = fsc_extractFile(_pathToExecutable.c_str()); if (!execData) - return STATUS_CODE::INVALID_RPX; + return PREPARE_STATUS_CODE::INVALID_RPX; uint32 h = generateHashFromRawRPXData(execData->data(), execData->size()); sForegroundTitleId = 0xFFFFFFFF00000000ULL | (uint64)h; cemuLog_log(LogType::Force, "Generated placeholder TitleId: {:016x}", sForegroundTitleId); @@ -834,7 +834,7 @@ namespace CafeSystem // load executable SetupExecutable(); InitVirtualMlcStorage(); - return STATUS_CODE::SUCCESS; + return PREPARE_STATUS_CODE::SUCCESS; } void _LaunchTitleThread() diff --git a/src/Cafe/CafeSystem.h b/src/Cafe/CafeSystem.h index c4043a59..e9de8d7d 100644 --- a/src/Cafe/CafeSystem.h +++ b/src/Cafe/CafeSystem.h @@ -15,20 +15,19 @@ namespace CafeSystem virtual void CafeRecreateCanvas() = 0; }; - enum class STATUS_CODE + enum class PREPARE_STATUS_CODE { SUCCESS, INVALID_RPX, UNABLE_TO_MOUNT, // failed to mount through TitleInfo (most likely caused by an invalid or outdated path) - //BAD_META_DATA, - the title list only stores titles with valid meta, so this error code is impossible }; void Initialize(); void SetImplementation(SystemImplementation* impl); void Shutdown(); - STATUS_CODE PrepareForegroundTitle(TitleId titleId); - STATUS_CODE PrepareForegroundTitleFromStandaloneRPX(const fs::path& path); + PREPARE_STATUS_CODE PrepareForegroundTitle(TitleId titleId); + PREPARE_STATUS_CODE PrepareForegroundTitleFromStandaloneRPX(const fs::path& path); void LaunchForegroundTitle(); bool IsTitleRunning(); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Time.h b/src/Cafe/OS/libs/coreinit/coreinit_Time.h index 018e8eb7..3aa92b99 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Time.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_Time.h @@ -40,7 +40,12 @@ namespace coreinit inline TimerTicks ConvertNsToTimerTicks(uint64 ns) { - return ((GetTimerClock() / 31250LL) * ((ns)) / 32000LL); + return ((GetTimerClock() / 31250LL) * ((TimerTicks)ns) / 32000LL); + } + + inline TimerTicks ConvertMsToTimerTicks(uint64 ms) + { + return (TimerTicks)ms * GetTimerClock() / 1000LL; } }; diff --git a/src/Cafe/OS/libs/erreula/erreula.cpp b/src/Cafe/OS/libs/erreula/erreula.cpp index a7f2f35c..342e8b64 100644 --- a/src/Cafe/OS/libs/erreula/erreula.cpp +++ b/src/Cafe/OS/libs/erreula/erreula.cpp @@ -9,32 +9,45 @@ #include <wx/msgdlg.h> #include "Cafe/OS/libs/coreinit/coreinit_FS.h" +#include "Cafe/OS/libs/coreinit/coreinit_Time.h" #include "Cafe/OS/libs/vpad/vpad.h" namespace nn { namespace erreula { -#define RESULTTYPE_NONE 0 -#define RESULTTYPE_FINISH 1 -#define RESULTTYPE_NEXT 2 -#define RESULTTYPE_JUMP 3 -#define RESULTTYPE_PASSWORD 4 -#define ERRORTYPE_CODE 0 -#define ERRORTYPE_TEXT 1 -#define ERRORTYPE_TEXT_ONE_BUTTON 2 -#define ERRORTYPE_TEXT_TWO_BUTTON 3 - -#define ERREULA_STATE_HIDDEN 0 -#define ERREULA_STATE_APPEARING 1 -#define ERREULA_STATE_VISIBLE 2 -#define ERREULA_STATE_DISAPPEARING 3 - - struct AppearArg_t + enum class ErrorDialogType : uint32 { - AppearArg_t() = default; - AppearArg_t(const AppearArg_t& o) + Code = 0, + Text = 1, + TextOneButton = 2, + TextTwoButton = 3 + }; + + static const sint32 FADE_TIME = 80; + + enum class ErrEulaState : uint32 + { + Hidden = 0, + Appearing = 1, + Visible = 2, + Disappearing = 3 + }; + + enum class ResultType : uint32 + { + None = 0, + Finish = 1, + Next = 2, + Jump = 3, + Password = 4 + }; + + struct AppearError + { + AppearError() = default; + AppearError(const AppearError& o) { errorType = o.errorType; screenType = o.screenType; @@ -49,7 +62,7 @@ namespace erreula drawCursor = o.drawCursor; } - uint32be errorType; + betype<ErrorDialogType> errorType; uint32be screenType; uint32be controllerType; uint32be holdType; @@ -63,7 +76,9 @@ namespace erreula bool drawCursor{}; }; - static_assert(sizeof(AppearArg_t) == 0x2C); // maybe larger + using AppearArg = AppearError; + + static_assert(sizeof(AppearError) == 0x2C); // maybe larger struct HomeNixSignArg_t { @@ -80,6 +95,132 @@ namespace erreula static_assert(sizeof(ControllerInfo_t) == 0x14); // maybe larger + class ErrEulaInstance + { + public: + enum class BUTTON_SELECTION : uint32 + { + NONE = 0xFFFFFFFF, + LEFT = 0, + RIGHT = 1, + }; + + void Init() + { + m_buttonSelection = BUTTON_SELECTION::NONE; + m_resultCode = -1; + m_resultCodeForLeftButton = 0; + m_resultCodeForRightButton = 0; + SetState(ErrEulaState::Hidden); + } + + void DoAppearError(AppearArg* arg) + { + m_buttonSelection = BUTTON_SELECTION::NONE; + m_resultCode = -1; + m_resultCodeForLeftButton = -1; + m_resultCodeForRightButton = -1; + // for standard dialog its 0 and 1? + m_resultCodeForLeftButton = 0; + m_resultCodeForRightButton = 1; + SetState(ErrEulaState::Appearing); + } + + void DoDisappearError() + { + if(m_state != ErrEulaState::Visible) + return; + SetState(ErrEulaState::Disappearing); + } + + void DoCalc() + { + // appearing and disappearing state will automatically advance after some time + if (m_state == ErrEulaState::Appearing || m_state == ErrEulaState::Disappearing) + { + uint32 elapsedTick = coreinit::OSGetTime() - m_lastStateChange; + if (elapsedTick > coreinit::EspressoTime::ConvertMsToTimerTicks(FADE_TIME)) + { + SetState(m_state == ErrEulaState::Appearing ? ErrEulaState::Visible : ErrEulaState::Hidden); + } + } + } + + bool IsDecideSelectButtonError() const + { + return m_buttonSelection != BUTTON_SELECTION::NONE; + } + + bool IsDecideSelectLeftButtonError() const + { + return m_buttonSelection != BUTTON_SELECTION::LEFT; + } + + bool IsDecideSelectRightButtonError() const + { + return m_buttonSelection != BUTTON_SELECTION::RIGHT; + } + + void SetButtonSelection(BUTTON_SELECTION selection) + { + cemu_assert_debug(m_buttonSelection == BUTTON_SELECTION::NONE); + m_buttonSelection = selection; + cemu_assert_debug(selection == BUTTON_SELECTION::LEFT || selection == BUTTON_SELECTION::RIGHT); + m_resultCode = selection == BUTTON_SELECTION::LEFT ? m_resultCodeForLeftButton : m_resultCodeForRightButton; + } + + ErrEulaState GetState() const + { + return m_state; + } + + sint32 GetResultCode() const + { + return m_resultCode; + } + + ResultType GetResultType() const + { + if(m_resultCode == -1) + return ResultType::None; + if(m_resultCode < 10) + return ResultType::Finish; + if(m_resultCode >= 9999) + return ResultType::Next; + if(m_resultCode == 40) + return ResultType::Password; + return ResultType::Jump; + } + + float GetFadeTransparency() const + { + if(m_state == ErrEulaState::Appearing || m_state == ErrEulaState::Disappearing) + { + uint32 elapsedTick = coreinit::OSGetTime() - m_lastStateChange; + if(m_state == ErrEulaState::Appearing) + return std::min<float>(1.0f, (float)elapsedTick / (float)coreinit::EspressoTime::ConvertMsToTimerTicks(FADE_TIME)); + else + return std::max<float>(0.0f, 1.0f - (float)elapsedTick / (float)coreinit::EspressoTime::ConvertMsToTimerTicks(FADE_TIME)); + } + return 1.0f; + } + + private: + void SetState(ErrEulaState state) + { + m_state = state; + m_lastStateChange = coreinit::OSGetTime(); + } + + ErrEulaState m_state; + uint32 m_lastStateChange; + + /* +0x30 */ betype<sint32> m_resultCode; + /* +0x239C */ betype<BUTTON_SELECTION> m_buttonSelection; + /* +0x23A0 */ betype<sint32> m_resultCodeForLeftButton; + /* +0x23A4 */ betype<sint32> m_resultCodeForRightButton; + }; + struct ErrEula_t { SysAllocator<coreinit::OSMutex> mutex; @@ -87,17 +228,11 @@ namespace erreula uint32 langType; MEMPTR<coreinit::FSClient_t> fsClient; - AppearArg_t currentDialog; - uint32 state; - bool buttonPressed; - bool rightButtonPressed; + std::unique_ptr<ErrEulaInstance> errEulaInstance; + AppearError currentDialog; bool homeNixSignVisible; - - std::chrono::steady_clock::time_point stateTimer{}; } g_errEula = {}; - - std::wstring GetText(uint16be* text) { @@ -113,22 +248,61 @@ namespace erreula } - void export_ErrEulaCreate(PPCInterpreter_t* hCPU) + void ErrEulaCreate(void* workmem, uint32 regionType, uint32 langType, coreinit::FSClient_t* fsClient) { - ppcDefineParamMEMPTR(thisptr, uint8, 0); - ppcDefineParamU32(regionType, 1); - ppcDefineParamU32(langType, 2); - ppcDefineParamMEMPTR(fsClient, coreinit::FSClient_t, 3); - coreinit::OSLockMutex(&g_errEula.mutex); g_errEula.regionType = regionType; g_errEula.langType = langType; g_errEula.fsClient = fsClient; + cemu_assert_debug(!g_errEula.errEulaInstance); + g_errEula.errEulaInstance = std::make_unique<ErrEulaInstance>(); + g_errEula.errEulaInstance->Init(); coreinit::OSUnlockMutex(&g_errEula.mutex); + } - osLib_returnFromFunction(hCPU, 0); + void ErrEulaDestroy() + { + g_errEula.errEulaInstance.reset(); + } + + // check if any dialog button was selected + bool IsDecideSelectButtonError() + { + if(!g_errEula.errEulaInstance) + return false; + return g_errEula.errEulaInstance->IsDecideSelectButtonError(); + } + + // check if left dialog button was selected + bool IsDecideSelectLeftButtonError() + { + if(!g_errEula.errEulaInstance) + return false; + return g_errEula.errEulaInstance->IsDecideSelectLeftButtonError(); + } + + // check if right dialog button was selected + bool IsDecideSelectRightButtonError() + { + if(!g_errEula.errEulaInstance) + return false; + return g_errEula.errEulaInstance->IsDecideSelectRightButtonError(); + } + + sint32 GetResultCode() + { + if(!g_errEula.errEulaInstance) + return -1; + return g_errEula.errEulaInstance->GetResultCode(); + } + + ResultType GetResultType() + { + if(!g_errEula.errEulaInstance) + return ResultType::None; + return g_errEula.errEulaInstance->GetResultType(); } void export_AppearHomeNixSign(PPCInterpreter_t* hCPU) @@ -137,28 +311,24 @@ namespace erreula osLib_returnFromFunction(hCPU, 0); } - void export_AppearError(PPCInterpreter_t* hCPU) + void ErrEulaAppearError(AppearArg* arg) { - ppcDefineParamMEMPTR(arg, AppearArg_t, 0); - - g_errEula.currentDialog = *arg.GetPtr(); - g_errEula.state = ERREULA_STATE_APPEARING; - g_errEula.buttonPressed = false; - g_errEula.rightButtonPressed = false; - - g_errEula.stateTimer = tick_cached(); - - osLib_returnFromFunction(hCPU, 0); + g_errEula.currentDialog = *arg; + if(g_errEula.errEulaInstance) + g_errEula.errEulaInstance->DoAppearError(arg); } - void export_GetStateErrorViewer(PPCInterpreter_t* hCPU) + void ErrEulaDisappearError() { - osLib_returnFromFunction(hCPU, g_errEula.state); + if(g_errEula.errEulaInstance) + g_errEula.errEulaInstance->DoDisappearError(); } - void export_DisappearError(PPCInterpreter_t* hCPU) + + ErrEulaState ErrEulaGetStateErrorViewer() { - g_errEula.state = ERREULA_STATE_HIDDEN; - osLib_returnFromFunction(hCPU, 0); + if(!g_errEula.errEulaInstance) + return ErrEulaState::Hidden; + return g_errEula.errEulaInstance->GetState(); } void export_ChangeLang(PPCInterpreter_t* hCPU) @@ -168,27 +338,6 @@ namespace erreula osLib_returnFromFunction(hCPU, 0); } - void export_IsDecideSelectButtonError(PPCInterpreter_t* hCPU) - { - if (g_errEula.buttonPressed) - cemuLog_logDebug(LogType::Force, "IsDecideSelectButtonError: TRUE"); - osLib_returnFromFunction(hCPU, g_errEula.buttonPressed); - } - - void export_IsDecideSelectLeftButtonError(PPCInterpreter_t* hCPU) - { - if (g_errEula.buttonPressed) - cemuLog_logDebug(LogType::Force, "IsDecideSelectLeftButtonError: TRUE"); - osLib_returnFromFunction(hCPU, g_errEula.buttonPressed); - } - - void export_IsDecideSelectRightButtonError(PPCInterpreter_t* hCPU) - { - if (g_errEula.rightButtonPressed) - cemuLog_logDebug(LogType::Force, "IsDecideSelectRightButtonError: TRUE"); - osLib_returnFromFunction(hCPU, g_errEula.rightButtonPressed); - } - void export_IsAppearHomeNixSign(PPCInterpreter_t* hCPU) { osLib_returnFromFunction(hCPU, g_errEula.homeNixSignVisible); @@ -200,61 +349,19 @@ namespace erreula osLib_returnFromFunction(hCPU, 0); } - void export_GetResultType(PPCInterpreter_t* hCPU) + void ErrEulaCalc(ControllerInfo_t* controllerInfo) { - uint32 result = RESULTTYPE_NONE; - if (g_errEula.buttonPressed || g_errEula.rightButtonPressed) - { - cemuLog_logDebug(LogType::Force, "GetResultType: FINISH"); - result = RESULTTYPE_FINISH; - } - - osLib_returnFromFunction(hCPU, result); - } - - void export_Calc(PPCInterpreter_t* hCPU) - { - ppcDefineParamMEMPTR(controllerInfo, ControllerInfo_t, 0); - // TODO: check controller buttons bla to accept dialog? - osLib_returnFromFunction(hCPU, 0); + if(g_errEula.errEulaInstance) + g_errEula.errEulaInstance->DoCalc(); } void render(bool mainWindow) { - if(g_errEula.state == ERREULA_STATE_HIDDEN) + if(!g_errEula.errEulaInstance) return; - - if(g_errEula.state == ERREULA_STATE_APPEARING) - { - if(std::chrono::duration_cast<std::chrono::milliseconds>(tick_cached() - g_errEula.stateTimer).count() <= 1000) - { - return; - } - - g_errEula.state = ERREULA_STATE_VISIBLE; - g_errEula.stateTimer = tick_cached(); - } - /*else if(g_errEula.state == STATE_VISIBLE) - { - if (std::chrono::duration_cast<std::chrono::milliseconds>(tick_cached() - g_errEula.stateTimer).count() >= 1000) - { - g_errEula.state = STATE_DISAPPEARING; - g_errEula.stateTimer = tick_cached(); - return; - } - }*/ - else if(g_errEula.state == ERREULA_STATE_DISAPPEARING) - { - if (std::chrono::duration_cast<std::chrono::milliseconds>(tick_cached() - g_errEula.stateTimer).count() >= 2000) - { - g_errEula.state = ERREULA_STATE_HIDDEN; - g_errEula.stateTimer = tick_cached(); - } - + if(g_errEula.errEulaInstance->GetState() != ErrEulaState::Visible && g_errEula.errEulaInstance->GetState() != ErrEulaState::Appearing && g_errEula.errEulaInstance->GetState() != ErrEulaState::Disappearing) return; - } - - const AppearArg_t& appearArg = g_errEula.currentDialog; + const AppearError& appearArg = g_errEula.currentDialog; std::string text; const uint32 errorCode = (uint32)appearArg.errorCode; if (errorCode != 0) @@ -276,17 +383,28 @@ namespace erreula ImGui::SetNextWindowPos(position, ImGuiCond_Always, pivot); ImGui::SetNextWindowBgAlpha(0.9f); ImGui::PushFont(font); - + std::string title; if (appearArg.title) title = boost::nowide::narrow(GetText(appearArg.title.GetPtr())); - if(title.empty()) // ImGui doesn't allow empty titles, so set one if appearArg.title is not set or empty + if (title.empty()) // ImGui doesn't allow empty titles, so set one if appearArg.title is not set or empty title = "ErrEula"; + + float fadeTransparency = 1.0f; + if (g_errEula.errEulaInstance->GetState() == ErrEulaState::Appearing || g_errEula.errEulaInstance->GetState() == ErrEulaState::Disappearing) + { + fadeTransparency = g_errEula.errEulaInstance->GetFadeTransparency(); + } + + float originalAlpha = ImGui::GetStyle().Alpha; + ImGui::GetStyle().Alpha = fadeTransparency; + ImGui::SetNextWindowBgAlpha(0.9f * fadeTransparency); if (ImGui::Begin(title.c_str(), nullptr, kPopupFlags)) { const float startx = ImGui::GetWindowSize().x / 2.0f; + bool hasLeftButtonPressed = false, hasRightButtonPressed = false; - switch ((uint32)appearArg.errorType) + switch (appearArg.errorType) { default: { @@ -294,11 +412,10 @@ namespace erreula ImGui::TextUnformatted(text.c_str(), text.c_str() + text.size()); ImGui::Spacing(); ImGui::SetCursorPosX(startx - 50); - g_errEula.buttonPressed |= ImGui::Button("OK", {100, 0}); - + hasLeftButtonPressed = ImGui::Button("OK", {100, 0}); break; } - case ERRORTYPE_TEXT: + case ErrorDialogType::Text: { std::string txtTmp = "Unknown Error"; if (appearArg.text) @@ -309,10 +426,10 @@ namespace erreula ImGui::Spacing(); ImGui::SetCursorPosX(startx - 50); - g_errEula.buttonPressed |= ImGui::Button("OK", { 100, 0 }); + hasLeftButtonPressed = ImGui::Button("OK", { 100, 0 }); break; } - case ERRORTYPE_TEXT_ONE_BUTTON: + case ErrorDialogType::TextOneButton: { std::string txtTmp = "Unknown Error"; if (appearArg.text) @@ -328,10 +445,10 @@ namespace erreula float width = std::max(100.0f, ImGui::CalcTextSize(button1.c_str()).x + 10.0f); ImGui::SetCursorPosX(startx - (width / 2.0f)); - g_errEula.buttonPressed |= ImGui::Button(button1.c_str(), { width, 0 }); + hasLeftButtonPressed = ImGui::Button(button1.c_str(), { width, 0 }); break; } - case ERRORTYPE_TEXT_TWO_BUTTON: + case ErrorDialogType::TextTwoButton: { std::string txtTmp = "Unknown Error"; if (appearArg.text) @@ -352,42 +469,52 @@ namespace erreula float width2 = std::max(100.0f, ImGui::CalcTextSize(button2.c_str()).x + 10.0f); ImGui::SetCursorPosX(startx - (width1 / 2.0f) - (width2 / 2.0f) - 10); - g_errEula.buttonPressed |= ImGui::Button(button1.c_str(), { width1, 0 }); + hasLeftButtonPressed = ImGui::Button(button1.c_str(), { width1, 0 }); ImGui::SameLine(); - g_errEula.rightButtonPressed |= ImGui::Button(button2.c_str(), { width2, 0 }); + hasRightButtonPressed = ImGui::Button(button2.c_str(), { width2, 0 }); break; } } + if (!g_errEula.errEulaInstance->IsDecideSelectButtonError()) + { + if (hasLeftButtonPressed) + g_errEula.errEulaInstance->SetButtonSelection(ErrEulaInstance::BUTTON_SELECTION::LEFT); + if (hasRightButtonPressed) + g_errEula.errEulaInstance->SetButtonSelection(ErrEulaInstance::BUTTON_SELECTION::RIGHT); + } } ImGui::End(); ImGui::PopFont(); - - if(g_errEula.buttonPressed || g_errEula.rightButtonPressed) - { - g_errEula.state = ERREULA_STATE_DISAPPEARING; - g_errEula.stateTimer = tick_cached(); - } + ImGui::GetStyle().Alpha = originalAlpha; } void load() { + g_errEula.errEulaInstance.reset(); + OSInitMutexEx(&g_errEula.mutex, nullptr); - //osLib_addFunction("erreula", "ErrEulaCreate__3RplFPUcQ3_2nn7erreula10", export_ErrEulaCreate); // copy ctor? - osLib_addFunction("erreula", "ErrEulaCreate__3RplFPUcQ3_2nn7erreula10RegionTypeQ3_2nn7erreula8LangTypeP8FSClient", export_ErrEulaCreate); + cafeExportRegisterFunc(ErrEulaCreate, "erreula", "ErrEulaCreate__3RplFPUcQ3_2nn7erreula10RegionTypeQ3_2nn7erreula8LangTypeP8FSClient", LogType::Placeholder); + cafeExportRegisterFunc(ErrEulaDestroy, "erreula", "ErrEulaDestroy__3RplFv", LogType::Placeholder); + + cafeExportRegisterFunc(IsDecideSelectButtonError, "erreula", "ErrEulaIsDecideSelectButtonError__3RplFv", LogType::Placeholder); + cafeExportRegisterFunc(IsDecideSelectLeftButtonError, "erreula", "ErrEulaIsDecideSelectLeftButtonError__3RplFv", LogType::Placeholder); + cafeExportRegisterFunc(IsDecideSelectRightButtonError, "erreula", "ErrEulaIsDecideSelectRightButtonError__3RplFv", LogType::Placeholder); + + cafeExportRegisterFunc(GetResultCode, "erreula", "ErrEulaGetResultCode__3RplFv", LogType::Placeholder); + cafeExportRegisterFunc(GetResultType, "erreula", "ErrEulaGetResultType__3RplFv", LogType::Placeholder); + + cafeExportRegisterFunc(ErrEulaAppearError, "erreula", "ErrEulaAppearError__3RplFRCQ3_2nn7erreula9AppearArg", LogType::Placeholder); + cafeExportRegisterFunc(ErrEulaDisappearError, "erreula", "ErrEulaDisappearError__3RplFv", LogType::Placeholder); + cafeExportRegisterFunc(ErrEulaGetStateErrorViewer, "erreula", "ErrEulaGetStateErrorViewer__3RplFv", LogType::Placeholder); + + cafeExportRegisterFunc(ErrEulaCalc, "erreula", "ErrEulaCalc__3RplFRCQ3_2nn7erreula14ControllerInfo", LogType::Placeholder); + osLib_addFunction("erreula", "ErrEulaAppearHomeNixSign__3RplFRCQ3_2nn7erreula14HomeNixSignArg", export_AppearHomeNixSign); - osLib_addFunction("erreula", "ErrEulaAppearError__3RplFRCQ3_2nn7erreula9AppearArg", export_AppearError); - osLib_addFunction("erreula", "ErrEulaGetStateErrorViewer__3RplFv", export_GetStateErrorViewer); osLib_addFunction("erreula", "ErrEulaChangeLang__3RplFQ3_2nn7erreula8LangType", export_ChangeLang); - osLib_addFunction("erreula", "ErrEulaIsDecideSelectButtonError__3RplFv", export_IsDecideSelectButtonError); - osLib_addFunction("erreula", "ErrEulaCalc__3RplFRCQ3_2nn7erreula14ControllerInfo", export_Calc); - osLib_addFunction("erreula", "ErrEulaIsDecideSelectLeftButtonError__3RplFv", export_IsDecideSelectLeftButtonError); - osLib_addFunction("erreula", "ErrEulaIsDecideSelectRightButtonError__3RplFv", export_IsDecideSelectRightButtonError); osLib_addFunction("erreula", "ErrEulaIsAppearHomeNixSign__3RplFv", export_IsAppearHomeNixSign); osLib_addFunction("erreula", "ErrEulaDisappearHomeNixSign__3RplFv", export_DisappearHomeNixSign); - osLib_addFunction("erreula", "ErrEulaGetResultType__3RplFv", export_GetResultType); - osLib_addFunction("erreula", "ErrEulaDisappearError__3RplFv", export_DisappearError); } } } diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index c83ab16b..69ff4e99 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -483,20 +483,20 @@ bool MainWindow::FileLoad(const fs::path launchPath, wxLaunchGameEvent::INITIATE wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); return false; } - CafeSystem::STATUS_CODE r = CafeSystem::PrepareForegroundTitle(baseTitleId); - if (r == CafeSystem::STATUS_CODE::INVALID_RPX) + CafeSystem::PREPARE_STATUS_CODE r = CafeSystem::PrepareForegroundTitle(baseTitleId); + if (r == CafeSystem::PREPARE_STATUS_CODE::INVALID_RPX) { cemu_assert_debug(false); return false; } - else if (r == CafeSystem::STATUS_CODE::UNABLE_TO_MOUNT) + else if (r == CafeSystem::PREPARE_STATUS_CODE::UNABLE_TO_MOUNT) { wxString t = _("Unable to mount title.\nMake sure the configured game paths are still valid and refresh the game list.\n\nFile which failed to load:\n"); t.append(_pathToUtf8(launchPath)); wxMessageBox(t, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); return false; } - else if (r != CafeSystem::STATUS_CODE::SUCCESS) + else if (r != CafeSystem::PREPARE_STATUS_CODE::SUCCESS) { wxString t = _("Failed to launch game."); t.append(_pathToUtf8(launchPath)); @@ -511,8 +511,8 @@ bool MainWindow::FileLoad(const fs::path launchPath, wxLaunchGameEvent::INITIATE CafeTitleFileType fileType = DetermineCafeSystemFileType(launchPath); if (fileType == CafeTitleFileType::RPX || fileType == CafeTitleFileType::ELF) { - CafeSystem::STATUS_CODE r = CafeSystem::PrepareForegroundTitleFromStandaloneRPX(launchPath); - if (r != CafeSystem::STATUS_CODE::SUCCESS) + CafeSystem::PREPARE_STATUS_CODE r = CafeSystem::PrepareForegroundTitleFromStandaloneRPX(launchPath); + if (r != CafeSystem::PREPARE_STATUS_CODE::SUCCESS) { cemu_assert_debug(false); // todo wxString t = _("Failed to launch executable. Path: "); From 719c631f13c451e045903e79768e98e264a0e9b2 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 13 Nov 2024 06:28:13 +0100 Subject: [PATCH 201/299] config: Fix receive_untested_updates using the wrong default --- src/config/CemuConfig.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/CemuConfig.cpp b/src/config/CemuConfig.cpp index f5ee7ab4..26f420a5 100644 --- a/src/config/CemuConfig.cpp +++ b/src/config/CemuConfig.cpp @@ -38,7 +38,7 @@ void CemuConfig::Load(XMLConfigParser& parser) fullscreen_menubar = parser.get("fullscreen_menubar", false); feral_gamemode = parser.get("feral_gamemode", false); check_update = parser.get("check_update", check_update); - receive_untested_updates = parser.get("receive_untested_updates", check_update); + receive_untested_updates = parser.get("receive_untested_updates", receive_untested_updates); save_screenshot = parser.get("save_screenshot", save_screenshot); did_show_vulkan_warning = parser.get("vk_warning", did_show_vulkan_warning); did_show_graphic_pack_download = parser.get("gp_download", did_show_graphic_pack_download); From 6f9f3d52ea12d3951b2d9d71806f18fea46140e3 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 13 Nov 2024 06:38:13 +0100 Subject: [PATCH 202/299] CI: Remove outdated workflow --- ...imental_release.yml => deploy_release.yml} | 4 +- .github/workflows/deploy_stable_release.yml | 85 ------------------- 2 files changed, 2 insertions(+), 87 deletions(-) rename .github/workflows/{deploy_experimental_release.yml => deploy_release.yml} (98%) delete mode 100644 .github/workflows/deploy_stable_release.yml diff --git a/.github/workflows/deploy_experimental_release.yml b/.github/workflows/deploy_release.yml similarity index 98% rename from .github/workflows/deploy_experimental_release.yml rename to .github/workflows/deploy_release.yml index 97e0c69e..2b9ee491 100644 --- a/.github/workflows/deploy_experimental_release.yml +++ b/.github/workflows/deploy_release.yml @@ -1,4 +1,4 @@ -name: Deploy experimental release +name: Deploy release on: workflow_dispatch: inputs: @@ -54,7 +54,7 @@ jobs: next_version_major: ${{ needs.calculate-version.outputs.next_version_major }} next_version_minor: ${{ needs.calculate-version.outputs.next_version_minor }} deploy: - name: Deploy experimental release + name: Deploy release runs-on: ubuntu-22.04 needs: [call-release-build, calculate-version] steps: diff --git a/.github/workflows/deploy_stable_release.yml b/.github/workflows/deploy_stable_release.yml deleted file mode 100644 index fd339e7d..00000000 --- a/.github/workflows/deploy_stable_release.yml +++ /dev/null @@ -1,85 +0,0 @@ -name: Create new release -on: - workflow_dispatch: - inputs: - PlaceholderInput: - description: PlaceholderInput - required: false -jobs: - call-release-build: - uses: ./.github/workflows/build.yml - with: - deploymode: release - deploy: - name: Deploy release - runs-on: ubuntu-20.04 - needs: call-release-build - steps: - - uses: actions/checkout@v3 - - - uses: actions/download-artifact@v4 - with: - name: cemu-bin-linux-x64 - path: cemu-bin-linux-x64 - - - uses: actions/download-artifact@v4 - with: - name: cemu-appimage-x64 - path: cemu-appimage-x64 - - - uses: actions/download-artifact@v4 - with: - name: cemu-bin-windows-x64 - path: cemu-bin-windows-x64 - - - uses: actions/download-artifact@v4 - with: - name: cemu-bin-macos-x64 - path: cemu-bin-macos-x64 - - - name: Initialize - run: | - mkdir upload - sudo apt update -qq - sudo apt install -y zip - - - name: Get Cemu release version - run: | - gcc -o getversion .github/getversion.cpp - echo "Cemu CI version: $(./getversion)" - echo "CEMU_FOLDER_NAME=Cemu_$(./getversion)" >> $GITHUB_ENV - echo "CEMU_VERSION=$(./getversion)" >> $GITHUB_ENV - - - name: Create release from windows-bin - run: | - ls ./ - ls ./bin/ - cp -R ./bin ./${{ env.CEMU_FOLDER_NAME }} - mv cemu-bin-windows-x64/Cemu.exe ./${{ env.CEMU_FOLDER_NAME }}/Cemu.exe - zip -9 -r upload/cemu-${{ env.CEMU_VERSION }}-windows-x64.zip ${{ env.CEMU_FOLDER_NAME }} - rm -r ./${{ env.CEMU_FOLDER_NAME }} - - - name: Create appimage - run: | - VERSION=${{ env.CEMU_VERSION }} - echo "Cemu Version is $VERSION" - ls cemu-appimage-x64 - mv cemu-appimage-x64/Cemu-*-x86_64.AppImage upload/Cemu-$VERSION-x86_64.AppImage - - - name: Create release from ubuntu-bin - run: | - ls ./ - ls ./bin/ - cp -R ./bin ./${{ env.CEMU_FOLDER_NAME }} - mv cemu-bin-linux-x64/Cemu ./${{ env.CEMU_FOLDER_NAME }}/Cemu - zip -9 -r upload/cemu-${{ env.CEMU_VERSION }}-ubuntu-20.04-x64.zip ${{ env.CEMU_FOLDER_NAME }} - rm -r ./${{ env.CEMU_FOLDER_NAME }} - - - name: Create release from macos-bin - run: cp cemu-bin-macos-x64/Cemu.dmg upload/cemu-${{ env.CEMU_VERSION }}-macos-12-x64.dmg - - - name: Create release - run: | - wget -O ghr.tar.gz https://github.com/tcnksm/ghr/releases/download/v0.15.0/ghr_v0.15.0_linux_amd64.tar.gz - tar xvzf ghr.tar.gz; rm ghr.tar.gz - ghr_v0.15.0_linux_amd64/ghr -t ${{ secrets.GITHUB_TOKEN }} -n "Cemu ${{ env.CEMU_VERSION }}" -b "Changelog:" v${{ env.CEMU_VERSION }} ./upload From 269d5b9aabc2a346f441cae5e662fb32fbb7da41 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Sat, 16 Nov 2024 10:02:43 +0100 Subject: [PATCH 203/299] Vulkan: Make scaling shaders compatible + fixes (#1392) --- src/Cafe/GraphicPack/GraphicPack2.cpp | 2 +- src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp | 9 +- .../Latte/Renderer/OpenGL/OpenGLRenderer.cpp | 11 +- .../HW/Latte/Renderer/RendererOuputShader.cpp | 211 +++++++----------- .../HW/Latte/Renderer/RendererOuputShader.h | 14 +- .../Renderer/Vulkan/LatteTextureViewVk.cpp | 10 + .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 27 +++ 7 files changed, 135 insertions(+), 149 deletions(-) diff --git a/src/Cafe/GraphicPack/GraphicPack2.cpp b/src/Cafe/GraphicPack/GraphicPack2.cpp index c54c31cb..4b6cb095 100644 --- a/src/Cafe/GraphicPack/GraphicPack2.cpp +++ b/src/Cafe/GraphicPack/GraphicPack2.cpp @@ -960,7 +960,7 @@ bool GraphicPack2::Activate() auto option_upscale = rules.FindOption("upscaleMagFilter"); if(option_upscale && boost::iequals(*option_upscale, "NearestNeighbor")) m_output_settings.upscale_filter = LatteTextureView::MagFilter::kNearestNeighbor; - auto option_downscale = rules.FindOption("NearestNeighbor"); + auto option_downscale = rules.FindOption("downscaleMinFilter"); if (option_downscale && boost::iequals(*option_downscale, "NearestNeighbor")) m_output_settings.downscale_filter = LatteTextureView::MagFilter::kNearestNeighbor; } diff --git a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp index ca6a2a4d..3bb6c7e3 100644 --- a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp +++ b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp @@ -933,13 +933,6 @@ void LatteRenderTarget_copyToBackbuffer(LatteTextureView* textureView, bool isPa if (shader == nullptr) { sint32 scaling_filter = downscaling ? GetConfig().downscale_filter : GetConfig().upscale_filter; - - if (g_renderer->GetType() == RendererAPI::Vulkan) - { - // force linear or nearest neighbor filter - if(scaling_filter != kLinearFilter && scaling_filter != kNearestNeighborFilter) - scaling_filter = kLinearFilter; - } if (scaling_filter == kLinearFilter) { @@ -957,7 +950,7 @@ void LatteRenderTarget_copyToBackbuffer(LatteTextureView* textureView, bool isPa else shader = RendererOutputShader::s_bicubic_shader; - filter = LatteTextureView::MagFilter::kNearestNeighbor; + filter = LatteTextureView::MagFilter::kLinear; } else if (scaling_filter == kBicubicHermiteFilter) { diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp index cf134a5d..bbf988bc 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.cpp @@ -570,13 +570,10 @@ void OpenGLRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu g_renderer->ClearColorbuffer(padView); } - sint32 effectiveWidth, effectiveHeight; - texView->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, 0); - shader_unbind(RendererShader::ShaderType::kGeometry); shader_bind(shader->GetVertexShader()); shader_bind(shader->GetFragmentShader()); - shader->SetUniformParameters(*texView, { effectiveWidth, effectiveHeight }, { imageWidth, imageHeight }); + shader->SetUniformParameters(*texView, {imageWidth, imageHeight}); // set viewport glViewportIndexedf(0, imageX, imageY, imageWidth, imageHeight); @@ -584,6 +581,12 @@ void OpenGLRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu LatteTextureViewGL* texViewGL = (LatteTextureViewGL*)texView; texture_bindAndActivate(texView, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + texViewGL->samplerState.clampS = texViewGL->samplerState.clampT = 0xFF; + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, useLinearTexFilter ? GL_LINEAR : GL_NEAREST); + texViewGL->samplerState.filterMin = 0xFFFFFFFF; glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, useLinearTexFilter ? GL_LINEAR : GL_NEAREST); texViewGL->samplerState.filterMag = 0xFFFFFFFF; diff --git a/src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp b/src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp index cdbeb3f3..409dc24f 100644 --- a/src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp +++ b/src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp @@ -2,18 +2,7 @@ #include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h" const std::string RendererOutputShader::s_copy_shader_source = -R"(#version 420 - -#ifdef VULKAN -layout(location = 0) in vec2 passUV; -layout(binding = 0) uniform sampler2D textureSrc; -layout(location = 0) out vec4 colorOut0; -#else -in vec2 passUV; -layout(binding=0) uniform sampler2D textureSrc; -layout(location = 0) out vec4 colorOut0; -#endif - +R"( void main() { colorOut0 = vec4(texture(textureSrc, passUV).rgb,1.0); @@ -22,20 +11,6 @@ void main() const std::string RendererOutputShader::s_bicubic_shader_source = R"( -#version 420 - -#ifdef VULKAN -layout(location = 0) in vec2 passUV; -layout(binding = 0) uniform sampler2D textureSrc; -layout(binding = 1) uniform vec2 textureSrcResolution; -layout(location = 0) out vec4 colorOut0; -#else -in vec2 passUV; -layout(binding=0) uniform sampler2D textureSrc; -uniform vec2 textureSrcResolution; -layout(location = 0) out vec4 colorOut0; -#endif - vec4 cubic(float x) { float x2 = x * x; @@ -48,24 +23,23 @@ vec4 cubic(float x) return w / 6.0; } -vec4 bcFilter(vec2 texcoord, vec2 texscale) +vec4 bcFilter(vec2 uv, vec4 texelSize) { - float fx = fract(texcoord.x); - float fy = fract(texcoord.y); - texcoord.x -= fx; - texcoord.y -= fy; + vec2 pixel = uv*texelSize.zw - 0.5; + vec2 pixelFrac = fract(pixel); + vec2 pixelInt = pixel - pixelFrac; - vec4 xcubic = cubic(fx); - vec4 ycubic = cubic(fy); + vec4 xcubic = cubic(pixelFrac.x); + vec4 ycubic = cubic(pixelFrac.y); - vec4 c = vec4(texcoord.x - 0.5, texcoord.x + 1.5, texcoord.y - 0.5, texcoord.y + 1.5); + vec4 c = vec4(pixelInt.x - 0.5, pixelInt.x + 1.5, pixelInt.y - 0.5, pixelInt.y + 1.5); vec4 s = vec4(xcubic.x + xcubic.y, xcubic.z + xcubic.w, ycubic.x + ycubic.y, ycubic.z + ycubic.w); vec4 offset = c + vec4(xcubic.y, xcubic.w, ycubic.y, ycubic.w) / s; - vec4 sample0 = texture(textureSrc, vec2(offset.x, offset.z) * texscale); - vec4 sample1 = texture(textureSrc, vec2(offset.y, offset.z) * texscale); - vec4 sample2 = texture(textureSrc, vec2(offset.x, offset.w) * texscale); - vec4 sample3 = texture(textureSrc, vec2(offset.y, offset.w) * texscale); + vec4 sample0 = texture(textureSrc, vec2(offset.x, offset.z) * texelSize.xy); + vec4 sample1 = texture(textureSrc, vec2(offset.y, offset.z) * texelSize.xy); + vec4 sample2 = texture(textureSrc, vec2(offset.x, offset.w) * texelSize.xy); + vec4 sample3 = texture(textureSrc, vec2(offset.y, offset.w) * texelSize.xy); float sx = s.x / (s.x + s.y); float sy = s.z / (s.z + s.w); @@ -76,20 +50,13 @@ vec4 bcFilter(vec2 texcoord, vec2 texscale) } void main(){ - colorOut0 = vec4(bcFilter(passUV*textureSrcResolution, vec2(1.0,1.0)/textureSrcResolution).rgb,1.0); + vec4 texelSize = vec4( 1.0 / textureSrcResolution.xy, textureSrcResolution.xy); + colorOut0 = vec4(bcFilter(passUV, texelSize).rgb,1.0); } )"; const std::string RendererOutputShader::s_hermite_shader_source = -R"(#version 420 - -in vec4 gl_FragCoord; -in vec2 passUV; -layout(binding=0) uniform sampler2D textureSrc; -uniform vec2 textureSrcResolution; -uniform vec2 outputResolution; -layout(location = 0) out vec4 colorOut0; - +R"( // https://www.shadertoy.com/view/MllSzX vec3 CubicHermite (vec3 A, vec3 B, vec3 C, vec3 D, float t) @@ -111,7 +78,7 @@ vec3 BicubicHermiteTexture(vec2 uv, vec4 texelSize) vec2 frac = fract(pixel); pixel = floor(pixel) / texelSize.zw - vec2(texelSize.xy/2.0); - vec4 doubleSize = texelSize*texelSize; + vec4 doubleSize = texelSize*2.0; vec3 C00 = texture(textureSrc, pixel + vec2(-texelSize.x ,-texelSize.y)).rgb; vec3 C10 = texture(textureSrc, pixel + vec2( 0.0 ,-texelSize.y)).rgb; @@ -142,15 +109,17 @@ vec3 BicubicHermiteTexture(vec2 uv, vec4 texelSize) } void main(){ - vec4 texelSize = vec4( 1.0 / outputResolution.xy, outputResolution.xy); + vec4 texelSize = vec4( 1.0 / textureSrcResolution.xy, textureSrcResolution.xy); colorOut0 = vec4(BicubicHermiteTexture(passUV, texelSize), 1.0); } )"; RendererOutputShader::RendererOutputShader(const std::string& vertex_source, const std::string& fragment_source) { + auto finalFragmentSrc = PrependFragmentPreamble(fragment_source); + m_vertex_shader = g_renderer->shader_create(RendererShader::ShaderType::kVertex, 0, 0, vertex_source, false, false); - m_fragment_shader = g_renderer->shader_create(RendererShader::ShaderType::kFragment, 0, 0, fragment_source, false, false); + m_fragment_shader = g_renderer->shader_create(RendererShader::ShaderType::kFragment, 0, 0, finalFragmentSrc, false, false); m_vertex_shader->PreponeCompilation(true); m_fragment_shader->PreponeCompilation(true); @@ -163,74 +132,45 @@ RendererOutputShader::RendererOutputShader(const std::string& vertex_source, con if (g_renderer->GetType() == RendererAPI::OpenGL) { - m_attributes[0].m_loc_texture_src_resolution = m_vertex_shader->GetUniformLocation("textureSrcResolution"); - m_attributes[0].m_loc_input_resolution = m_vertex_shader->GetUniformLocation("inputResolution"); - m_attributes[0].m_loc_output_resolution = m_vertex_shader->GetUniformLocation("outputResolution"); + m_uniformLocations[0].m_loc_textureSrcResolution = m_vertex_shader->GetUniformLocation("textureSrcResolution"); + m_uniformLocations[0].m_loc_nativeResolution = m_vertex_shader->GetUniformLocation("nativeResolution"); + m_uniformLocations[0].m_loc_outputResolution = m_vertex_shader->GetUniformLocation("outputResolution"); - m_attributes[1].m_loc_texture_src_resolution = m_fragment_shader->GetUniformLocation("textureSrcResolution"); - m_attributes[1].m_loc_input_resolution = m_fragment_shader->GetUniformLocation("inputResolution"); - m_attributes[1].m_loc_output_resolution = m_fragment_shader->GetUniformLocation("outputResolution"); + m_uniformLocations[1].m_loc_textureSrcResolution = m_fragment_shader->GetUniformLocation("textureSrcResolution"); + m_uniformLocations[1].m_loc_nativeResolution = m_fragment_shader->GetUniformLocation("nativeResolution"); + m_uniformLocations[1].m_loc_outputResolution = m_fragment_shader->GetUniformLocation("outputResolution"); } - else - { - cemuLog_logDebug(LogType::Force, "RendererOutputShader() - todo for Vulkan"); - m_attributes[0].m_loc_texture_src_resolution = -1; - m_attributes[0].m_loc_input_resolution = -1; - m_attributes[0].m_loc_output_resolution = -1; - - m_attributes[1].m_loc_texture_src_resolution = -1; - m_attributes[1].m_loc_input_resolution = -1; - m_attributes[1].m_loc_output_resolution = -1; - } - } -void RendererOutputShader::SetUniformParameters(const LatteTextureView& texture_view, const Vector2i& input_res, const Vector2i& output_res) const +void RendererOutputShader::SetUniformParameters(const LatteTextureView& texture_view, const Vector2i& output_res) const { - float res[2]; - // vertex shader - if (m_attributes[0].m_loc_texture_src_resolution != -1) - { - res[0] = (float)texture_view.baseTexture->width; - res[1] = (float)texture_view.baseTexture->height; - m_vertex_shader->SetUniform2fv(m_attributes[0].m_loc_texture_src_resolution, res, 1); - } + sint32 effectiveWidth, effectiveHeight; + texture_view.baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, 0); + auto setUniforms = [&](RendererShader* shader, const UniformLocations& locations){ + float res[2]; + if (locations.m_loc_textureSrcResolution != -1) + { + res[0] = (float)effectiveWidth; + res[1] = (float)effectiveHeight; + shader->SetUniform2fv(locations.m_loc_textureSrcResolution, res, 1); + } - if (m_attributes[0].m_loc_input_resolution != -1) - { - res[0] = (float)input_res.x; - res[1] = (float)input_res.y; - m_vertex_shader->SetUniform2fv(m_attributes[0].m_loc_input_resolution, res, 1); - } + if (locations.m_loc_nativeResolution != -1) + { + res[0] = (float)texture_view.baseTexture->width; + res[1] = (float)texture_view.baseTexture->height; + shader->SetUniform2fv(locations.m_loc_nativeResolution, res, 1); + } - if (m_attributes[0].m_loc_output_resolution != -1) - { - res[0] = (float)output_res.x; - res[1] = (float)output_res.y; - m_vertex_shader->SetUniform2fv(m_attributes[0].m_loc_output_resolution, res, 1); - } - - // fragment shader - if (m_attributes[1].m_loc_texture_src_resolution != -1) - { - res[0] = (float)texture_view.baseTexture->width; - res[1] = (float)texture_view.baseTexture->height; - m_fragment_shader->SetUniform2fv(m_attributes[1].m_loc_texture_src_resolution, res, 1); - } - - if (m_attributes[1].m_loc_input_resolution != -1) - { - res[0] = (float)input_res.x; - res[1] = (float)input_res.y; - m_fragment_shader->SetUniform2fv(m_attributes[1].m_loc_input_resolution, res, 1); - } - - if (m_attributes[1].m_loc_output_resolution != -1) - { - res[0] = (float)output_res.x; - res[1] = (float)output_res.y; - m_fragment_shader->SetUniform2fv(m_attributes[1].m_loc_output_resolution, res, 1); - } + if (locations.m_loc_outputResolution != -1) + { + res[0] = (float)output_res.x; + res[1] = (float)output_res.y; + shader->SetUniform2fv(locations.m_loc_outputResolution, res, 1); + } + }; + setUniforms(m_vertex_shader, m_uniformLocations[0]); + setUniforms(m_fragment_shader, m_uniformLocations[1]); } RendererOutputShader* RendererOutputShader::s_copy_shader; @@ -341,6 +281,27 @@ void main(){ )"; return vertex_source.str(); } + +std::string RendererOutputShader::PrependFragmentPreamble(const std::string& shaderSrc) +{ + return R"(#version 430 +#ifdef VULKAN +layout(push_constant) uniform pc { + vec2 textureSrcResolution; + vec2 nativeResolution; + vec2 outputResolution; +}; +#else +uniform vec2 textureSrcResolution; +uniform vec2 nativeResolution; +uniform vec2 outputResolution; +#endif + +layout(location = 0) in vec2 passUV; +layout(binding = 0) uniform sampler2D textureSrc; +layout(location = 0) out vec4 colorOut0; +)" + shaderSrc; +} void RendererOutputShader::InitializeStatic() { std::string vertex_source, vertex_source_ud; @@ -349,28 +310,18 @@ void RendererOutputShader::InitializeStatic() { vertex_source = GetOpenGlVertexSource(false); vertex_source_ud = GetOpenGlVertexSource(true); - - s_copy_shader = new RendererOutputShader(vertex_source, s_copy_shader_source); - s_copy_shader_ud = new RendererOutputShader(vertex_source_ud, s_copy_shader_source); - - s_bicubic_shader = new RendererOutputShader(vertex_source, s_bicubic_shader_source); - s_bicubic_shader_ud = new RendererOutputShader(vertex_source_ud, s_bicubic_shader_source); - - s_hermit_shader = new RendererOutputShader(vertex_source, s_hermite_shader_source); - s_hermit_shader_ud = new RendererOutputShader(vertex_source_ud, s_hermite_shader_source); } else { vertex_source = GetVulkanVertexSource(false); vertex_source_ud = GetVulkanVertexSource(true); - - s_copy_shader = new RendererOutputShader(vertex_source, s_copy_shader_source); - s_copy_shader_ud = new RendererOutputShader(vertex_source_ud, s_copy_shader_source); - - /* s_bicubic_shader = new RendererOutputShader(vertex_source, s_bicubic_shader_source); TODO - s_bicubic_shader_ud = new RendererOutputShader(vertex_source_ud, s_bicubic_shader_source); - - s_hermit_shader = new RendererOutputShader(vertex_source, s_hermite_shader_source); - s_hermit_shader_ud = new RendererOutputShader(vertex_source_ud, s_hermite_shader_source);*/ } + s_copy_shader = new RendererOutputShader(vertex_source, s_copy_shader_source); + s_copy_shader_ud = new RendererOutputShader(vertex_source_ud, s_copy_shader_source); + + s_bicubic_shader = new RendererOutputShader(vertex_source, s_bicubic_shader_source); + s_bicubic_shader_ud = new RendererOutputShader(vertex_source_ud, s_bicubic_shader_source); + + s_hermit_shader = new RendererOutputShader(vertex_source, s_hermite_shader_source); + s_hermit_shader_ud = new RendererOutputShader(vertex_source_ud, s_hermite_shader_source); } diff --git a/src/Cafe/HW/Latte/Renderer/RendererOuputShader.h b/src/Cafe/HW/Latte/Renderer/RendererOuputShader.h index 398ac663..61b24c20 100644 --- a/src/Cafe/HW/Latte/Renderer/RendererOuputShader.h +++ b/src/Cafe/HW/Latte/Renderer/RendererOuputShader.h @@ -17,7 +17,7 @@ public: RendererOutputShader(const std::string& vertex_source, const std::string& fragment_source); virtual ~RendererOutputShader() = default; - void SetUniformParameters(const LatteTextureView& texture_view, const Vector2i& input_res, const Vector2i& output_res) const; + void SetUniformParameters(const LatteTextureView& texture_view, const Vector2i& output_res) const; RendererShader* GetVertexShader() const { @@ -43,16 +43,18 @@ public: static std::string GetVulkanVertexSource(bool render_upside_down); static std::string GetOpenGlVertexSource(bool render_upside_down); + static std::string PrependFragmentPreamble(const std::string& shaderSrc); + protected: RendererShader* m_vertex_shader; RendererShader* m_fragment_shader; - struct + struct UniformLocations { - sint32 m_loc_texture_src_resolution = -1; - sint32 m_loc_input_resolution = -1; - sint32 m_loc_output_resolution = -1; - } m_attributes[2]{}; + sint32 m_loc_textureSrcResolution = -1; + sint32 m_loc_nativeResolution = -1; + sint32 m_loc_outputResolution = -1; + } m_uniformLocations[2]{}; private: static const std::string s_copy_shader_source; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.cpp index f0e2295e..de76f76d 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/LatteTextureViewVk.cpp @@ -202,6 +202,13 @@ VkSampler LatteTextureViewVk::GetDefaultTextureSampler(bool useLinearTexFilter) VkSamplerCreateInfo samplerInfo{}; samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + // emulate OpenGL minFilters + // see note under: https://docs.vulkan.org/spec/latest/chapters/samplers.html#VkSamplerCreateInfo + // if maxLod = 0 then magnification is always performed + samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + samplerInfo.minLod = 0.0f; + samplerInfo.maxLod = 0.25f; + if (useLinearTexFilter) { samplerInfo.magFilter = VK_FILTER_LINEAR; @@ -212,6 +219,9 @@ VkSampler LatteTextureViewVk::GetDefaultTextureSampler(bool useLinearTexFilter) samplerInfo.magFilter = VK_FILTER_NEAREST; samplerInfo.minFilter = VK_FILTER_NEAREST; } + samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; if (vkCreateSampler(m_device, &samplerInfo, nullptr, &sampler) != VK_SUCCESS) { diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 9ad2c5ca..37432eeb 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -2581,10 +2581,18 @@ VkPipeline VulkanRenderer::backbufferBlit_createGraphicsPipeline(VkDescriptorSet colorBlending.blendConstants[2] = 0.0f; colorBlending.blendConstants[3] = 0.0f; + VkPushConstantRange pushConstantRange{ + .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, + .offset = 0, + .size = 3 * sizeof(float) * 2 // 3 vec2's + }; + VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutInfo.setLayoutCount = 1; pipelineLayoutInfo.pSetLayouts = &descriptorLayout; + pipelineLayoutInfo.pushConstantRangeCount = 1; + pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange; VkResult result = vkCreatePipelineLayout(m_logicalDevice, &pipelineLayoutInfo, nullptr, &m_pipelineLayout); if (result != VK_SUCCESS) @@ -2956,6 +2964,25 @@ void VulkanRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &descriptSet, 0, nullptr); + // update push constants + Vector2f pushData[3]; + + // textureSrcResolution + sint32 effectiveWidth, effectiveHeight; + texView->baseTexture->GetEffectiveSize(effectiveWidth, effectiveHeight, 0); + pushData[0] = {(float)effectiveWidth, (float)effectiveHeight}; + + // nativeResolution + pushData[1] = { + (float)texViewVk->baseTexture->width, + (float)texViewVk->baseTexture->height, + }; + + // outputResolution + pushData[2] = {(float)imageWidth,(float)imageHeight}; + + vkCmdPushConstants(m_state.currentCommandBuffer, m_pipelineLayout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(float) * 2 * 3, &pushData); + vkCmdDraw(m_state.currentCommandBuffer, 6, 1, 0, 0); vkCmdEndRenderPass(m_state.currentCommandBuffer); From 2065ac5f635e48a6bf01ac480236783f46bcf0de Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 16 Nov 2024 12:51:58 +0100 Subject: [PATCH 204/299] GfxPack: Better logging messages for diagnosing problems in rules.txt --- src/Cafe/GraphicPack/GraphicPack2.cpp | 42 ++++++++++++++++----------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/src/Cafe/GraphicPack/GraphicPack2.cpp b/src/Cafe/GraphicPack/GraphicPack2.cpp index 4b6cb095..f21bb89d 100644 --- a/src/Cafe/GraphicPack/GraphicPack2.cpp +++ b/src/Cafe/GraphicPack/GraphicPack2.cpp @@ -345,7 +345,7 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules) const auto preset_name = rules.FindOption("name"); if (!preset_name) { - cemuLog_log(LogType::Force, "Graphic pack \"{}\": Preset in line {} skipped because it has no name option defined", m_name, rules.GetCurrentSectionLineNumber()); + cemuLog_log(LogType::Force, "Graphic pack \"{}\": Preset in line {} skipped because it has no name option defined", GetNormalizedPathString(), rules.GetCurrentSectionLineNumber()); continue; } @@ -369,7 +369,7 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules) } catch (const std::exception & ex) { - cemuLog_log(LogType::Force, "Graphic pack \"{}\": Can't parse preset \"{}\": {}", m_name, *preset_name, ex.what()); + cemuLog_log(LogType::Force, "Graphic pack \"{}\": Can't parse preset \"{}\": {}", GetNormalizedPathString(), *preset_name, ex.what()); } } else if (boost::iequals(currentSectionName, "RAM")) @@ -383,7 +383,7 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules) { if (m_version <= 5) { - cemuLog_log(LogType::Force, "Graphic pack \"{}\": [RAM] options are only available for graphic pack version 6 or higher", m_name, optionNameBuf); + cemuLog_log(LogType::Force, "Graphic pack \"{}\": [RAM] options are only available for graphic pack version 6 or higher", GetNormalizedPathString(), optionNameBuf); throw std::exception(); } @@ -393,12 +393,12 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules) { if (addrEnd <= addrStart) { - cemuLog_log(LogType::Force, "Graphic pack \"{}\": start address (0x{:08x}) must be greater than end address (0x{:08x}) for {}", m_name, addrStart, addrEnd, optionNameBuf); + cemuLog_log(LogType::Force, "Graphic pack \"{}\": start address (0x{:08x}) must be greater than end address (0x{:08x}) for {}", GetNormalizedPathString(), addrStart, addrEnd, optionNameBuf); throw std::exception(); } else if ((addrStart & 0xFFF) != 0 || (addrEnd & 0xFFF) != 0) { - cemuLog_log(LogType::Force, "Graphic pack \"{}\": addresses for %s are not aligned to 0x1000", m_name, optionNameBuf); + cemuLog_log(LogType::Force, "Graphic pack \"{}\": addresses for %s are not aligned to 0x1000", GetNormalizedPathString(), optionNameBuf); throw std::exception(); } else @@ -408,7 +408,7 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules) } else { - cemuLog_log(LogType::Force, "Graphic pack \"{}\": has invalid syntax for option {}", m_name, optionNameBuf); + cemuLog_log(LogType::Force, "Graphic pack \"{}\": has invalid syntax for option {}", GetNormalizedPathString(), optionNameBuf); throw std::exception(); } } @@ -422,24 +422,32 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules) std::unordered_map<std::string, std::vector<PresetPtr>> tmp_map; // all vars must be defined in the default preset vars before - for (const auto& entry : m_presets) + std::vector<std::pair<std::string, std::string>> mismatchingPresetVars; + for (const auto& presetEntry : m_presets) { - tmp_map[entry->category].emplace_back(entry); + tmp_map[presetEntry->category].emplace_back(presetEntry); - for (auto& kv : entry->variables) + for (auto& presetVar : presetEntry->variables) { - const auto it = m_preset_vars.find(kv.first); + const auto it = m_preset_vars.find(presetVar.first); if (it == m_preset_vars.cend()) { - cemuLog_log(LogType::Force, "Graphic pack: \"{}\" contains preset variables which are not defined in the default section", m_name); - throw std::exception(); + mismatchingPresetVars.emplace_back(presetEntry->name, presetVar.first); + continue; } - // overwrite var type with default var type - kv.second.first = it->second.first; + presetVar.second.first = it->second.first; } } + if(!mismatchingPresetVars.empty()) + { + cemuLog_log(LogType::Force, "Graphic pack \"{}\" contains preset variables which are not defined in the [Default] section:", GetNormalizedPathString()); + for (const auto& [presetName, varName] : mismatchingPresetVars) + cemuLog_log(LogType::Force, "Preset: {} Variable: {}", presetName, varName); + throw std::exception(); + } + // have first entry be default active for every category if no default= is set for(auto entry : get_values(tmp_map)) { @@ -469,7 +477,7 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules) auto& p2 = kv.second[i + 1]; if (p1->variables.size() != p2->variables.size()) { - cemuLog_log(LogType::Force, "Graphic pack: \"{}\" contains inconsistent preset variables", m_name); + cemuLog_log(LogType::Force, "Graphic pack: \"{}\" contains inconsistent preset variables", GetNormalizedPathString()); throw std::exception(); } @@ -477,14 +485,14 @@ GraphicPack2::GraphicPack2(fs::path rulesPath, IniParser& rules) std::set<std::string> keys2(get_keys(p2->variables).begin(), get_keys(p2->variables).end()); if (keys1 != keys2) { - cemuLog_log(LogType::Force, "Graphic pack: \"{}\" contains inconsistent preset variables", m_name); + cemuLog_log(LogType::Force, "Graphic pack: \"{}\" contains inconsistent preset variables", GetNormalizedPathString()); throw std::exception(); } if(p1->is_default) { if(has_default) - cemuLog_log(LogType::Force, "Graphic pack: \"{}\" has more than one preset with the default key set for the same category \"{}\"", m_name, p1->name); + cemuLog_log(LogType::Force, "Graphic pack: \"{}\" has more than one preset with the default key set for the same category \"{}\"", GetNormalizedPathString(), p1->name); p1->active = true; has_default = true; } From c3e29fb6199cfd297a86dd0f6bc0d92fe7dab3de Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 16 Nov 2024 13:45:33 +0100 Subject: [PATCH 205/299] Latte: Add support for shader instructions MIN_UINT and MAX_UINT Seen in the eShop version of Fatal Frame Also made some warnings less spammy since this game seems to trigger it a lot --- .../LatteDecompiler.cpp | 2 ++ .../LatteDecompilerAnalyzer.cpp | 2 ++ .../LatteDecompilerEmitGLSL.cpp | 20 +++++++++++-------- .../LatteDecompilerInstructions.h | 2 ++ src/Cafe/OS/libs/gx2/GX2_Shader.cpp | 2 +- src/input/emulated/VPADController.cpp | 2 +- 6 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.cpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.cpp index c3f7c19e..5972aacc 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.cpp +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.cpp @@ -370,6 +370,8 @@ bool LatteDecompiler_IsALUTransInstruction(bool isOP3, uint32 opcode) opcode == ALU_OP2_INST_LSHR_INT || opcode == ALU_OP2_INST_MAX_INT || opcode == ALU_OP2_INST_MIN_INT || + opcode == ALU_OP2_INST_MAX_UINT || + opcode == ALU_OP2_INST_MIN_UINT || opcode == ALU_OP2_INST_MOVA_FLOOR || opcode == ALU_OP2_INST_MOVA_INT || opcode == ALU_OP2_INST_SETE_DX10 || diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp index 19604e0c..ff64988c 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerAnalyzer.cpp @@ -140,6 +140,8 @@ bool _isIntegerInstruction(const LatteDecompilerALUInstruction& aluInstruction) case ALU_OP2_INST_SUB_INT: case ALU_OP2_INST_MAX_INT: case ALU_OP2_INST_MIN_INT: + case ALU_OP2_INST_MAX_UINT: + case ALU_OP2_INST_MIN_UINT: case ALU_OP2_INST_SETE_INT: case ALU_OP2_INST_SETGT_INT: case ALU_OP2_INST_SETGE_INT: diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp index 7a6605f8..e7ebcf3a 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerEmitGLSL.cpp @@ -1415,19 +1415,23 @@ void _emitALUOP2InstructionCode(LatteDecompilerShaderContext* shaderContext, Lat } else if( aluInstruction->opcode == ALU_OP2_INST_ADD_INT ) _emitALUOperationBinary<LATTE_DECOMPILER_DTYPE_SIGNED_INT>(shaderContext, aluInstruction, " + "); - else if( aluInstruction->opcode == ALU_OP2_INST_MAX_INT || aluInstruction->opcode == ALU_OP2_INST_MIN_INT ) + else if( aluInstruction->opcode == ALU_OP2_INST_MAX_INT || aluInstruction->opcode == ALU_OP2_INST_MIN_INT || + aluInstruction->opcode == ALU_OP2_INST_MAX_UINT || aluInstruction->opcode == ALU_OP2_INST_MIN_UINT) { // not verified + bool isUnsigned = aluInstruction->opcode == ALU_OP2_INST_MAX_UINT || aluInstruction->opcode == ALU_OP2_INST_MIN_UINT; + auto opType = isUnsigned ? LATTE_DECOMPILER_DTYPE_UNSIGNED_INT : LATTE_DECOMPILER_DTYPE_SIGNED_INT; _emitInstructionOutputVariableName(shaderContext, aluInstruction); - if( aluInstruction->opcode == ALU_OP2_INST_MAX_INT ) - src->add(" = max("); + src->add(" = "); + _emitTypeConversionPrefix(shaderContext, opType, outputType); + if( aluInstruction->opcode == ALU_OP2_INST_MAX_INT || aluInstruction->opcode == ALU_OP2_INST_MAX_UINT ) + src->add("max("); else - src->add(" = min("); - _emitTypeConversionPrefix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); - _emitOperandInputCode(shaderContext, aluInstruction, 0, LATTE_DECOMPILER_DTYPE_SIGNED_INT); + src->add("min("); + _emitOperandInputCode(shaderContext, aluInstruction, 0, opType); src->add(", "); - _emitOperandInputCode(shaderContext, aluInstruction, 1, LATTE_DECOMPILER_DTYPE_SIGNED_INT); - _emitTypeConversionSuffix(shaderContext, LATTE_DECOMPILER_DTYPE_SIGNED_INT, outputType); + _emitOperandInputCode(shaderContext, aluInstruction, 1, opType); + _emitTypeConversionSuffix(shaderContext, opType, outputType); src->add(");" _CRLF); } else if( aluInstruction->opcode == ALU_OP2_INST_SUB_INT ) diff --git a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInstructions.h b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInstructions.h index 4cb1982e..6c029b46 100644 --- a/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInstructions.h +++ b/src/Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompilerInstructions.h @@ -60,6 +60,8 @@ #define ALU_OP2_INST_SUB_INT (0x035) // integer instruction #define ALU_OP2_INST_MAX_INT (0x036) // integer instruction #define ALU_OP2_INST_MIN_INT (0x037) // integer instruction +#define ALU_OP2_INST_MAX_UINT (0x038) // integer instruction +#define ALU_OP2_INST_MIN_UINT (0x039) // integer instruction #define ALU_OP2_INST_SETE_INT (0x03A) // integer instruction #define ALU_OP2_INST_SETGT_INT (0x03B) // integer instruction #define ALU_OP2_INST_SETGE_INT (0x03C) // integer instruction diff --git a/src/Cafe/OS/libs/gx2/GX2_Shader.cpp b/src/Cafe/OS/libs/gx2/GX2_Shader.cpp index dfbbfcff..7a153737 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Shader.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_Shader.cpp @@ -421,7 +421,7 @@ namespace GX2 { if(aluRegisterOffset&0x8000) { - cemuLog_logDebug(LogType::Force, "_GX2SubmitUniformReg(): Unhandled loop const special case or invalid offset"); + cemuLog_logDebugOnce(LogType::Force, "_GX2SubmitUniformReg(): Unhandled loop const special case or invalid offset"); return; } if((aluRegisterOffset+sizeInU32s) > 0x400) diff --git a/src/input/emulated/VPADController.cpp b/src/input/emulated/VPADController.cpp index f1ab1bc4..81615c9b 100644 --- a/src/input/emulated/VPADController.cpp +++ b/src/input/emulated/VPADController.cpp @@ -408,7 +408,7 @@ bool VPADController::push_rumble(uint8* pattern, uint8 length) std::scoped_lock lock(m_rumble_mutex); if (m_rumble_queue.size() >= 5) { - cemuLog_logDebug(LogType::Force, "too many cmds"); + cemuLog_logDebugOnce(LogType::Force, "VPADControlMotor(): Pattern too long"); return false; } From 7b513f1744a7952abf2fb927c27b2c09a2c992f4 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 17 Nov 2024 09:52:44 +0100 Subject: [PATCH 206/299] Latte: Add workaround for infinite loop in Fatal Frame shaders --- src/config/ActiveSettings.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/config/ActiveSettings.cpp b/src/config/ActiveSettings.cpp index 560f2986..f81f8336 100644 --- a/src/config/ActiveSettings.cpp +++ b/src/config/ActiveSettings.cpp @@ -198,14 +198,20 @@ bool ActiveSettings::ShaderPreventInfiniteLoopsEnabled() { const uint64 titleId = CafeSystem::GetForegroundTitleId(); // workaround for NSMBU (and variants) having a bug where shaders can get stuck in infinite loops - // update: As of Cemu 1.20.0 this should no longer be required + // Fatal Frame has an actual infinite loop in shader 0xb6a67c19f6472e00 encountered during a cutscene for the second drop (eShop version only?) + // update: As of Cemu 1.20.0 this should no longer be required for NSMBU/NSLU due to fixes with uniform handling. But we leave it here for good measure + // todo - Once we add support for loop config registers this workaround should become unnecessary return /* NSMBU JP */ titleId == 0x0005000010101C00 || /* NSMBU US */ titleId == 0x0005000010101D00 || /* NSMBU EU */ titleId == 0x0005000010101E00 || /* NSMBU+L US */ titleId == 0x000500001014B700 || /* NSMBU+L EU */ titleId == 0x000500001014B800 || /* NSLU US */ titleId == 0x0005000010142300 || - /* NSLU EU */ titleId == 0x0005000010142400; + /* NSLU EU */ titleId == 0x0005000010142400 || + /* Project Zero: Maiden of Black Water (EU) */ titleId == 0x00050000101D0300 || + /* Fatal Frame: Maiden of Black Water (US) */ titleId == 0x00050000101D0600 || + /* Project Zero: Maiden of Black Water (JP) */ titleId == 0x000500001014D200 || + /* Project Zero: Maiden of Black Water (Trial, EU) */ titleId == 0x00050000101D3F00; } bool ActiveSettings::FlushGPUCacheOnSwap() From 409f12b13a2d67f18465c2920ab04a4d5ab27ae6 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 17 Nov 2024 10:38:39 +0100 Subject: [PATCH 207/299] coreinit: Fix calculation of thread total awake time --- src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp index 2f3808b7..db457047 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp @@ -1114,13 +1114,13 @@ namespace coreinit thread->requestFlags = (OSThread_t::REQUEST_FLAG_BIT)(thread->requestFlags & OSThread_t::REQUEST_FLAG_CANCEL); // remove all flags except cancel flag // update total cycles - uint64 remainingCycles = std::min((uint64)hCPU->remainingCycles, (uint64)thread->quantumTicks); - uint64 executedCycles = thread->quantumTicks - remainingCycles; - if (executedCycles < hCPU->skippedCycles) + sint64 executedCycles = (sint64)thread->quantumTicks - (sint64)hCPU->remainingCycles; + executedCycles = std::max<sint64>(executedCycles, 0); + if (executedCycles < (sint64)hCPU->skippedCycles) executedCycles = 0; else executedCycles -= hCPU->skippedCycles; - thread->totalCycles += executedCycles; + thread->totalCycles += (uint64)executedCycles; // store context and set current thread to null __OSThreadStoreContext(hCPU, thread); OSSetCurrentThread(OSGetCoreId(), nullptr); From 90eb2e01f405e502ed33fea330352e167c4a14db Mon Sep 17 00:00:00 2001 From: capitalistspz <keipitalists@proton.me> Date: Fri, 22 Nov 2024 12:43:12 +0000 Subject: [PATCH 208/299] nsyshid/dimensions: add missing return (#1425) --- src/Cafe/OS/libs/nsyshid/Dimensions.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Cafe/OS/libs/nsyshid/Dimensions.cpp b/src/Cafe/OS/libs/nsyshid/Dimensions.cpp index f328dde7..8a2acc76 100644 --- a/src/Cafe/OS/libs/nsyshid/Dimensions.cpp +++ b/src/Cafe/OS/libs/nsyshid/Dimensions.cpp @@ -675,6 +675,7 @@ namespace nsyshid figureChangeResponse[13] = GenerateChecksum(figureChangeResponse, 13); m_figureAddedRemovedResponses.push(figureChangeResponse); + return true; } bool DimensionsUSB::CancelRemove(uint8 index) From 073523768692c1adb994ce2f07d1c29530de41e6 Mon Sep 17 00:00:00 2001 From: capitalistspz <keipitalists@proton.me> Date: Sat, 30 Nov 2024 22:05:50 +0000 Subject: [PATCH 209/299] Input: Move pairing dialog button and source (#1424) --- src/gui/CMakeLists.txt | 4 ++-- src/gui/input/InputSettings2.cpp | 9 +++++++++ src/gui/{ => input}/PairingDialog.cpp | 2 +- src/gui/{ => input}/PairingDialog.h | 0 src/gui/input/panels/WiimoteInputPanel.cpp | 12 ------------ 5 files changed, 12 insertions(+), 15 deletions(-) rename src/gui/{ => input}/PairingDialog.cpp (99%) rename src/gui/{ => input}/PairingDialog.h (100%) diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 02f96a9c..e1a04ec0 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -75,6 +75,8 @@ add_library(CemuGui input/InputAPIAddWindow.h input/InputSettings2.cpp input/InputSettings2.h + input/PairingDialog.cpp + input/PairingDialog.h input/panels/ClassicControllerInputPanel.cpp input/panels/ClassicControllerInputPanel.h input/panels/InputPanel.cpp @@ -97,8 +99,6 @@ add_library(CemuGui MemorySearcherTool.h PadViewFrame.cpp PadViewFrame.h - PairingDialog.cpp - PairingDialog.h TitleManager.cpp TitleManager.h EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp diff --git a/src/gui/input/InputSettings2.cpp b/src/gui/input/InputSettings2.cpp index 72bf4f7d..2ae8a74b 100644 --- a/src/gui/input/InputSettings2.cpp +++ b/src/gui/input/InputSettings2.cpp @@ -20,6 +20,8 @@ #include "gui/input/InputAPIAddWindow.h" #include "input/ControllerFactory.h" +#include "gui/input/PairingDialog.h" + #include "gui/input/panels/VPADInputPanel.h" #include "gui/input/panels/ProControllerInputPanel.h" @@ -252,6 +254,13 @@ wxWindow* InputSettings2::initialize_page(size_t index) page_data.m_controller_api_remove = remove_api; } + auto* pairingDialog = new wxButton(page, wxID_ANY, _("Pair Wii/Wii U Controller")); + pairingDialog->Bind(wxEVT_BUTTON, [this](wxEvent&) { + PairingDialog pairing_dialog(this); + pairing_dialog.ShowModal(); + }); + sizer->Add(pairingDialog, wxGBPosition(5, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL | wxALL, 5); + // controller auto* controller_bttns = new wxBoxSizer(wxHORIZONTAL); auto* settings = new wxButton(page, wxID_ANY, _("Settings"), wxDefaultPosition, wxDefaultSize, 0); diff --git a/src/gui/PairingDialog.cpp b/src/gui/input/PairingDialog.cpp similarity index 99% rename from src/gui/PairingDialog.cpp rename to src/gui/input/PairingDialog.cpp index f90e6d13..03d6315b 100644 --- a/src/gui/PairingDialog.cpp +++ b/src/gui/input/PairingDialog.cpp @@ -1,5 +1,5 @@ #include "gui/wxgui.h" -#include "gui/PairingDialog.h" +#include "PairingDialog.h" #if BOOST_OS_WINDOWS #include <bluetoothapis.h> diff --git a/src/gui/PairingDialog.h b/src/gui/input/PairingDialog.h similarity index 100% rename from src/gui/PairingDialog.h rename to src/gui/input/PairingDialog.h diff --git a/src/gui/input/panels/WiimoteInputPanel.cpp b/src/gui/input/panels/WiimoteInputPanel.cpp index 050baad1..44a7c001 100644 --- a/src/gui/input/panels/WiimoteInputPanel.cpp +++ b/src/gui/input/panels/WiimoteInputPanel.cpp @@ -12,7 +12,6 @@ #include "input/emulated/WiimoteController.h" #include "gui/helpers/wxHelpers.h" #include "gui/components/wxInputDraw.h" -#include "gui/PairingDialog.h" constexpr WiimoteController::ButtonId g_kFirstColumnItems[] = { @@ -40,11 +39,6 @@ WiimoteInputPanel::WiimoteInputPanel(wxWindow* parent) auto* main_sizer = new wxBoxSizer(wxVERTICAL); auto* horiz_main_sizer = new wxBoxSizer(wxHORIZONTAL); - auto* pair_button = new wxButton(this, wxID_ANY, _("Pair a Wii or Wii U controller")); - pair_button->Bind(wxEVT_BUTTON, &WiimoteInputPanel::on_pair_button, this); - horiz_main_sizer->Add(pair_button); - horiz_main_sizer->AddSpacer(10); - auto* extensions_sizer = new wxBoxSizer(wxHORIZONTAL); horiz_main_sizer->Add(extensions_sizer, wxSizerFlags(0).Align(wxALIGN_CENTER_VERTICAL)); @@ -264,9 +258,3 @@ void WiimoteInputPanel::load_controller(const EmulatedControllerPtr& emulated_co set_active_device_type(wiimote->get_device_type()); } } - -void WiimoteInputPanel::on_pair_button(wxCommandEvent& event) -{ - PairingDialog pairing_dialog(this); - pairing_dialog.ShowModal(); -} From 80a6057512240b14f6aa6ab45ba955e25a1c7acf Mon Sep 17 00:00:00 2001 From: Jeremy Kescher <jeremy@kescher.at> Date: Mon, 2 Dec 2024 01:01:22 +0100 Subject: [PATCH 210/299] build: Fix linker failure with glslang 15.0.0 (#1436) --- src/Cafe/CMakeLists.txt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index 0901fece..76dba007 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -532,6 +532,12 @@ set_property(TARGET CemuCafe PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CON target_include_directories(CemuCafe PUBLIC "../") +if (glslang_VERSION VERSION_LESS "15.0.0") + set(glslang_target "glslang::SPIRV") +else() + set(glslang_target "glslang") +endif() + target_link_libraries(CemuCafe PRIVATE CemuAsm CemuAudio @@ -547,7 +553,7 @@ target_link_libraries(CemuCafe PRIVATE Boost::nowide CURL::libcurl fmt::fmt - glslang::SPIRV + ${glslang_target} ih264d OpenSSL::Crypto OpenSSL::SSL From eca7374567bff66a22b0d210f15744bcaafb8ad3 Mon Sep 17 00:00:00 2001 From: neebyA <126654084+neebyA@users.noreply.github.com> Date: Sun, 1 Dec 2024 20:19:15 -0800 Subject: [PATCH 211/299] Set version for macOS bundle (#1431) --- src/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3792ab85..79471321 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -82,8 +82,8 @@ if (MACOS_BUNDLE) set(MACOSX_BUNDLE_ICON_FILE "cemu.icns") set(MACOSX_BUNDLE_GUI_IDENTIFIER "info.cemu.Cemu") set(MACOSX_BUNDLE_BUNDLE_NAME "Cemu") - set(MACOSX_BUNDLE_SHORT_VERSION_STRING ${CMAKE_PROJECT_VERSION}) - set(MACOSX_BUNDLE_BUNDLE_VERSION ${CMAKE_PROJECT_VERSION}) + set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${EMULATOR_VERSION_MAJOR}.${EMULATOR_VERSION_MINOR}.${EMULATOR_VERSION_PATCH}") + set(MACOSX_BUNDLE_BUNDLE_VERSION "${EMULATOR_VERSION_MAJOR}.${EMULATOR_VERSION_MINOR}.${EMULATOR_VERSION_PATCH}") set(MACOSX_BUNDLE_COPYRIGHT "Copyright © 2024 Cemu Project") set(MACOSX_BUNDLE_CATEGORY "public.app-category.games") From 40d9664d1c23a7a1ff6e0197a77a4358032b870a Mon Sep 17 00:00:00 2001 From: Cemu-Language CI <github-actions@github.com> Date: Sat, 7 Dec 2024 07:14:20 +0000 Subject: [PATCH 212/299] Update translation files --- bin/resources/ar/‏‏cemu.mo | Bin 0 -> 81849 bytes bin/resources/de/cemu.mo | Bin 65571 -> 69412 bytes bin/resources/ru/cemu.mo | Bin 90752 -> 91888 bytes bin/resources/sv/cemu.mo | Bin 15821 -> 67980 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 bin/resources/ar/‏‏cemu.mo diff --git a/bin/resources/ar/‏‏cemu.mo b/bin/resources/ar/‏‏cemu.mo new file mode 100644 index 0000000000000000000000000000000000000000..4062628be1486a0ffe6a0d2731c6853e4b133880 GIT binary patch literal 81849 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 TKs4mH0zXmFQQdOH$_hRRf@rscp0;p3<RLH z1|gJoaB2k%y}em5V{WPYoekOCI#A|EyYYJond}B4^ab!y39QXJjMcKWCUy+*h;+bv zMLZ_<wAeJ`dWB(#<b(0h*3xpO_`cHfm^kXmQ1^)Br)*pBQ9EFgI@)ODv5o<ySK9e| zNxOrgGu5wB`v$XFWd>(qpi(k4lN!R0ZPJE*%q`m_pf-EEfa&YT%*PoRYpVKu4>U-F zsRw-Jc-`4zm3F=MsxKb%BHa|EAlWpZL_<=XQTq)<j+hXIArrD0KK68C$n=m6vpQe{ z$&v(>3x$;BJ2Ml9G5=VyifxRvQPH!^VR&{F?gmC9_Ga)$t5aI>{(r`ORRBWLU`Z}E z2|#Q;s*f)ZMMYkPwWN0@;4M9D?_z3|`EK-Rurg!&VO(&;7+~9y{*+|)bTJh(cq)D( z+=8x@6zF7xC+GxT&<Rn<XtUE2=!Atoh83ODaGIHGe;o*QnX135`~tGR?`$d78vLU{ z<KwFQWS+nIQSMPpLB~xnJEi_xBOVE<*6(X?hQ}whkG<EP{No{z!g5qclExH^I|ZrJ z@W(?s1s4bp#T^=SAVsj{#wGY7hN^W*%4BFoBHs8|hBa6a%wGsP7t8Jg)XP^vXhhWs z@B$xI3v}7ZPJ(F2S%9Jxa#!#&OG8g>x2t%Y(5)Lau1UieuRRf&Q_HZPO5t3uZ_=4o zOOA6{WJa(|<8F(><mpW6)J5Ea%-<t9j!iHDL}cmJOKqt%2n>1#QH3M^n~j84rw9^( zn>#aOGA=RNl!C3$Q`Lz;G9I<5&ZyC*F&+RO>n2?#l5DL5!L0t|=EV#(K@cmiO}G}L zPn9o7(Mg$!%=ptcC_J`Q&!@LF(Vjnpq|06jRA%zx>zC&KYW7~l=8~UvX8rb=01s3{ zx%IK`ai59nAU<YJZu=$=KGfH``CCO8s)K8McbwLYy``y81u3XFRoVW-l_$CpC#m+; z+d{NXrfK_89Rs4pREGoy_%FI)S0+dsP!^O)ABknwP_=%8kh1H)i@Z2n&-5sSsJi|@ zK~Iw#)y#=PR0zdr9yzwgRjK@hQ&kU=-hscnBFN%r&W15s<pv)PHNILl)a2ACLNO*+ z$(n7*Mi~~$e!0H4;-8Ie7W7p%_Hb6UEkj7Xer6R1vAp7-6UzazG7~sej0wO(4Knj$ z(&>6zgd*_}n$ojvZx?leW{45dHhz^3APzj6On`oZLpwq@i^Lk>&ib}zlNJjnVk?{a z*1v1l5DK34D@5Cxhft;6-w9N$WbVWer^me}$E1{zc!|j0;Hj#09&y<9<@BH#R%txq ze-cu=x<f7~_J`CN?$3U>9vl=AVMtNn*w7{S`e|<R#t6aE3O8t@@Z=ElsbEV<fm{(s zG9uXHhxsxIS^VUx4w|!g94hUw)WWlf1%U61nlL0#hD9d{d5<6v&?(kFBr&<Uo1Y&^ z-<7;8!%hKYr~AGvk=!HTe94Rc(0UlrAPKd8&8lDxWMHk8C+!9Pc!2{0%2ds&(wV)q z;aA`;$?^J!e3@|3=%bf)q=ez>CLAEnawK}?Y};@^{iux+KRgu;#&R1dhb;wf8N7XF zplt)ZO*2Wvt_6bwu|tahC&ChIn{<=E7px<}&QHAS!Gvdh<{gpFm_w$Y4#|MzaaI!P zdm!NtlAf6AvLuc4pa_FvkxIER&a?3W8c}M>NJ4K|AP`<)4n*y;lbe`P)^q^6+`I1R zV_xZ8+K8MlviMTZgD+0+X{BGDXd|_MZlw$1(Oo9&9d_f<05<XpQ76oBj3`xe4RlnY z&*ZFa2%HCsm>&@x90Vo8dG&=2mZUq9!TggEji{W^eN_@8&_Pt<>}t#cL1(+_U0@u> zyt?hP^oLtY<LG^y=(}56<|)*-@4*?6CY5o)sr_lB5ELypum20l(qbAsIF1(z478)< zNsn`a!fHYF;kkkN`4pL^>-LRnnOgNx$y$U0RKOJ1K*P9VJ7keK=KO_}o5G9R5-rL7 ztDFCHTmIC7Dk_S62_LxpB@c8I8RY!{WYMLWx5_PxF<c&hmQ%5kc6V=0w@X1`@7z5B zGjDgC_g#(qDRsevz(-YxoD&5Wqs6~7J-`mv(rL^^{keC8L$7St28>waC8Rqd2Vu%* zgQ}KfOzf^eVBP4L8KP877dCafn;q4Shp@_K7*NPEjB0s*^l7p(z)Xb`3F{cy!Vs5P zl7eZ}iLyuf34P;i>v9w#wG1^#RL@_&oa)th1w~bJQl0?wgI71fRQr4(LtcYJ*gW{9 zs~`2JOcXAq6=D7=o9H>R&^Pl>c@>5V9Y0`X>=^Z2vU*5}TbI9+OUcD-A!E0Yq@a;= z5|0Y`uzNx52Vf41Q*jYKWE#UEE_YpmhP`NLfYgz{*c<mHJk6)H#g5SyH8ejve>`wq zxo5{9kQa_Y1RShPs<xU$a8n%2V=ud&Y7WpFT(EtlbrMqkBZ_b~+EP+hc0{0BerIZ; zGxad^Z8c^{+d@-K9WhjREoQK-gsre?O%oDYEM`c$L?$?%O&EXKgttje**fj&>|qH9 z%mr}x<EF1qixmdi;$T7$BB-fCQw=F1<q<(wiGZUG0wPB<?%w1r;EM3VY%|?z1hVMl zLmE+MTs9gCcsk_eZ)s%-lBn)eRAM9fBdlQY{hX)DL@@4h%}}aRZU&1GL2Dx*EWZs0 zy)20RY`ik@=zNd_ir>z9D1}+>fLsLjRel~T0>}(<reTvfUOkbGk^s$4h)9f-zEX|g z|8ET-PwhdI85(O}di4Sdzk21x7cad0(s}*_e_|R<W0EF8UKEOh#W4fDZsubEqAw`+ zL=&zb&?U{tu)Yh0uov<t$#sdI&QJ+kQ<gs+G?n^vR$J(!^|At715(CbCqyOTi25#L ze|zlG-?r+}PgOCb6eU7#&l`2#4o~YwAVEmPA2e<2UKno3eqZ^sEK5|@Ea_e!Q7`v6 z9sDo>1o~szBKqhtG?5OBM>?{wEkE99HSJz12%YI@TSzoJIeTs!R^8nrbu+GKArvv` ziH+G|{xSoH>It&G9#+oB?nK<pO>;!0fIv3nWODE#bpkc{tw^B;xvNZs#yj)Yo-(*} z+CJ1NC^o@!(_O%nM-rgp0ZzuffpNc}aX2zl{h&o*F<w?RCG&C)_oZALlhny`D~ykz zckz*^+${(?FhW&ia1eAbX-D9sF1jRZgCt5!<l8Z1fcj|Q|KwW_K~LF?oD|Q|_9~w6 zOMy1UAq|9I@PoEjE6W4i)@H&mo>wb|H31gj^6JofmjNH~f2FarbGY;tOC4t1hCuBi zoDvAWH~ieWNG+`NStQ!pmKiAeWZed%;ZTVu{jF#oz(N78(4jflBh8|cyqusYDLW5> z-Z3VP3L!N;iU($!pJecwBX?W)9$``yrLWIWA_k4zcMlgpU*$r)39|1oWbKJC2fvY9 zzrZ(H-ne{?PfCcCNX3CpF1auLqx4p$fL`%TmXrC{>#7hkhO`bRo;DSPWeh(Fm9Azc zpkyTK(b_P8my<n5#@Zqrf(|s9WCEeNq<JO_wcK*2N=$>37qtIi(uq)`wXZVWv263~ zub*)ga7#0RB1na}{k35DI9WzUMbJD2;-5}FP)>oB*NEWR1SCQ!r}Uw3WplGtaD}E= zFYz<CTZ?fKKR~foEEjzrT4z-hdQ59ivO+4O;B2lf;S0mMkQWX~m5<l=j1a9cb3*t` zjZzZm{u%3CQCo>%3AheLVw#n6=A0d(DiD%jI-b(3v4jhuX?0H_(`fp8hRa>5vce}2 z<ak#D=UlUw2s=ZfL&X52Ics8JMoSdYX=5!!$(;-x7R{qo6;G{DMk1M_WJZJ*3ZP^z zqd7MgJGp`owSyfMbIRT$DCJZlC!Fa!Nd-UTIzFrM!b38SjBEJD5mT=je4Vk2FpF{f zk#h_f;j+S<7K7O;b;kl|<C$jMesaarv%fyaLwlQcS#$iv6Y;`Tv#QghQAP-GseZ08 zn~GQ`=Xhcmp|xblqoGnfXI%f>rz_$fXOL>oN!-B(VO6HWSwb$aM5IGW%lIi!;y^DW zDD3+zPWBEAC3A|D(*Yn~O&ihzf+t<OH3Gbtx9QRpme-cS2^{ERNJ3O{`+z(uN&jTE z^g5m?%WZK)PcS@99PMa@`$ZqakigtW#1OEcEhYiYns%eQtWlS9R2&+a!?vfZ@d!-i z5y^HU|MoadofRtN7(vK6tmzpf1C!jg7-G<!s+(YhTi*u7oyL8ThFddWQ)Fx8ngqn$ zHyI3bY9&gN*k#@H|432X@=_5v5ect?F>gWa;}7vnokFXHS{BMD;$RKv81t^Pla0OI z9RrpV05}WZ=x)q@3k_P=*~;h~d>9kvx(O{nupfLuIU`~y$Psq|OU|2Rs<=HP*<u+C z6FvskE*Ow(e$&7ZOBB~bCrZi7py3oA5<L^eYzT75#YD<xl~{m~97%;O`iJpT9*yAH zG~s#G68td|by?>B0#T;Ma@h_+JLW6RjAnJQ6M)e)R2F<CE;g+h$dAxDVo=rc2VSOf zHx2*`GxrE}B~syi*jpx&>A<K;aP?B-Qv{c{Rf=lVu;lNL2XgohSa)U6i*Hr5)|zbA zwOUz73uY%+KLUZeZ99m0;U_<SVc=DHd70P9BQiwqU%H(_5N@!*M^#HrGF#iUmJUu6 z|4Tj)-?X|me=$F$gg!FVa=JiXar^dV{sEe2L=#~EGLknaEXp_44mQ<}GSgV?9dmmO zg+!T{whm~ctjuEpN4ryjdJKg@E{jN7peNixXwDK;swD`vaHr+e8^_@4I>7<BUyU*r zQLhKW85FGV#pFLIp|)!EFFhZ{4&C04Hr#xLWsV03xyU<z3U69gFueh11yh-RH7VM_ zqb<q}YCk(T#U#3zwHfEG*<^`272eViWW0S~IXZ~jc*l{e1=Rx|$!d<+WK&KT9ppKd zCwW^TX>oUFH%Jk>b&Ia#(<0HX#T992uT}JjWvU4}(Kj#%W?(lwC;Vix86J1LKvztT zd~+pTm0n0DRZx-}XLxDqU7*OG^6vmoIr>%NH(Ps<&zJw86a|YJeS-$!;}Z$+ul63r z3O{s3H%ZT8H%tGPDCP(}+N0|O#!oO6nD$S@==h|>z+|;%$xKHAT~<Lf6PIsTy_wbp z@kCM%V1+RO?yfDZiM!~eOcYA%4jO27dkn8<<De69lxy;K12<8qjH%`6vn>Nq;o$%z zrGfws9f6w7mM#L`*09n5<~Q3K9CO#_P+TUFaT3{B61l^lf8dPlr0z0b^>`AP_GsQx z?#bn8v3olIqN$4%U0qI6G;^;hsywfbPwU#d1IZ~<1wAU)JrQ{7QXZZc(Cy1G&2$LT zB<pGfV5%$f)m@|hIYK<0rVv6OLh{?ISI^@vHAoALU?~sa3jdNobw7loK(Y=r?u+*k z7EnFYG%y3bUTJRI^i@qm-yq}M<D9iROE=9|r4Z6fi?q!UPuaCn8HYm_1V1$w<Z?F) zwt=1slr(Kfk8Sl7Fb2L>$k0tqJ_Gy4ORqe=x7zc>s~Wd#pwLEyiCP9QyE`%@40tRw zlyMM~6|L^@3{e6DRPKZyAUQfvbJT?>dOMJULuJIg(xCzdu>TT7GTbm-vYISxddWy> zoHy|4yA`s_kb1W4DKDgTF>InOf)}nyTY?l--s7Dt#<9@L7!bf8uFhZO!{Jx{`4>!N z_3~AHY4svFk)%i(gy8v$d{p!0^WdUTO2@PyxoWE*g}hqRKuQU(PJZ)BP75~fB>bGa zwkxVT9<BHk{kxa0UeWkyHw3+(Ytge5tCh@>b_{K)j`{k;Oh&frlW||1q5Le=Cn?ed z!IxFwDH(&TP86U|2Fon7*#3vJ;XMT-hLT-=@`*S28u}9i`~1PzWv|?L0s=nq&ZQd{ zpE$P%1!8HBS-B*IC!{;VWE^=K>548`opWQclA#FDq&VPCvs3S&_5hC8YF96q+KUBQ zCSxTupa=@Z!8a+(_K>`neL85(w^~MYkbC&zM`S#<KU!XN_^^FNtvD(q0<}EjB-f5_ z7RQuVfY@a7ARY;hx=hQ_xra(KSS(z6hBJ}z5Y7pKvR`sg*44`#vO6$o1@GDoaH7;a zGUcrvqU7NigOw7aswp`Em5ydzAuowz+J?RZU8ctw>?h5SRtz~;tn-j<V@8vn#cQFi zmRN$G$W5LLcNv|wj!<oRN#r{oZEuZ^{!!XZ>pd!cd%A0#qdj|^;@SJR@36x<Xvfg{ zst@h9m5C#&iLGjBkr@@P(3{d`l}}Nfj9IWS2yr!eFrn63@Ls!KuQPQf9>Jl_wXK!z zN`X_yYXyE<*!g6teu1MeB}J$i{()51*D=J=qqwez>DU%b!v%BAB?nu!x%#Hjcm)1h zR0mE=Qj-h;B)8i$m~1}JwA5;Iu9X#ooMm9~NGYbGU1k@T%4?{Qq`1@_n<cKXJbl6> zWpzab48c~UMTC2(izR>{`2#F?+<uN;&GMOrQP8><#><gX%jnrjqiq_v6z!H^=v)GO z$jm+r8}P{1U1-@P%<p)y(ZH}UN1V03bN{{fbsLu?Q5%?6qYOd*7KMP?tVS+<51<gK zBSfqv<9FVEqja1JBGZtjta?Y700|>p>=jWSBW>BinN~DkLWhWf&7t{cW^7i923X(% z3W*YkeKH}Zk`2qWgghemU9F3)AGX7*lrNwb)sc2wB$C;`)pu0v1IOPKY~fk}41cO7 zph?(S?Y;>l3Ej>F*c<@$mh=jFvav0E3Ri0Io}vn!A>~l$(K`2pOA%b66@uEm^39DP z`y$imfl#8lX3*<F1d4JyPLf6S$RiON^oYnomQQBHt{S>giwT330D;{Lk$rw;3TZKp zX%d7}TQOgPyG3_)QUJNV1r7?zQ8s-bD%^JiY=##{^j>I9q!#|B0FIx@z;I2ct;LtV z>+2K&NgPwtz{oh;aG~gv##LBO<9(W2YNG=ph4f=cWw}^ufad!GcLJ>)r%y(?b=Br7 zh>{LLc5&=B^Q3lje_7biZ)ls=0L#$`$-!gSQ=!$;0x-e6A3<pWzi=#O8GqDSrJug` z0{idwWt9{~HxJiM9t>d6=i9bvPwl-n3n#r>u;*_Y%1PzRGBO~Xg2FfBVE55!y_-~v ziHkVVF#7%Z-RXb`X;p=Y(LUajV-i^vLe9VP#v~zih|FY&^?;^&9R9MGgA&yg;gj_= z68GoO!HHBjC776X1dEblKLK^dg#};dW&Iro<do30TuE*Xg-}<uhiRIPPB~c9rEB=$ zZNgx2CcqGcwlS$qn@2(@kKsjz2CPf~1(tw9_CnZpdrQ<wmPA$Is7<47WH`v16i=}= z;j?GUAJ{%)Omn3nENnUGXvwqDaZF}xY&Rk}7@@!1d~GN4B>Lp$45ZQ_G~%Oju(=`@ zxHZZfg)+fuYYEXLLlF84P*Bbvg8kIrGtUO5fp|vuhK^W_WHaQ6)21M8RcwJmm@NcI zcU-E6-itwP9K<#E2cV@<HGr<lNwp09zpL*({cVYfWCi+CBOCZ5&dT)g8g5f#=3zcX zxD8h~&xv}np%$PaR>K9ZG0{-~=|e-6TeQlr_*u)C^ju~%%IgvZ-W&e`;Rt592MF>& zcYRONQu#FFa^l%h*;5sqS?upZDSxX<^49nu`ZWZwjBa|`dn>AZ4ADrphF${v<PZMt zoT%FQ7>7TLXAU5>0cqBfDT-Ue`mQ2mO%-nf>C++d0IAYb(NCEFb_ID=3Q~H{(|aP9 z{)|`*6y-5hB!pgUAo2f+Y%L;nB(qvWobgZ)GEgdWwRWt=hwF+dZP(dg1e$Fqyj&~v z528CHn18cWCtNsTA@p5+0@2QlTIRqsqjgA&#A(nZ<m7mR0Gh!W8s}7hkvD{46nFy3 zR;YoZf8^L74~cILtuu;(z`S!i=I}cx6K00j>ZK)u`zCqPjV3Y{IH{O<q*=M9T0o=3 zZ|vi4axlSAGQhpcGf>Qm<7nqdj^K$uG1%goKy;sBR-MKBMuL!tE`9zOC#81MMq;xl zJ<w0`@QeyU{^#lU-+3E90azzcA#u}!H0U_8q7W3h)dshwZ8)B9I;1`S$OQBGy|39@ z!E6=-@hzzd%c;Z&noiI}v+X?KSuvhW0@=n1vT`e8XXHjZY^`gq7IdSSu82iy0@6cW zR88gC(A#4GK{j~yKsl_F5MB#o1`vtp+>)EA77@$;eaT!sWCCR@Zl6lWDy@5vX6F7( zbFz>b6K`lZbw-U;SmcRns0&2;eA_cjcifCOg^#Tx?ZtrI4xqIJN?T48<C9;uTGP+% zcX#*i<Kn?x7czuC+46)ac~;77$NgEhq3Vb?F^;atA238ZE<MoM3QF>rfZueuo+4ZE z9i5WFMb#GATB*YQqyVqOl*2Vi8^H!jz+tla)w(=89<f3q@u^qqRzpX4QeOu*p(F}P z>uj18LEdbwWzzqP=n0gjjfv1iR!~>#GWp&$;$XJiPAaPySCsykn)kaG-?(x4SC{67 z7TYSqG6xDi*oZk;i>$9^^pFrlmRJ$I{1<~-gPDVTZOaQ<5*x+J8KOnaRDX%A48vFj zb1*Q`6`Bcq|6G121Pm(L1M%9<Bg;ke1EAemU3%rMtJj#EG%s+=`nLw&Ug<^#6<#0+ z&Ekc)$`7lU$`oUR5$;Jr1(f8?p(K#}oNi$NVVF$VaR%+0&s~O;PtkuFCuXHkBnL>w zcmqICJ$`0{S1*DN^=iii&e~htn+Z=i!P)hHE2!vjK?$#yXFyE~w{L_Smb*|f-x|I< zMueu`+TJO1aNiWz{!~0gEXYw9!Z}LEW05jFZnrX>D~^G<7kr1GzV?0odx8Ic^bdn3 zBUfWxu0L6#RwkE+nJUllw6x)s3sAIiPUxb+5KCIjKNQA~*8eS$F6ofy@Qho+oWrR{ zZu9~ON~C!84&hUEMtVx_b1e|TJR1KK;l=^WnSO~JA;l;y{J5pA+8lwDg3d=S3|CDv zs#!h~xm@w^*X1vs8|bSD2#My%2zc5-kw`>WTD^Pi{l8iL_0yN$e)HS_aUw(e(bab@ zz5ec{_k{k`^u1rc^*W72VlACE5byo6|BU_R>sJNvy^H_C=v_c5rLKcpU`5=aHLO%I zk<G`A%|%|fuRpXJ_vc_wfYQ#Cqjj~`JQc#-$W4GiqCF6RMBR|SG9GBPwF6*%-4Gfh ztFg;?5;8r_g)VUf17wi^*Vv~V;-Y^6M+5zl5u_1){@HJT*M&crkHuhc@-{u%8~24t z;6|5uAjadUL)BhGgOw}Q79gFDMqrxuo**M(6af^$mC@#NsA8X7T<`vDq-OPBS1k}| z8vlDSmD1n2c6ANN9^~X;8{xZugeV;uIH?1|g{Zco1+&GVK>M<YB1{mN8IVAT<7r9V zhB-`{PQOU<vtFylT8Dc$9;&3Yb@FX}Rc49Q-2RIpq)bdrwNRjFGXU2nLE&7ElFg=Q z9*N4*kmXHnE2iC5uEb(uh9Fsjj0sVJU<_s_IedWDtxtGTcUdsBf@Ew+bA2s57Pj$u zsT>m=nS^2qiGz$lt#@lS?1qO7E>lV9BzBMJAwvPd83aorqQ%O4Ua+VK&aP=!@=B|l zpebG8qbDNO{&@8Kp09v=O0Lvf<g`%rWl4AvI@)||#Wis&T1vU)=@IL#t-t?Iudcqe zdhsW(zWDN@xhAl4Bh=oDulytJnV%{g<joTnJGM^ga~cXmU|Om^(0!qmptMS!t#zk6 zO?*V95+T#DUs@DzH<i^ftN}_eZVSaafab9m=kLq}*&m&^{uHWvBMG3zFu{+#s*)0T z-r`4AWW%9IlQ6--;a)CjpSL7L>Dn<L@V&61Fy}~iMbU~#36|TlS>ZtdCKV=7@r<@a zIJq#uBnJC7y5N^YX@+2|!x+hN(P#>h>oY75?8dY}j4FgJJsC92A{;`&(0Z?RBGUpf z8G4b~>8iFAQ+^3Fw;3G`35t_(@klch-gCGur>;MnT-~e-BTLYsWb;jljXsX1Kc}UI zYOC`8#!ExR_=kzWW6(&n^ld~8&&+PFzwpeYu^6E^q(t^T02m2ROQ<AbUdIn|v_)AJ z^bofJN`*m8H1jmmiIPYQT~Jwe`FIs1i{X=8k;F4UW<G`ob%=vUkjlh8EU_Ibsw`Mm zmR3^#Bym)I(83+}d(n{KLMCJi3p&Q}p`uHduUx)yHh||ZtiE0c(<HM&Q2i4+6Gk$H zKYZP;?IOG2+6EM8TOZkj$dUv_F{q1Wdd}*pZL(umyF9+NM)N2m;hAr%&cFqd>w9OT z@6X?V?raU;dhWa58BE-^RZO}?7$&jXj^9$Z2Bgig&43p8``AKT9tRtDHZ-oh`NkO= zUisPUSFc|`bK^I!4Og!G?9FSd3opO+!i6(;yFx#trCqhSQ!@a5{o=1Kow4!tf4$BZ ze9zeU#>JlxS8rU4+au=ItUX7{@{n_;IJJ1GSZ`R{cARd0qKyp1<78I?ak|_h`~q@^ zTt(()xPIx<wRf++vvmgjyT{BT;~W|9j9#$5wb{3Q?<XjP{nA<jdpsqBuHr)m8CHb! z!LYcK)?Sgu_%b(@rL2YLEe>SdmvR%30qb}AgB*uHS!(MFtH)uc!$>kS6+u4Mz}68` Vgzj&`LH}xLZ2dy=?u{P|{|}7B5uN}5 literal 0 HcmV?d00001 diff --git a/bin/resources/de/cemu.mo b/bin/resources/de/cemu.mo index 8dc4e8cc9ec032d5dcd38af5359b3246398a7e0a..cd9edd3cb981068219006dd4b84921e7b6169b00 100644 GIT binary patch delta 25224 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 delta 21408 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 diff --git a/bin/resources/ru/cemu.mo b/bin/resources/ru/cemu.mo index 4ff04e2bf7ba65f396cd0c2f640e8bc7df3c35cf..eb8f372f691d87c2d6059a630432007c5ee44eaa 100644 GIT binary patch delta 19385 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 O^1lV&H7d##==wj(F5LbA delta 18537 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 E1BADx(*OVf diff --git a/bin/resources/sv/cemu.mo b/bin/resources/sv/cemu.mo index 3e850b365f93c9190904a00dc65da40718ab6c6e..c8fd68ee6a01d9e46e65852673262b289852d362 100644 GIT binary patch literal 67980 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 literal 15821 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 I$@Jv>FAgnol>h($ From e2d0871ca34ab9c20bd9939e661c299067b04124 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 7 Dec 2024 07:48:30 +0100 Subject: [PATCH 213/299] Camera: Set error code in CAMInit Fixes Hunter's Trophy 2 crashing on boot --- src/Cafe/OS/libs/camera/camera.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cafe/OS/libs/camera/camera.cpp b/src/Cafe/OS/libs/camera/camera.cpp index 4debb37f..03e01bfc 100644 --- a/src/Cafe/OS/libs/camera/camera.cpp +++ b/src/Cafe/OS/libs/camera/camera.cpp @@ -181,7 +181,7 @@ namespace camera sint32 CAMInit(uint32 cameraId, CAMInitInfo_t* camInitInfo, uint32be* error) { CameraInstance* camInstance = new CameraInstance(camInitInfo->width, camInitInfo->height, camInitInfo->handlerFuncPtr); - + *error = 0; // Hunter's Trophy 2 will fail to boot if we don't set this std::unique_lock<std::recursive_mutex> _lock(g_mutex_camera); if (g_cameraCounter == 0) { From 356cf0e5e00a17a632ee6c845072a877c96c8eaf Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 7 Dec 2024 09:23:11 +0100 Subject: [PATCH 214/299] Multiple smaller HLE improvements --- src/Cafe/OS/RPL/rpl_structs.h | 2 +- src/Cafe/OS/libs/coreinit/coreinit_GHS.cpp | 14 +++++++++++++- src/Cafe/OS/libs/coreinit/coreinit_GHS.h | 4 ++++ src/Cafe/OS/libs/coreinit/coreinit_Thread.h | 2 +- src/Cafe/OS/libs/gx2/GX2_Resource.cpp | 6 ++++++ src/Cafe/OS/libs/nsysnet/nsysnet.cpp | 11 +++-------- 6 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/Cafe/OS/RPL/rpl_structs.h b/src/Cafe/OS/RPL/rpl_structs.h index 998ec8d7..c66f6136 100644 --- a/src/Cafe/OS/RPL/rpl_structs.h +++ b/src/Cafe/OS/RPL/rpl_structs.h @@ -116,7 +116,7 @@ typedef struct /* +0x34 */ uint32be ukn34; /* +0x38 */ uint32be ukn38; /* +0x3C */ uint32be ukn3C; - /* +0x40 */ uint32be toolkitVersion; + /* +0x40 */ uint32be minimumToolkitVersion; /* +0x44 */ uint32be ukn44; /* +0x48 */ uint32be ukn48; /* +0x4C */ uint32be ukn4C; diff --git a/src/Cafe/OS/libs/coreinit/coreinit_GHS.cpp b/src/Cafe/OS/libs/coreinit/coreinit_GHS.cpp index e2864fb9..33c8eedc 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_GHS.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_GHS.cpp @@ -156,12 +156,22 @@ namespace coreinit return ¤tThread->crt.eh_mem_manage; } - void* __gh_errno_ptr() + sint32be* __gh_errno_ptr() { OSThread_t* currentThread = coreinit::OSGetCurrentThread(); return ¤tThread->context.ghs_errno; } + void __gh_set_errno(sint32 errNo) + { + *__gh_errno_ptr() = errNo; + } + + sint32 __gh_get_errno() + { + return *__gh_errno_ptr(); + } + void* __get_eh_store_globals() { OSThread_t* currentThread = coreinit::OSGetCurrentThread(); @@ -272,6 +282,8 @@ namespace coreinit cafeExportRegister("coreinit", __get_eh_globals, LogType::Placeholder); cafeExportRegister("coreinit", __get_eh_mem_manage, LogType::Placeholder); cafeExportRegister("coreinit", __gh_errno_ptr, LogType::Placeholder); + cafeExportRegister("coreinit", __gh_set_errno, LogType::Placeholder); + cafeExportRegister("coreinit", __gh_get_errno, LogType::Placeholder); cafeExportRegister("coreinit", __get_eh_store_globals, LogType::Placeholder); cafeExportRegister("coreinit", __get_eh_store_globals_tdeh, LogType::Placeholder); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_GHS.h b/src/Cafe/OS/libs/coreinit/coreinit_GHS.h index 0ac09e94..5f000732 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_GHS.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_GHS.h @@ -4,5 +4,9 @@ namespace coreinit { void PrepareGHSRuntime(); + sint32be* __gh_errno_ptr(); + void __gh_set_errno(sint32 errNo); + sint32 __gh_get_errno(); + void InitializeGHS(); }; \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.h b/src/Cafe/OS/libs/coreinit/coreinit_Thread.h index df787bf0..1a93022b 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.h @@ -38,7 +38,7 @@ struct OSContext_t /* +0x1E0 */ uint64be fp_ps1[32]; /* +0x2E0 */ uint64be coretime[3]; /* +0x2F8 */ uint64be starttime; - /* +0x300 */ uint32be ghs_errno; // returned by __gh_errno_ptr() (used by socketlasterr) + /* +0x300 */ sint32be ghs_errno; // returned by __gh_errno_ptr() (used by socketlasterr) /* +0x304 */ uint32be affinity; /* +0x308 */ uint32be upmc1; /* +0x30C */ uint32be upmc2; diff --git a/src/Cafe/OS/libs/gx2/GX2_Resource.cpp b/src/Cafe/OS/libs/gx2/GX2_Resource.cpp index 97f51a0d..a6029de9 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Resource.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_Resource.cpp @@ -87,6 +87,11 @@ namespace GX2 return true; } + void GX2RSetBufferName(GX2RBuffer* buffer, const char* name) + { + // no-op in production builds + } + void* GX2RLockBufferEx(GX2RBuffer* buffer, uint32 resFlags) { return buffer->GetPtr(); @@ -226,6 +231,7 @@ namespace GX2 cafeExportRegister("gx2", GX2RCreateBufferUserMemory, LogType::GX2); cafeExportRegister("gx2", GX2RDestroyBufferEx, LogType::GX2); cafeExportRegister("gx2", GX2RBufferExists, LogType::GX2); + cafeExportRegister("gx2", GX2RSetBufferName, LogType::GX2); cafeExportRegister("gx2", GX2RLockBufferEx, LogType::GX2); cafeExportRegister("gx2", GX2RUnlockBufferEx, LogType::GX2); cafeExportRegister("gx2", GX2RInvalidateBuffer, LogType::GX2); diff --git a/src/Cafe/OS/libs/nsysnet/nsysnet.cpp b/src/Cafe/OS/libs/nsysnet/nsysnet.cpp index 5a0ddc59..c83915db 100644 --- a/src/Cafe/OS/libs/nsysnet/nsysnet.cpp +++ b/src/Cafe/OS/libs/nsysnet/nsysnet.cpp @@ -3,6 +3,7 @@ #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" #include "Cafe/IOSU/legacy/iosu_crypto.h" #include "Cafe/OS/libs/coreinit/coreinit_Time.h" +#include "Cafe/OS/libs/coreinit/coreinit_GHS.h" #include "Common/socket.h" @@ -117,20 +118,14 @@ void nsysnetExport_socket_lib_finish(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, 0); // 0 -> Success } -static uint32be* __gh_errno_ptr() -{ - OSThread_t* osThread = coreinit::OSGetCurrentThread(); - return &osThread->context.ghs_errno; -} - void _setSockError(sint32 errCode) { - *(uint32be*)__gh_errno_ptr() = (uint32)errCode; + coreinit::__gh_set_errno(errCode); } sint32 _getSockError() { - return (sint32)*(uint32be*)__gh_errno_ptr(); + return coreinit::__gh_get_errno(); } // error translation modes for _translateError From 934cb5460577dd4f672ae3d9c918826e50073934 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 7 Dec 2024 09:24:59 +0100 Subject: [PATCH 215/299] Properly check if MLC is writeable --- src/gui/CemuApp.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/gui/CemuApp.cpp b/src/gui/CemuApp.cpp index 50ff3b89..c4b1f4e4 100644 --- a/src/gui/CemuApp.cpp +++ b/src/gui/CemuApp.cpp @@ -234,6 +234,12 @@ void CemuApp::InitializeExistingMLCOrFail(fs::path mlc) g_config.Save(); } } + else + { + // default path is not writeable. Just let the user know and quit. Unsure if it would be a good idea to ask the user to choose an alternative path instead + wxMessageBox(formatWxString(_("Cemu failed to write to the default mlc directory.\nThe path is:\n{}"), wxHelper::FromPath(mlc)), _("Error"), wxOK | wxCENTRE | wxICON_ERROR); + exit(0); + } } bool CemuApp::OnInit() @@ -507,6 +513,13 @@ bool CemuApp::CreateDefaultMLCFiles(const fs::path& mlc) file.flush(); file.close(); } + // create a dummy file in the mlc folder to check if it's writable + const auto dummyFile = fs::path(mlc).append("writetestdummy"); + std::ofstream file(dummyFile); + if (!file.is_open()) + return false; + file.close(); + fs::remove(dummyFile); } catch (const std::exception& ex) { From dd0af0a56fa3c6b8a82f60c19e67bbe06d673d0e Mon Sep 17 00:00:00 2001 From: capitalistspz <keipitalists@proton.me> Date: Sat, 7 Dec 2024 11:02:40 +0000 Subject: [PATCH 216/299] Linux: Allow connecting Wiimotes via L2CAP (#1353) --- .github/workflows/build.yml | 4 +- BUILD.md | 6 +- CMakeLists.txt | 7 + cmake/Findbluez.cmake | 20 + src/gui/input/PairingDialog.cpp | 394 ++++++++++-------- src/gui/input/PairingDialog.h | 2 +- src/input/CMakeLists.txt | 10 + .../api/Wiimote/WiimoteControllerProvider.cpp | 120 ++++-- .../api/Wiimote/WiimoteControllerProvider.h | 6 +- src/input/api/Wiimote/WiimoteDevice.h | 3 +- .../api/Wiimote/hidapi/HidapiWiimote.cpp | 7 +- src/input/api/Wiimote/hidapi/HidapiWiimote.h | 4 +- src/input/api/Wiimote/l2cap/L2CapWiimote.cpp | 148 +++++++ src/input/api/Wiimote/l2cap/L2CapWiimote.h | 22 + 14 files changed, 532 insertions(+), 221 deletions(-) create mode 100644 cmake/Findbluez.cmake create mode 100644 src/input/api/Wiimote/l2cap/L2CapWiimote.cpp create mode 100644 src/input/api/Wiimote/l2cap/L2CapWiimote.h diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 72bbcf52..6ae4b892 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,7 +39,7 @@ jobs: - name: "Install system dependencies" run: | sudo apt update -qq - sudo apt install -y clang-15 cmake freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libudev-dev nasm ninja-build + sudo apt install -y clang-15 cmake freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libudev-dev nasm ninja-build libbluetooth-dev - name: "Setup cmake" uses: jwlawson/actions-setup-cmake@v2 @@ -96,7 +96,7 @@ jobs: - name: "Install system dependencies" run: | sudo apt update -qq - sudo apt install -y clang-15 cmake freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev nasm ninja-build appstream + sudo apt install -y clang-15 cmake freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev nasm ninja-build appstream libbluetooth-dev - name: "Build AppImage" run: | diff --git a/BUILD.md b/BUILD.md index 44d69c6c..41de928e 100644 --- a/BUILD.md +++ b/BUILD.md @@ -46,10 +46,10 @@ To compile Cemu, a recent enough compiler and STL with C++20 support is required ### Dependencies #### For Arch and derivatives: -`sudo pacman -S --needed base-devel clang cmake freeglut git glm gtk3 libgcrypt libpulse libsecret linux-headers llvm nasm ninja systemd unzip zip` +`sudo pacman -S --needed base-devel bluez-libs clang cmake freeglut git glm gtk3 libgcrypt libpulse libsecret linux-headers llvm nasm ninja systemd unzip zip` #### For Debian, Ubuntu and derivatives: -`sudo apt install -y cmake curl clang-15 freeglut3-dev git libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libtool nasm ninja-build` +`sudo apt install -y cmake curl clang-15 freeglut3-dev git libbluetooth-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libtool nasm ninja-build` You may also need to install `libusb-1.0-0-dev` as a workaround for an issue with the vcpkg hidapi package. @@ -57,7 +57,7 @@ At Step 3 in [Build Cemu using cmake and clang](#build-cemu-using-cmake-and-clan `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang-15 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-15 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja` #### For Fedora and derivatives: -`sudo dnf install clang cmake cubeb-devel freeglut-devel git glm-devel gtk3-devel kernel-headers libgcrypt-devel libsecret-devel libtool libusb1-devel llvm nasm ninja-build perl-core systemd-devel zlib-devel zlib-static` +`sudo dnf install bluez-libs clang cmake cubeb-devel freeglut-devel git glm-devel gtk3-devel kernel-headers libgcrypt-devel libsecret-devel libtool libusb1-devel llvm nasm ninja-build perl-core systemd-devel zlib-devel zlib-static` ### Build Cemu diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b5cff6c..cf04b235 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,6 +98,7 @@ endif() if (UNIX AND NOT APPLE) option(ENABLE_WAYLAND "Build with Wayland support" ON) option(ENABLE_FERAL_GAMEMODE "Enables Feral Interactive GameMode Support" ON) + option(ENABLE_BLUEZ "Build with Bluez support" ON) endif() option(ENABLE_OPENGL "Enables the OpenGL backend" ON) @@ -179,6 +180,12 @@ if (UNIX AND NOT APPLE) endif() find_package(GTK3 REQUIRED) + if(ENABLE_BLUEZ) + find_package(bluez REQUIRED) + set(ENABLE_WIIMOTE ON) + add_compile_definitions(HAS_BLUEZ) + endif() + endif() if (ENABLE_VULKAN) diff --git a/cmake/Findbluez.cmake b/cmake/Findbluez.cmake new file mode 100644 index 00000000..007cdac9 --- /dev/null +++ b/cmake/Findbluez.cmake @@ -0,0 +1,20 @@ +# SPDX-FileCopyrightText: 2022 Andrea Pappacoda <andrea@pappacoda.it> +# SPDX-License-Identifier: ISC + +find_package(bluez CONFIG) +if (NOT bluez_FOUND) + find_package(PkgConfig) + if (PKG_CONFIG_FOUND) + pkg_search_module(bluez IMPORTED_TARGET GLOBAL bluez-1.0 bluez) + if (bluez_FOUND) + add_library(bluez::bluez ALIAS PkgConfig::bluez) + endif () + endif () +endif () + +find_package_handle_standard_args(bluez + REQUIRED_VARS + bluez_LINK_LIBRARIES + bluez_FOUND + VERSION_VAR bluez_VERSION +) diff --git a/src/gui/input/PairingDialog.cpp b/src/gui/input/PairingDialog.cpp index 03d6315b..350fce81 100644 --- a/src/gui/input/PairingDialog.cpp +++ b/src/gui/input/PairingDialog.cpp @@ -4,233 +4,297 @@ #if BOOST_OS_WINDOWS #include <bluetoothapis.h> #endif +#if BOOST_OS_LINUX +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> +#include <input/api/Wiimote/l2cap/L2CapWiimote.h> +#endif wxDECLARE_EVENT(wxEVT_PROGRESS_PAIR, wxCommandEvent); wxDEFINE_EVENT(wxEVT_PROGRESS_PAIR, wxCommandEvent); PairingDialog::PairingDialog(wxWindow* parent) - : wxDialog(parent, wxID_ANY, _("Pairing..."), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxMINIMIZE_BOX | wxSYSTEM_MENU | wxTAB_TRAVERSAL | wxCLOSE_BOX) + : wxDialog(parent, wxID_ANY, _("Pairing..."), 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(350, 20), wxGA_HORIZONTAL); - m_gauge->SetValue(0); - sizer->Add(m_gauge, 0, wxALL | wxEXPAND, 5); + auto* sizer = new wxBoxSizer(wxVERTICAL); + m_gauge = new wxGauge(this, wxID_ANY, 100, wxDefaultPosition, wxSize(350, 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); + auto* rows = new wxFlexGridSizer(0, 2, 0, 0); + rows->AddGrowableCol(1); - m_text = new wxStaticText(this, wxID_ANY, _("Searching for controllers...")); - rows->Add(m_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + m_text = new wxStaticText(this, wxID_ANY, _("Searching for controllers...")); + rows->Add(m_text, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); - { - auto* right_side = new wxBoxSizer(wxHORIZONTAL); + { + auto* right_side = new wxBoxSizer(wxHORIZONTAL); - m_cancelButton = new wxButton(this, wxID_ANY, _("Cancel")); - m_cancelButton->Bind(wxEVT_BUTTON, &PairingDialog::OnCancelButton, this); - right_side->Add(m_cancelButton, 0, wxALL, 5); + m_cancelButton = new wxButton(this, wxID_ANY, _("Cancel")); + m_cancelButton->Bind(wxEVT_BUTTON, &PairingDialog::OnCancelButton, this); + right_side->Add(m_cancelButton, 0, wxALL, 5); - rows->Add(right_side, 1, wxALIGN_RIGHT, 5); - } + rows->Add(right_side, 1, wxALIGN_RIGHT, 5); + } - sizer->Add(rows, 0, wxALL | wxEXPAND, 5); + sizer->Add(rows, 0, wxALL | wxEXPAND, 5); - SetSizerAndFit(sizer); - Centre(wxBOTH); + SetSizerAndFit(sizer); + Centre(wxBOTH); - Bind(wxEVT_CLOSE_WINDOW, &PairingDialog::OnClose, this); - Bind(wxEVT_PROGRESS_PAIR, &PairingDialog::OnGaugeUpdate, this); + Bind(wxEVT_CLOSE_WINDOW, &PairingDialog::OnClose, this); + Bind(wxEVT_PROGRESS_PAIR, &PairingDialog::OnGaugeUpdate, this); - m_thread = std::thread(&PairingDialog::WorkerThread, this); + m_thread = std::thread(&PairingDialog::WorkerThread, this); } PairingDialog::~PairingDialog() { - Unbind(wxEVT_CLOSE_WINDOW, &PairingDialog::OnClose, this); + Unbind(wxEVT_CLOSE_WINDOW, &PairingDialog::OnClose, this); } void PairingDialog::OnClose(wxCloseEvent& event) { - event.Skip(); + event.Skip(); - m_threadShouldQuit = true; - if (m_thread.joinable()) - m_thread.join(); + m_threadShouldQuit = true; + if (m_thread.joinable()) + m_thread.join(); } void PairingDialog::OnCancelButton(const wxCommandEvent& event) { - Close(); + Close(); } void PairingDialog::OnGaugeUpdate(wxCommandEvent& event) { - PairingState state = (PairingState)event.GetInt(); + PairingState state = (PairingState)event.GetInt(); - switch (state) - { - case PairingState::Pairing: - { - m_text->SetLabel(_("Found controller. Pairing...")); - m_gauge->SetValue(50); - break; - } + switch (state) + { + case PairingState::Pairing: + { + m_text->SetLabel(_("Found controller. Pairing...")); + m_gauge->SetValue(50); + break; + } - case PairingState::Finished: - { - m_text->SetLabel(_("Successfully paired the controller.")); - m_gauge->SetValue(100); - m_cancelButton->SetLabel(_("Close")); - break; - } + case PairingState::Finished: + { + m_text->SetLabel(_("Successfully paired the controller.")); + m_gauge->SetValue(100); + m_cancelButton->SetLabel(_("Close")); + break; + } - case PairingState::NoBluetoothAvailable: - { - m_text->SetLabel(_("Failed to find a suitable Bluetooth radio.")); - m_gauge->SetValue(0); - m_cancelButton->SetLabel(_("Close")); - break; - } + case PairingState::NoBluetoothAvailable: + { + m_text->SetLabel(_("Failed to find a suitable Bluetooth radio.")); + m_gauge->SetValue(0); + m_cancelButton->SetLabel(_("Close")); + break; + } - case PairingState::BluetoothFailed: - { - m_text->SetLabel(_("Failed to search for controllers.")); - m_gauge->SetValue(0); - m_cancelButton->SetLabel(_("Close")); - break; - } + case PairingState::SearchFailed: + { + m_text->SetLabel(_("Failed to find controllers.")); + m_gauge->SetValue(0); + m_cancelButton->SetLabel(_("Close")); + break; + } - case PairingState::PairingFailed: - { - m_text->SetLabel(_("Failed to pair with the found controller.")); - m_gauge->SetValue(0); - m_cancelButton->SetLabel(_("Close")); - break; - } + case PairingState::PairingFailed: + { + m_text->SetLabel(_("Failed to pair with the found controller.")); + m_gauge->SetValue(0); + m_cancelButton->SetLabel(_("Close")); + break; + } - case PairingState::BluetoothUnusable: - { - m_text->SetLabel(_("Please use your system's Bluetooth manager instead.")); - m_gauge->SetValue(0); - m_cancelButton->SetLabel(_("Close")); - break; - } + case PairingState::BluetoothUnusable: + { + m_text->SetLabel(_("Please use your system's Bluetooth manager instead.")); + m_gauge->SetValue(0); + m_cancelButton->SetLabel(_("Close")); + break; + } - - default: - { - break; - } - } + default: + { + break; + } + } } -void PairingDialog::WorkerThread() -{ - const std::wstring wiimoteName = L"Nintendo RVL-CNT-01"; - const std::wstring wiiUProControllerName = L"Nintendo RVL-CNT-01-UC"; - #if BOOST_OS_WINDOWS - const GUID bthHidGuid = {0x00001124,0x0000,0x1000,{0x80,0x00,0x00,0x80,0x5F,0x9B,0x34,0xFB}}; +void PairingDialog::WorkerThread() +{ + const std::wstring wiimoteName = L"Nintendo RVL-CNT-01"; + const std::wstring wiiUProControllerName = L"Nintendo RVL-CNT-01-UC"; - const BLUETOOTH_FIND_RADIO_PARAMS radioFindParams = - { - .dwSize = sizeof(BLUETOOTH_FIND_RADIO_PARAMS) - }; + const GUID bthHidGuid = {0x00001124, 0x0000, 0x1000, {0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB}}; - HANDLE radio = INVALID_HANDLE_VALUE; - HBLUETOOTH_RADIO_FIND radioFind = BluetoothFindFirstRadio(&radioFindParams, &radio); - if (radioFind == nullptr) - { - UpdateCallback(PairingState::NoBluetoothAvailable); - return; - } + const BLUETOOTH_FIND_RADIO_PARAMS radioFindParams = + { + .dwSize = sizeof(BLUETOOTH_FIND_RADIO_PARAMS)}; - BluetoothFindRadioClose(radioFind); + HANDLE radio = INVALID_HANDLE_VALUE; + HBLUETOOTH_RADIO_FIND radioFind = BluetoothFindFirstRadio(&radioFindParams, &radio); + if (radioFind == nullptr) + { + UpdateCallback(PairingState::NoBluetoothAvailable); + return; + } - BLUETOOTH_RADIO_INFO radioInfo = - { - .dwSize = sizeof(BLUETOOTH_RADIO_INFO) - }; + BluetoothFindRadioClose(radioFind); - DWORD result = BluetoothGetRadioInfo(radio, &radioInfo); - if (result != ERROR_SUCCESS) - { - UpdateCallback(PairingState::NoBluetoothAvailable); - return; - } + BLUETOOTH_RADIO_INFO radioInfo = + { + .dwSize = sizeof(BLUETOOTH_RADIO_INFO)}; - const BLUETOOTH_DEVICE_SEARCH_PARAMS searchParams = - { - .dwSize = sizeof(BLUETOOTH_DEVICE_SEARCH_PARAMS), + DWORD result = BluetoothGetRadioInfo(radio, &radioInfo); + if (result != ERROR_SUCCESS) + { + UpdateCallback(PairingState::NoBluetoothAvailable); + return; + } - .fReturnAuthenticated = FALSE, - .fReturnRemembered = FALSE, - .fReturnUnknown = TRUE, - .fReturnConnected = FALSE, + const BLUETOOTH_DEVICE_SEARCH_PARAMS searchParams = + { + .dwSize = sizeof(BLUETOOTH_DEVICE_SEARCH_PARAMS), - .fIssueInquiry = TRUE, - .cTimeoutMultiplier = 5, + .fReturnAuthenticated = FALSE, + .fReturnRemembered = FALSE, + .fReturnUnknown = TRUE, + .fReturnConnected = FALSE, - .hRadio = radio - }; + .fIssueInquiry = TRUE, + .cTimeoutMultiplier = 5, - BLUETOOTH_DEVICE_INFO info = - { - .dwSize = sizeof(BLUETOOTH_DEVICE_INFO) - }; + .hRadio = radio}; - while (!m_threadShouldQuit) - { - HBLUETOOTH_DEVICE_FIND deviceFind = BluetoothFindFirstDevice(&searchParams, &info); - if (deviceFind == nullptr) - { - UpdateCallback(PairingState::BluetoothFailed); - return; - } + BLUETOOTH_DEVICE_INFO info = + { + .dwSize = sizeof(BLUETOOTH_DEVICE_INFO)}; - while (!m_threadShouldQuit) - { - if (info.szName == wiimoteName || info.szName == wiiUProControllerName) - { - BluetoothFindDeviceClose(deviceFind); + while (!m_threadShouldQuit) + { + HBLUETOOTH_DEVICE_FIND deviceFind = BluetoothFindFirstDevice(&searchParams, &info); + if (deviceFind == nullptr) + { + UpdateCallback(PairingState::SearchFailed); + return; + } - UpdateCallback(PairingState::Pairing); + while (!m_threadShouldQuit) + { + if (info.szName == wiimoteName || info.szName == wiiUProControllerName) + { + BluetoothFindDeviceClose(deviceFind); - wchar_t passwd[6] = { radioInfo.address.rgBytes[0], radioInfo.address.rgBytes[1], radioInfo.address.rgBytes[2], radioInfo.address.rgBytes[3], radioInfo.address.rgBytes[4], radioInfo.address.rgBytes[5] }; - DWORD bthResult = BluetoothAuthenticateDevice(nullptr, radio, &info, passwd, 6); - if (bthResult != ERROR_SUCCESS) - { - UpdateCallback(PairingState::PairingFailed); - return; - } + UpdateCallback(PairingState::Pairing); - bthResult = BluetoothSetServiceState(radio, &info, &bthHidGuid, BLUETOOTH_SERVICE_ENABLE); - if (bthResult != ERROR_SUCCESS) - { - UpdateCallback(PairingState::PairingFailed); - return; - } + wchar_t passwd[6] = {radioInfo.address.rgBytes[0], radioInfo.address.rgBytes[1], radioInfo.address.rgBytes[2], radioInfo.address.rgBytes[3], radioInfo.address.rgBytes[4], radioInfo.address.rgBytes[5]}; + DWORD bthResult = BluetoothAuthenticateDevice(nullptr, radio, &info, passwd, 6); + if (bthResult != ERROR_SUCCESS) + { + UpdateCallback(PairingState::PairingFailed); + return; + } - UpdateCallback(PairingState::Finished); - return; - } + bthResult = BluetoothSetServiceState(radio, &info, &bthHidGuid, BLUETOOTH_SERVICE_ENABLE); + if (bthResult != ERROR_SUCCESS) + { + UpdateCallback(PairingState::PairingFailed); + return; + } - BOOL nextDevResult = BluetoothFindNextDevice(deviceFind, &info); - if (nextDevResult == FALSE) - { - break; - } - } + UpdateCallback(PairingState::Finished); + return; + } - BluetoothFindDeviceClose(deviceFind); - } -#else - UpdateCallback(PairingState::BluetoothUnusable); -#endif + BOOL nextDevResult = BluetoothFindNextDevice(deviceFind, &info); + if (nextDevResult == FALSE) + { + break; + } + } + + BluetoothFindDeviceClose(deviceFind); + } } +#elif BOOST_OS_LINUX +void PairingDialog::WorkerThread() +{ + constexpr static uint8_t LIAC_LAP[] = {0x00, 0x8b, 0x9e}; + constexpr static auto isWiimoteName = [](std::string_view name) { + return name == "Nintendo RVL-CNT-01" || name == "Nintendo RVL-CNT-01-TR"; + }; + + // Get default BT device + const auto hostId = hci_get_route(nullptr); + if (hostId < 0) + { + UpdateCallback(PairingState::NoBluetoothAvailable); + return; + } + + // Search for device + inquiry_info* infos = nullptr; + m_cancelButton->Disable(); + const auto respCount = hci_inquiry(hostId, 7, 4, LIAC_LAP, &infos, IREQ_CACHE_FLUSH); + m_cancelButton->Enable(); + if (respCount <= 0) + { + UpdateCallback(PairingState::SearchFailed); + return; + } + stdx::scope_exit infoFree([&]() { bt_free(infos);}); + + if (m_threadShouldQuit) + return; + + // Open dev to read name + const auto hostDev = hci_open_dev(hostId); + stdx::scope_exit devClose([&]() { hci_close_dev(hostDev);}); + + char nameBuffer[HCI_MAX_NAME_LENGTH] = {}; + + bool foundADevice = false; + // Get device name and compare. Would use product and vendor id from SDP, but many third-party Wiimotes don't store them + for (const auto& devInfo : std::span(infos, respCount)) + { + const auto& addr = devInfo.bdaddr; + const auto err = hci_read_remote_name(hostDev, &addr, HCI_MAX_NAME_LENGTH, nameBuffer, + 2000); + if (m_threadShouldQuit) + return; + if (err || !isWiimoteName(nameBuffer)) + continue; + + L2CapWiimote::AddCandidateAddress(addr); + foundADevice = true; + const auto& b = addr.b; + cemuLog_log(LogType::Force, "Pairing Dialog: Found '{}' with address '{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}'", + nameBuffer, b[5], b[4], b[3], b[2], b[1], b[0]); + } + if (foundADevice) + UpdateCallback(PairingState::Finished); + else + UpdateCallback(PairingState::SearchFailed); +} +#else +void PairingDialog::WorkerThread() +{ + UpdateCallback(PairingState::BluetoothUnusable); +} +#endif void PairingDialog::UpdateCallback(PairingState state) { - auto* event = new wxCommandEvent(wxEVT_PROGRESS_PAIR); - event->SetInt((int)state); - wxQueueEvent(this, event); + auto* event = new wxCommandEvent(wxEVT_PROGRESS_PAIR); + event->SetInt((int)state); + wxQueueEvent(this, event); } \ No newline at end of file diff --git a/src/gui/input/PairingDialog.h b/src/gui/input/PairingDialog.h index 6c7612d1..02cab4fc 100644 --- a/src/gui/input/PairingDialog.h +++ b/src/gui/input/PairingDialog.h @@ -17,7 +17,7 @@ private: Pairing, Finished, NoBluetoothAvailable, - BluetoothFailed, + SearchFailed, PairingFailed, BluetoothUnusable }; diff --git a/src/input/CMakeLists.txt b/src/input/CMakeLists.txt index 9f7873a1..004dc2ba 100644 --- a/src/input/CMakeLists.txt +++ b/src/input/CMakeLists.txt @@ -73,6 +73,11 @@ if (ENABLE_WIIMOTE) api/Wiimote/hidapi/HidapiWiimote.cpp api/Wiimote/hidapi/HidapiWiimote.h ) + if (UNIX AND NOT APPLE) + target_sources(CemuInput PRIVATE + api/Wiimote/l2cap/L2CapWiimote.cpp + api/Wiimote/l2cap/L2CapWiimote.h) + endif() endif () @@ -97,3 +102,8 @@ endif() if (ENABLE_WXWIDGETS) target_link_libraries(CemuInput PRIVATE wx::base wx::core) endif() + + +if (UNIX AND NOT APPLE) + target_link_libraries(CemuInput PRIVATE bluez::bluez) +endif () \ No newline at end of file diff --git a/src/input/api/Wiimote/WiimoteControllerProvider.cpp b/src/input/api/Wiimote/WiimoteControllerProvider.cpp index c80f3fbe..221d75a7 100644 --- a/src/input/api/Wiimote/WiimoteControllerProvider.cpp +++ b/src/input/api/Wiimote/WiimoteControllerProvider.cpp @@ -2,7 +2,12 @@ #include "input/api/Wiimote/NativeWiimoteController.h" #include "input/api/Wiimote/WiimoteMessages.h" +#ifdef HAS_HIDAPI #include "input/api/Wiimote/hidapi/HidapiWiimote.h" +#endif +#ifdef HAS_BLUEZ +#include "input/api/Wiimote/l2cap/L2CapWiimote.h" +#endif #include <numbers> #include <queue> @@ -12,6 +17,7 @@ WiimoteControllerProvider::WiimoteControllerProvider() { m_reader_thread = std::thread(&WiimoteControllerProvider::reader_thread, this); m_writer_thread = std::thread(&WiimoteControllerProvider::writer_thread, this); + m_connectionThread = std::thread(&WiimoteControllerProvider::connectionThread, this); } WiimoteControllerProvider::~WiimoteControllerProvider() @@ -21,48 +27,51 @@ WiimoteControllerProvider::~WiimoteControllerProvider() m_running = false; m_writer_thread.join(); m_reader_thread.join(); + m_connectionThread.join(); } } std::vector<std::shared_ptr<ControllerBase>> WiimoteControllerProvider::get_controllers() { + m_connectedDeviceMutex.lock(); + auto devices = m_connectedDevices; + m_connectedDeviceMutex.unlock(); + std::scoped_lock lock(m_device_mutex); - std::queue<uint32> disconnected_wiimote_indices; - for (auto i{0u}; i < m_wiimotes.size(); ++i){ - if (!(m_wiimotes[i].connected = m_wiimotes[i].device->write_data({kStatusRequest, 0x00}))){ - disconnected_wiimote_indices.push(i); - } - } - - const auto valid_new_device = [&](std::shared_ptr<WiimoteDevice> & device) { - const auto writeable = device->write_data({kStatusRequest, 0x00}); - const auto not_already_connected = - std::none_of(m_wiimotes.cbegin(), m_wiimotes.cend(), - [device](const auto& it) { - return (*it.device == *device) && it.connected; - }); - return writeable && not_already_connected; - }; - - for (auto& device : WiimoteDevice_t::get_devices()) + for (auto& device : devices) { - if (!valid_new_device(device)) + const auto writeable = device->write_data({kStatusRequest, 0x00}); + if (!writeable) continue; - // Replace disconnected wiimotes - if (!disconnected_wiimote_indices.empty()){ - const auto idx = disconnected_wiimote_indices.front(); - disconnected_wiimote_indices.pop(); - m_wiimotes.replace(idx, std::make_unique<Wiimote>(device)); - } - // Otherwise add them - else { - m_wiimotes.push_back(std::make_unique<Wiimote>(device)); - } + bool isDuplicate = false; + ssize_t lowestReplaceableIndex = -1; + for (ssize_t i = m_wiimotes.size() - 1; i >= 0; --i) + { + const auto& wiimoteDevice = m_wiimotes[i].device; + if (wiimoteDevice) + { + if (*wiimoteDevice == *device) + { + isDuplicate = true; + break; + } + continue; + } + + lowestReplaceableIndex = i; + } + if (isDuplicate) + continue; + if (lowestReplaceableIndex != -1) + m_wiimotes.replace(lowestReplaceableIndex, std::make_unique<Wiimote>(device)); + else + m_wiimotes.push_back(std::make_unique<Wiimote>(device)); } std::vector<std::shared_ptr<ControllerBase>> result; + result.reserve(m_wiimotes.size()); for (size_t i = 0; i < m_wiimotes.size(); ++i) { result.emplace_back(std::make_shared<NativeWiimoteController>(i)); @@ -74,7 +83,7 @@ std::vector<std::shared_ptr<ControllerBase>> WiimoteControllerProvider::get_cont bool WiimoteControllerProvider::is_connected(size_t index) { std::shared_lock lock(m_device_mutex); - return index < m_wiimotes.size() && m_wiimotes[index].connected; + return index < m_wiimotes.size() && m_wiimotes[index].device; } bool WiimoteControllerProvider::is_registered_device(size_t index) @@ -141,6 +150,30 @@ WiimoteControllerProvider::WiimoteState WiimoteControllerProvider::get_state(siz return {}; } +void WiimoteControllerProvider::connectionThread() +{ + SetThreadName("Wiimote-connect"); + while (m_running.load(std::memory_order_relaxed)) + { + std::vector<WiimoteDevicePtr> devices; +#ifdef HAS_HIDAPI + const auto& hidDevices = HidapiWiimote::get_devices(); + std::ranges::move(hidDevices, std::back_inserter(devices)); +#endif +#ifdef HAS_BLUEZ + const auto& l2capDevices = L2CapWiimote::get_devices(); + std::ranges::move(l2capDevices, std::back_inserter(devices)); +#endif + { + std::scoped_lock lock(m_connectedDeviceMutex); + m_connectedDevices.clear(); + std::ranges::move(devices, std::back_inserter(m_connectedDevices)); + } + std::this_thread::sleep_for(std::chrono::seconds(2)); + } +} + + void WiimoteControllerProvider::reader_thread() { SetThreadName("Wiimote-reader"); @@ -148,7 +181,7 @@ void WiimoteControllerProvider::reader_thread() while (m_running.load(std::memory_order_relaxed)) { const auto now = std::chrono::steady_clock::now(); - if (std::chrono::duration_cast<std::chrono::seconds>(now - lastCheck) > std::chrono::seconds(2)) + if (std::chrono::duration_cast<std::chrono::seconds>(now - lastCheck) > std::chrono::milliseconds(500)) { // check for new connected wiimotes get_controllers(); @@ -160,11 +193,16 @@ void WiimoteControllerProvider::reader_thread() for (size_t index = 0; index < m_wiimotes.size(); ++index) { auto& wiimote = m_wiimotes[index]; - if (!wiimote.connected) + if (!wiimote.device) continue; const auto read_data = wiimote.device->read_data(); - if (!read_data || read_data->empty()) + if (!read_data) + { + wiimote.device.reset(); + continue; + } + if (read_data->empty()) continue; receivedAnyPacket = true; @@ -921,18 +959,18 @@ void WiimoteControllerProvider::writer_thread() if (index != (size_t)-1 && !data.empty()) { - if (m_wiimotes[index].rumble) + auto& wiimote = m_wiimotes[index]; + if (!wiimote.device) + continue; + if (wiimote.rumble) data[1] |= 1; - - m_wiimotes[index].connected = m_wiimotes[index].device->write_data(data); - if (m_wiimotes[index].connected) + if (!wiimote.device->write_data(data)) { - m_wiimotes[index].data_ts = std::chrono::high_resolution_clock::now(); + wiimote.device.reset(); + wiimote.rumble = false; } else - { - m_wiimotes[index].rumble = false; - } + wiimote.data_ts = std::chrono::high_resolution_clock::now(); } device_lock.unlock(); diff --git a/src/input/api/Wiimote/WiimoteControllerProvider.h b/src/input/api/Wiimote/WiimoteControllerProvider.h index 7629b641..90f28d5c 100644 --- a/src/input/api/Wiimote/WiimoteControllerProvider.h +++ b/src/input/api/Wiimote/WiimoteControllerProvider.h @@ -77,16 +77,17 @@ public: private: std::atomic_bool m_running = false; std::thread m_reader_thread, m_writer_thread; - std::shared_mutex m_device_mutex; + std::thread m_connectionThread; + std::vector<WiimoteDevicePtr> m_connectedDevices; + std::mutex m_connectedDeviceMutex; struct Wiimote { Wiimote(WiimoteDevicePtr device) : device(std::move(device)) {} WiimoteDevicePtr device; - std::atomic_bool connected = true; std::atomic_bool rumble = false; std::shared_mutex mutex; @@ -103,6 +104,7 @@ private: void reader_thread(); void writer_thread(); + void connectionThread(); void calibrate(size_t index); IRMode set_ir_camera(size_t index, bool state); diff --git a/src/input/api/Wiimote/WiimoteDevice.h b/src/input/api/Wiimote/WiimoteDevice.h index 7938bbdf..8ea5b321 100644 --- a/src/input/api/Wiimote/WiimoteDevice.h +++ b/src/input/api/Wiimote/WiimoteDevice.h @@ -9,8 +9,7 @@ public: virtual bool write_data(const std::vector<uint8>& data) = 0; virtual std::optional<std::vector<uint8_t>> read_data() = 0; - virtual bool operator==(WiimoteDevice& o) const = 0; - bool operator!=(WiimoteDevice& o) const { return *this == o; } + virtual bool operator==(const WiimoteDevice& o) const = 0; }; using WiimoteDevicePtr = std::shared_ptr<WiimoteDevice>; diff --git a/src/input/api/Wiimote/hidapi/HidapiWiimote.cpp b/src/input/api/Wiimote/hidapi/HidapiWiimote.cpp index db185675..5780909f 100644 --- a/src/input/api/Wiimote/hidapi/HidapiWiimote.cpp +++ b/src/input/api/Wiimote/hidapi/HidapiWiimote.cpp @@ -47,8 +47,11 @@ std::vector<WiimoteDevicePtr> HidapiWiimote::get_devices() { return wiimote_devices; } -bool HidapiWiimote::operator==(WiimoteDevice& o) const { - return static_cast<HidapiWiimote const&>(o).m_path == m_path; +bool HidapiWiimote::operator==(const WiimoteDevice& rhs) const { + auto other = dynamic_cast<const HidapiWiimote*>(&rhs); + if (!other) + return false; + return m_path == other->m_path; } HidapiWiimote::~HidapiWiimote() { diff --git a/src/input/api/Wiimote/hidapi/HidapiWiimote.h b/src/input/api/Wiimote/hidapi/HidapiWiimote.h index 858cb1f3..952a36f0 100644 --- a/src/input/api/Wiimote/hidapi/HidapiWiimote.h +++ b/src/input/api/Wiimote/hidapi/HidapiWiimote.h @@ -10,7 +10,7 @@ public: bool write_data(const std::vector<uint8> &data) override; std::optional<std::vector<uint8>> read_data() override; - bool operator==(WiimoteDevice& o) const override; + bool operator==(const WiimoteDevice& o) const override; static std::vector<WiimoteDevicePtr> get_devices(); @@ -19,5 +19,3 @@ private: const std::string m_path; }; - -using WiimoteDevice_t = HidapiWiimote; \ No newline at end of file diff --git a/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp b/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp new file mode 100644 index 00000000..28a123f3 --- /dev/null +++ b/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp @@ -0,0 +1,148 @@ +#include "L2CapWiimote.h" +#include <bluetooth/l2cap.h> + +constexpr auto comparator = [](const bdaddr_t& a, const bdaddr_t& b) { + return bacmp(&a, &b); +}; + +static auto s_addresses = std::map<bdaddr_t, bool, decltype(comparator)>(comparator); +static std::mutex s_addressMutex; + +static bool AttemptConnect(int sockFd, const sockaddr_l2& addr) +{ + auto res = connect(sockFd, reinterpret_cast<const sockaddr*>(&addr), + sizeof(sockaddr_l2)); + if (res == 0) + return true; + return connect(sockFd, reinterpret_cast<const sockaddr*>(&addr), + sizeof(sockaddr_l2)) == 0; +} + +static bool AttemptSetNonBlock(int sockFd) +{ + return fcntl(sockFd, F_SETFL, fcntl(sockFd, F_GETFL) | O_NONBLOCK) == 0; +} + +L2CapWiimote::L2CapWiimote(int recvFd, int sendFd, bdaddr_t addr) + : m_recvFd(recvFd), m_sendFd(sendFd), m_addr(addr) +{ +} + +L2CapWiimote::~L2CapWiimote() +{ + close(m_recvFd); + close(m_sendFd); + const auto& b = m_addr.b; + cemuLog_logDebug(LogType::Force, "Wiimote at {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x} disconnected", b[5], b[4], b[3], b[2], b[1], b[0]); + + // Re-add to candidate vec + s_addressMutex.lock(); + s_addresses[m_addr] = false; + s_addressMutex.unlock(); +} + +void L2CapWiimote::AddCandidateAddress(bdaddr_t addr) +{ + std::scoped_lock lock(s_addressMutex); + s_addresses.try_emplace(addr, false); +} + +std::vector<WiimoteDevicePtr> L2CapWiimote::get_devices() +{ + s_addressMutex.lock(); + std::vector<bdaddr_t> unconnected; + for (const auto& [addr, connected] : s_addresses) + { + if (!connected) + unconnected.push_back(addr); + } + s_addressMutex.unlock(); + + std::vector<WiimoteDevicePtr> outDevices; + for (const auto& addr : unconnected) + { + // Socket for sending data to controller, PSM 0x11 + auto sendFd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + if (sendFd < 0) + { + cemuLog_logDebug(LogType::Force, "Failed to open send socket: {}", strerror(errno)); + continue; + } + + sockaddr_l2 sendAddr{}; + sendAddr.l2_family = AF_BLUETOOTH; + sendAddr.l2_psm = htobs(0x11); + sendAddr.l2_bdaddr = addr; + + if (!AttemptConnect(sendFd, sendAddr) || !AttemptSetNonBlock(sendFd)) + { + const auto& b = addr.b; + cemuLog_logDebug(LogType::Force, "Failed to connect send socket to '{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}': {}", + b[5], b[4], b[3], b[2], b[1], b[0], strerror(errno)); + close(sendFd); + continue; + } + + // Socket for receiving data from controller, PSM 0x13 + auto recvFd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + if (recvFd < 0) + { + cemuLog_logDebug(LogType::Force, "Failed to open recv socket: {}", strerror(errno)); + close(sendFd); + continue; + } + sockaddr_l2 recvAddr{}; + recvAddr.l2_family = AF_BLUETOOTH; + recvAddr.l2_psm = htobs(0x13); + recvAddr.l2_bdaddr = addr; + + if (!AttemptConnect(recvFd, recvAddr) || !AttemptSetNonBlock(recvFd)) + { + const auto& b = addr.b; + cemuLog_logDebug(LogType::Force, "Failed to connect recv socket to '{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}': {}", + b[5], b[4], b[3], b[2], b[1], b[0], strerror(errno)); + close(sendFd); + close(recvFd); + continue; + } + outDevices.emplace_back(std::make_shared<L2CapWiimote>(sendFd, recvFd, addr)); + + s_addressMutex.lock(); + s_addresses[addr] = true; + s_addressMutex.unlock(); + } + return outDevices; +} + +bool L2CapWiimote::write_data(const std::vector<uint8>& data) +{ + const auto size = data.size(); + cemu_assert_debug(size < 23); + uint8 buffer[23]; + // All outgoing messages must be prefixed with 0xA2 + buffer[0] = 0xA2; + std::memcpy(buffer + 1, data.data(), size); + const auto outSize = size + 1; + return send(m_sendFd, buffer, outSize, 0) == outSize; +} + +std::optional<std::vector<uint8>> L2CapWiimote::read_data() +{ + uint8 buffer[23]; + const auto nBytes = recv(m_sendFd, buffer, 23, 0); + + if (nBytes < 0 && errno == EWOULDBLOCK) + return std::vector<uint8>{}; + // All incoming messages must be prefixed with 0xA1 + if (nBytes < 2 || buffer[0] != 0xA1) + return std::nullopt; + return std::vector(buffer + 1, buffer + 1 + nBytes - 1); +} + +bool L2CapWiimote::operator==(const WiimoteDevice& rhs) const +{ + auto mote = dynamic_cast<const L2CapWiimote*>(&rhs); + if (!mote) + return false; + return bacmp(&m_addr, &mote->m_addr) == 0; +} \ No newline at end of file diff --git a/src/input/api/Wiimote/l2cap/L2CapWiimote.h b/src/input/api/Wiimote/l2cap/L2CapWiimote.h new file mode 100644 index 00000000..cc8d071b --- /dev/null +++ b/src/input/api/Wiimote/l2cap/L2CapWiimote.h @@ -0,0 +1,22 @@ +#pragma once +#include <input/api/Wiimote/WiimoteDevice.h> +#include <bluetooth/bluetooth.h> + +class L2CapWiimote : public WiimoteDevice +{ + public: + L2CapWiimote(int recvFd, int sendFd, bdaddr_t addr); + ~L2CapWiimote() override; + + bool write_data(const std::vector<uint8>& data) override; + std::optional<std::vector<uint8>> read_data() override; + bool operator==(const WiimoteDevice& o) const override; + + static void AddCandidateAddress(bdaddr_t addr); + static std::vector<WiimoteDevicePtr> get_devices(); + private: + int m_recvFd; + int m_sendFd; + bdaddr_t m_addr; +}; + From adab729f43ca27bc81280be764b32f11a350308e Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 8 Dec 2024 13:33:34 +0100 Subject: [PATCH 217/299] UI: Correctly handle unicode paths during save export --- src/gui/TitleManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/TitleManager.cpp b/src/gui/TitleManager.cpp index 00e7992f..4a4f7f56 100644 --- a/src/gui/TitleManager.cpp +++ b/src/gui/TitleManager.cpp @@ -632,7 +632,7 @@ void TitleManager::OnSaveExport(wxCommandEvent& event) 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, + wxFileDialog path_dialog(this, _("Select a target file to export the save entry"), wxHelper::FromPath(entry->path), wxEmptyString, fmt::format("{}|*.zip", _("Exported save entry (*.zip)")), wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (path_dialog.ShowModal() != wxID_OK || path_dialog.GetPath().IsEmpty()) return; From 6aaad1eb83819bcfb9109da2e5e09b1f8d776722 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 15 Dec 2024 21:47:05 +0100 Subject: [PATCH 218/299] Debugger: Added right click context menu to disasm view + small fixes --- src/Cafe/HW/Espresso/Debugger/Debugger.cpp | 28 +++++++ src/Cafe/HW/Espresso/Debugger/Debugger.h | 1 + src/gui/debugger/DebuggerWindow2.cpp | 11 ++- src/gui/debugger/DebuggerWindow2.h | 2 + src/gui/debugger/DisasmCtrl.cpp | 86 +++++++++++++++++----- src/gui/debugger/DisasmCtrl.h | 13 ++++ 6 files changed, 121 insertions(+), 20 deletions(-) diff --git a/src/Cafe/HW/Espresso/Debugger/Debugger.cpp b/src/Cafe/HW/Espresso/Debugger/Debugger.cpp index e7369af6..1fed07cd 100644 --- a/src/Cafe/HW/Espresso/Debugger/Debugger.cpp +++ b/src/Cafe/HW/Espresso/Debugger/Debugger.cpp @@ -447,6 +447,34 @@ bool debugger_hasPatch(uint32 address) return false; } +void debugger_removePatch(uint32 address) +{ + for (sint32 i = 0; i < debuggerState.patches.size(); i++) + { + auto& patch = debuggerState.patches[i]; + if (address < patch->address || address >= (patch->address + patch->length)) + continue; + MPTR startAddress = patch->address; + MPTR endAddress = patch->address + patch->length; + // remove any breakpoints overlapping with the patch + for (auto& bp : debuggerState.breakpoints) + { + if (bp->address + 4 > startAddress && bp->address < endAddress) + { + bp->enabled = false; + debugger_updateExecutionBreakpoint(bp->address); + } + } + // restore original data + memcpy(MEMPTR<void>(startAddress).GetPtr(), patch->origData.data(), patch->length); + PPCRecompiler_invalidateRange(startAddress, endAddress); + // remove patch + delete patch; + debuggerState.patches.erase(debuggerState.patches.begin() + i); + return; + } +} + void debugger_stepInto(PPCInterpreter_t* hCPU, bool updateDebuggerWindow = true) { bool isRecEnabled = ppcRecompilerEnabled; diff --git a/src/Cafe/HW/Espresso/Debugger/Debugger.h b/src/Cafe/HW/Espresso/Debugger/Debugger.h index 717df28a..249c47b8 100644 --- a/src/Cafe/HW/Espresso/Debugger/Debugger.h +++ b/src/Cafe/HW/Espresso/Debugger/Debugger.h @@ -114,6 +114,7 @@ void debugger_updateExecutionBreakpoint(uint32 address, bool forceRestore = fals void debugger_createPatch(uint32 address, std::span<uint8> patchData); bool debugger_hasPatch(uint32 address); +void debugger_removePatch(uint32 address); void debugger_forceBreak(); // force breakpoint at the next possible instruction bool debugger_isTrapped(); diff --git a/src/gui/debugger/DebuggerWindow2.cpp b/src/gui/debugger/DebuggerWindow2.cpp index 969e40bd..9f25cf96 100644 --- a/src/gui/debugger/DebuggerWindow2.cpp +++ b/src/gui/debugger/DebuggerWindow2.cpp @@ -64,6 +64,7 @@ wxBEGIN_EVENT_TABLE(DebuggerWindow2, wxFrame) 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) + EVT_COMMAND(wxID_ANY, wxEVT_DISASMCTRL_NOTIFY_GOTO_ADDRESS, DebuggerWindow2::OnDisasmCtrlGotoAddress) // file menu EVT_MENU(MENU_ID_FILE_EXIT, DebuggerWindow2::OnExit) // window @@ -383,6 +384,12 @@ void DebuggerWindow2::OnMoveIP(wxCommandEvent& event) m_disasm_ctrl->CenterOffset(ip); } +void DebuggerWindow2::OnDisasmCtrlGotoAddress(wxCommandEvent& event) +{ + uint32 address = static_cast<uint32>(event.GetExtraLong()); + UpdateModuleLabel(address); +} + void DebuggerWindow2::OnParentMove(const wxPoint& main_position, const wxSize& main_size) { m_main_position = main_position; @@ -416,7 +423,7 @@ void DebuggerWindow2::OnNotifyModuleLoaded(wxCommandEvent& event) void DebuggerWindow2::OnNotifyModuleUnloaded(wxCommandEvent& event) { - RPLModule* module = (RPLModule*)event.GetClientData(); + RPLModule* module = (RPLModule*)event.GetClientData(); // todo - the RPL module is already unloaded at this point. Find a better way to handle this SaveModuleStorage(module, true); m_module_window->OnGameLoaded(); m_symbol_window->OnGameLoaded(); @@ -659,7 +666,7 @@ void DebuggerWindow2::CreateMenuBar() void DebuggerWindow2::UpdateModuleLabel(uint32 address) { - if(address == 0) + if (address == 0) address = m_disasm_ctrl->GetViewBaseAddress(); RPLModule* module = RPLLoader_FindModuleByCodeAddr(address); diff --git a/src/gui/debugger/DebuggerWindow2.h b/src/gui/debugger/DebuggerWindow2.h index 0ca44c44..145b5e1d 100644 --- a/src/gui/debugger/DebuggerWindow2.h +++ b/src/gui/debugger/DebuggerWindow2.h @@ -86,6 +86,8 @@ private: void OnMoveIP(wxCommandEvent& event); void OnNotifyModuleLoaded(wxCommandEvent& event); void OnNotifyModuleUnloaded(wxCommandEvent& event); + // events from DisasmCtrl + void OnDisasmCtrlGotoAddress(wxCommandEvent& event); void CreateMenuBar(); void UpdateModuleLabel(uint32 address = 0); diff --git a/src/gui/debugger/DisasmCtrl.cpp b/src/gui/debugger/DisasmCtrl.cpp index c2cd5722..2f38d55e 100644 --- a/src/gui/debugger/DisasmCtrl.cpp +++ b/src/gui/debugger/DisasmCtrl.cpp @@ -15,6 +15,8 @@ #include "Cafe/HW/Espresso/Debugger/DebugSymbolStorage.h" #include <wx/mstream.h> // for wxMemoryInputStream +wxDEFINE_EVENT(wxEVT_DISASMCTRL_NOTIFY_GOTO_ADDRESS, wxCommandEvent); + #define MAX_SYMBOL_LEN (120) #define COLOR_DEBUG_ACTIVE_BP 0xFFFFA0FF @@ -74,6 +76,8 @@ DisasmCtrl::DisasmCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& po 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); + + Bind(wxEVT_MENU, &DisasmCtrl::OnContextMenuEntryClicked, this, IDContextMenu_ToggleBreakpoint, IDContextMenu_Last); } void DisasmCtrl::Init() @@ -662,29 +666,67 @@ void DisasmCtrl::CopyToClipboard(std::string text) { #endif } +static uint32 GetUnrelocatedAddress(MPTR address) +{ + RPLModule* rplModule = RPLLoader_FindModuleByCodeAddr(address); + if (!rplModule) + return 0; + if (address >= rplModule->regionMappingBase_text.GetMPTR() && address < (rplModule->regionMappingBase_text.GetMPTR() + rplModule->regionSize_text)) + return 0x02000000 + (address - rplModule->regionMappingBase_text.GetMPTR()); + return 0; +} + 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; + m_contextMenuAddress = virtualAddress; + // show dialog + wxMenu menu; + menu.Append(IDContextMenu_ToggleBreakpoint, _("Toggle breakpoint")); + if(debugger_hasPatch(virtualAddress)) + menu.Append(IDContextMenu_RestoreOriginalInstructions, _("Restore original instructions")); + menu.AppendSeparator(); + menu.Append(IDContextMenu_CopyAddress, _("Copy address")); + uint32 unrelocatedAddress = GetUnrelocatedAddress(virtualAddress); + if (unrelocatedAddress && unrelocatedAddress != virtualAddress) + menu.Append(IDContextMenu_CopyUnrelocatedAddress, _("Copy virtual address (for IDA/Ghidra)")); + PopupMenu(&menu); +} - // address - if (pos.x <= OFFSET_ADDRESS + OFFSET_ADDRESS_RELATIVE) +void DisasmCtrl::OnContextMenuEntryClicked(wxCommandEvent& event) +{ + switch(event.GetId()) { - 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; + case IDContextMenu_ToggleBreakpoint: + { + debugger_toggleExecuteBreakpoint(m_contextMenuAddress); + wxCommandEvent evt(wxEVT_BREAKPOINT_CHANGE); + wxPostEvent(this->m_parent, evt); + break; + } + case IDContextMenu_RestoreOriginalInstructions: + { + debugger_removePatch(m_contextMenuAddress); + wxCommandEvent evt(wxEVT_BREAKPOINT_CHANGE); // This also refreshes the disassembly view + wxPostEvent(this->m_parent, evt); + break; + } + case IDContextMenu_CopyAddress: + { + CopyToClipboard(fmt::format("{:#10x}", m_contextMenuAddress)); + break; + } + case IDContextMenu_CopyUnrelocatedAddress: + { + uint32 unrelocatedAddress = GetUnrelocatedAddress(m_contextMenuAddress); + CopyToClipboard(fmt::format("{:#10x}", unrelocatedAddress)); + break; + } + default: + UNREACHABLE; } } @@ -722,7 +764,6 @@ 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; @@ -751,8 +792,6 @@ void DisasmCtrl::CenterOffset(uint32 offset) m_active_line = line; RefreshLine(m_active_line); - - debug_printf("scroll to %x\n", debuggerState.debugSession.instructionPointer); } void DisasmCtrl::GoToAddressDialog() @@ -765,6 +804,10 @@ void DisasmCtrl::GoToAddressDialog() auto value = goto_dialog.GetValue().ToStdString(); std::transform(value.begin(), value.end(), value.begin(), tolower); + // trim any leading spaces + while(!value.empty() && value[0] == ' ') + value.erase(value.begin()); + debugger_addParserSymbols(parser); // try to parse expression as hex value first (it should interpret 1234 as 0x1234, not 1234) @@ -773,17 +816,24 @@ void DisasmCtrl::GoToAddressDialog() const auto result = (uint32)parser.Evaluate("0x"+value); m_lastGotoTarget = result; CenterOffset(result); + wxCommandEvent evt(wxEVT_DISASMCTRL_NOTIFY_GOTO_ADDRESS); + evt.SetExtraLong(static_cast<long>(result)); + wxPostEvent(GetParent(), evt); } else if (parser.IsConstantExpression(value)) { const auto result = (uint32)parser.Evaluate(value); m_lastGotoTarget = result; CenterOffset(result); + wxCommandEvent evt(wxEVT_DISASMCTRL_NOTIFY_GOTO_ADDRESS); + evt.SetExtraLong(static_cast<long>(result)); + wxPostEvent(GetParent(), evt); } else { try { + // if not a constant expression (i.e. relying on unknown variables), then evaluating will throw an exception with a detailed error message const auto _ = (uint32)parser.Evaluate(value); } catch (const std::exception& ex) diff --git a/src/gui/debugger/DisasmCtrl.h b/src/gui/debugger/DisasmCtrl.h index 993d5697..5a67e49a 100644 --- a/src/gui/debugger/DisasmCtrl.h +++ b/src/gui/debugger/DisasmCtrl.h @@ -1,9 +1,20 @@ #pragma once #include "gui/components/TextList.h" +wxDECLARE_EVENT(wxEVT_DISASMCTRL_NOTIFY_GOTO_ADDRESS, wxCommandEvent); // Notify parent that goto address operation completed. Event contains the address that was jumped to. + class DisasmCtrl : public TextList { + enum + { + IDContextMenu_ToggleBreakpoint = wxID_HIGHEST + 1, + IDContextMenu_RestoreOriginalInstructions, + IDContextMenu_CopyAddress, + IDContextMenu_CopyUnrelocatedAddress, + IDContextMenu_Last + }; public: + DisasmCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, const wxSize& size, long style); void Init(); @@ -26,6 +37,7 @@ protected: 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; + void OnContextMenuEntryClicked(wxCommandEvent& event); bool OnShowTooltip(const wxPoint& position, uint32 line) override; void ScrollWindow(int dx, int dy, const wxRect* prect) override; @@ -40,6 +52,7 @@ private: sint32 m_mouse_line, m_mouse_line_drawn; sint32 m_active_line; uint32 m_lastGotoTarget{}; + uint32 m_contextMenuAddress{}; // code region info uint32 currentCodeRegionStart; uint32 currentCodeRegionEnd; From b53b223ba9b45974cd80674d8de9c6e736e34ae9 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Mon, 16 Dec 2024 02:38:43 +0100 Subject: [PATCH 219/299] Vulkan: Use cache for sampler objects --- .../HW/Latte/Core/LattePerformanceMonitor.h | 1 + src/Cafe/HW/Latte/Renderer/Vulkan/VKRBase.h | 48 ++++++-- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 110 +++++++++++++++++- .../Renderer/Vulkan/VulkanRendererCore.cpp | 8 +- 4 files changed, 152 insertions(+), 15 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.h b/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.h index 713e094e..ac75bb1b 100644 --- a/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.h +++ b/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.h @@ -124,6 +124,7 @@ typedef struct LattePerfStatCounter numGraphicPipelines; LattePerfStatCounter numImages; LattePerfStatCounter numImageViews; + LattePerfStatCounter numSamplers; LattePerfStatCounter numRenderPass; LattePerfStatCounter numFramebuffer; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VKRBase.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRBase.h index f79bd2dc..9c7e03f3 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VKRBase.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRBase.h @@ -19,7 +19,7 @@ public: virtual ~VKRMoveableRefCounter() { - cemu_assert_debug(refCount == 0); + cemu_assert_debug(m_refCount == 0); // remove references #ifdef CEMU_DEBUG_ASSERT @@ -30,7 +30,11 @@ public: } #endif for (auto itr : refs) - itr->ref->refCount--; + { + itr->ref->m_refCount--; + if (itr->ref->m_refCount == 0) + itr->ref->RefCountReachedZero(); + } refs.clear(); delete selfRef; selfRef = nullptr; @@ -41,8 +45,8 @@ public: VKRMoveableRefCounter(VKRMoveableRefCounter&& rhs) noexcept { this->refs = std::move(rhs.refs); - this->refCount = rhs.refCount; - rhs.refCount = 0; + this->m_refCount = rhs.m_refCount; + rhs.m_refCount = 0; this->selfRef = rhs.selfRef; rhs.selfRef = nullptr; this->selfRef->ref = this; @@ -57,7 +61,7 @@ public: void addRef(VKRMoveableRefCounter* refTarget) { this->refs.emplace_back(refTarget->selfRef); - refTarget->refCount++; + refTarget->m_refCount++; #ifdef CEMU_DEBUG_ASSERT // add reverse ref @@ -68,16 +72,23 @@ public: // methods to directly increment/decrement ref counter (for situations where no external object is available) void incRef() { - this->refCount++; + m_refCount++; } void decRef() { - this->refCount--; + m_refCount--; + if (m_refCount == 0) + RefCountReachedZero(); } protected: - int refCount{}; + virtual void RefCountReachedZero() + { + // does nothing by default + } + + int m_refCount{}; private: VKRMoveableRefCounterRef* selfRef; std::vector<VKRMoveableRefCounterRef*> refs; @@ -88,7 +99,7 @@ private: void moveObj(VKRMoveableRefCounter&& rhs) { this->refs = std::move(rhs.refs); - this->refCount = rhs.refCount; + this->m_refCount = rhs.m_refCount; this->selfRef = rhs.selfRef; this->selfRef->ref = this; } @@ -131,6 +142,25 @@ public: VkSampler m_textureDefaultSampler[2] = { VK_NULL_HANDLE, VK_NULL_HANDLE }; // relict from LatteTextureViewVk, get rid of it eventually }; + +class VKRObjectSampler : public VKRDestructibleObject +{ + public: + VKRObjectSampler(VkSamplerCreateInfo* samplerInfo); + ~VKRObjectSampler() override; + + static VKRObjectSampler* GetOrCreateSampler(VkSamplerCreateInfo* samplerInfo); + static void DestroyCache(); + + void RefCountReachedZero() override; // sampler objects are destroyed when not referenced anymore + + VkSampler GetSampler() const { return m_sampler; } + private: + static std::unordered_map<uint64, VKRObjectSampler*> s_samplerCache; + VkSampler m_sampler{ VK_NULL_HANDLE }; + uint64 m_hash; +}; + class VKRObjectRenderPass : public VKRDestructibleObject { public: diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 37432eeb..eae6daf2 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -672,6 +672,8 @@ VulkanRenderer::~VulkanRenderer() if (m_commandPool != VK_NULL_HANDLE) vkDestroyCommandPool(m_logicalDevice, m_commandPool, nullptr); + VKRObjectSampler::DestroyCache(); + // destroy debug callback if (m_debugCallback) { @@ -3707,6 +3709,7 @@ void VulkanRenderer::AppendOverlayDebugInfo() ImGui::Text("DS StorageBuf %u", performanceMonitor.vk.numDescriptorStorageBuffers.get()); ImGui::Text("Images %u", performanceMonitor.vk.numImages.get()); ImGui::Text("ImageView %u", performanceMonitor.vk.numImageViews.get()); + ImGui::Text("ImageSampler %u", performanceMonitor.vk.numSamplers.get()); ImGui::Text("RenderPass %u", performanceMonitor.vk.numRenderPass.get()); ImGui::Text("Framebuffer %u", performanceMonitor.vk.numFramebuffer.get()); m_spinlockDestructionQueue.lock(); @@ -3752,7 +3755,7 @@ void VKRDestructibleObject::flagForCurrentCommandBuffer() bool VKRDestructibleObject::canDestroy() { - if (refCount > 0) + if (m_refCount > 0) return false; return VulkanRenderer::GetInstance()->HasCommandBufferFinished(m_lastCmdBufferId); } @@ -3793,6 +3796,111 @@ VKRObjectTextureView::~VKRObjectTextureView() performanceMonitor.vk.numImageViews.decrement(); } +static uint64 CalcHashSamplerCreateInfo(const VkSamplerCreateInfo& info) +{ + uint64 h = 0xcbf29ce484222325ULL; + auto fnvHashCombine = [](uint64_t &h, auto val) { + using T = decltype(val); + static_assert(sizeof(T) <= 8); + uint64_t val64 = 0; + std::memcpy(&val64, &val, sizeof(val)); + h ^= val64; + h *= 0x100000001b3ULL; + }; + cemu_assert_debug(info.sType == VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO); + fnvHashCombine(h, info.flags); + fnvHashCombine(h, info.magFilter); + fnvHashCombine(h, info.minFilter); + fnvHashCombine(h, info.mipmapMode); + fnvHashCombine(h, info.addressModeU); + fnvHashCombine(h, info.addressModeV); + fnvHashCombine(h, info.addressModeW); + fnvHashCombine(h, info.mipLodBias); + fnvHashCombine(h, info.anisotropyEnable); + if(info.anisotropyEnable == VK_TRUE) + fnvHashCombine(h, info.maxAnisotropy); + fnvHashCombine(h, info.compareEnable); + if(info.compareEnable == VK_TRUE) + fnvHashCombine(h, info.compareOp); + fnvHashCombine(h, info.minLod); + fnvHashCombine(h, info.maxLod); + fnvHashCombine(h, info.borderColor); + fnvHashCombine(h, info.unnormalizedCoordinates); + // handle custom border color + VkBaseOutStructure* ext = (VkBaseOutStructure*)info.pNext; + while(ext) + { + if(ext->sType == VK_STRUCTURE_TYPE_SAMPLER_CUSTOM_BORDER_COLOR_CREATE_INFO_EXT) + { + auto* extInfo = (VkSamplerCustomBorderColorCreateInfoEXT*)ext; + fnvHashCombine(h, extInfo->customBorderColor.uint32[0]); + fnvHashCombine(h, extInfo->customBorderColor.uint32[1]); + fnvHashCombine(h, extInfo->customBorderColor.uint32[2]); + fnvHashCombine(h, extInfo->customBorderColor.uint32[3]); + } + else + { + cemu_assert_unimplemented(); + } + ext = ext->pNext; + } + return h; +} + +std::unordered_map<uint64, VKRObjectSampler*> VKRObjectSampler::s_samplerCache; + +VKRObjectSampler::VKRObjectSampler(VkSamplerCreateInfo* samplerInfo) +{ + auto* vulkanRenderer = VulkanRenderer::GetInstance(); + if (vkCreateSampler(vulkanRenderer->GetLogicalDevice(), samplerInfo, nullptr, &m_sampler) != VK_SUCCESS) + vulkanRenderer->UnrecoverableError("Failed to create texture sampler"); + performanceMonitor.vk.numSamplers.increment(); + m_hash = CalcHashSamplerCreateInfo(*samplerInfo); +} + +VKRObjectSampler::~VKRObjectSampler() +{ + vkDestroySampler(VulkanRenderer::GetInstance()->GetLogicalDevice(), m_sampler, nullptr); + performanceMonitor.vk.numSamplers.decrement(); + // remove from cache + auto it = s_samplerCache.find(m_hash); + if(it != s_samplerCache.end()) + s_samplerCache.erase(it); +} + +void VKRObjectSampler::RefCountReachedZero() +{ + VulkanRenderer::GetInstance()->ReleaseDestructibleObject(this); +} + +VKRObjectSampler* VKRObjectSampler::GetOrCreateSampler(VkSamplerCreateInfo* samplerInfo) +{ + auto* vulkanRenderer = VulkanRenderer::GetInstance(); + uint64 hash = CalcHashSamplerCreateInfo(*samplerInfo); + auto it = s_samplerCache.find(hash); + if (it != s_samplerCache.end()) + { + auto* sampler = it->second; + return sampler; + } + auto* sampler = new VKRObjectSampler(samplerInfo); + s_samplerCache[hash] = sampler; + return sampler; +} + +void VKRObjectSampler::DestroyCache() +{ + // assuming all other objects which depend on vkSampler are destroyed, this cache should also have been emptied already + // but just to be sure lets still clear the cache + cemu_assert_debug(s_samplerCache.empty()); + for(auto& sampler : s_samplerCache) + { + cemu_assert_debug(sampler.second->m_refCount == 0); + delete sampler.second; + } + s_samplerCache.clear(); +} + VKRObjectRenderPass::VKRObjectRenderPass(AttachmentInfo_t& attachmentInfo, sint32 colorAttachmentCount) { // generate helper hash for pipeline state diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp index 3a684072..dd39bd88 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp @@ -727,7 +727,6 @@ VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo* VkSamplerCustomBorderColorCreateInfoEXT samplerCustomBorderColor{}; - VkSampler sampler; VkSamplerCreateInfo samplerInfo{}; samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; @@ -899,10 +898,9 @@ VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo* } } } - - if (vkCreateSampler(m_logicalDevice, &samplerInfo, nullptr, &sampler) != VK_SUCCESS) - UnrecoverableError("Failed to create texture sampler"); - info.sampler = sampler; + VKRObjectSampler* samplerObj = VKRObjectSampler::GetOrCreateSampler(&samplerInfo); + vkObjDS->addRef(samplerObj); + info.sampler = samplerObj->GetSampler(); textureArray.emplace_back(info); } From 3738ccd2e676aa2c2cf2cdd5f08d5ac8dd221f57 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Wed, 18 Dec 2024 15:55:23 +0100 Subject: [PATCH 220/299] Play bootSound.btsnd while shaders/pipelines are compiling (#1047) --- src/Cafe/HW/Latte/Core/LatteShaderCache.cpp | 122 +++++++++++++++++++- src/Cafe/OS/libs/snd_core/ax_out.cpp | 79 ++----------- src/audio/CubebAPI.cpp | 2 +- src/audio/DirectSoundAPI.cpp | 2 +- src/audio/IAudioAPI.cpp | 42 +++++++ src/audio/IAudioAPI.h | 9 +- src/audio/XAudio27API.cpp | 17 ++- src/audio/XAudio27API.h | 2 + src/audio/XAudio2API.cpp | 13 ++- src/audio/XAudio2API.h | 2 + src/config/CemuConfig.cpp | 2 + src/config/CemuConfig.h | 16 ++- src/gui/GeneralSettings2.cpp | 56 ++------- src/gui/GeneralSettings2.h | 1 + src/util/CMakeLists.txt | 2 + src/util/bootSound/BootSoundReader.cpp | 51 ++++++++ src/util/bootSound/BootSoundReader.h | 20 ++++ 17 files changed, 310 insertions(+), 128 deletions(-) create mode 100644 src/util/bootSound/BootSoundReader.cpp create mode 100644 src/util/bootSound/BootSoundReader.h diff --git a/src/Cafe/HW/Latte/Core/LatteShaderCache.cpp b/src/Cafe/HW/Latte/Core/LatteShaderCache.cpp index 9576eb2e..9b24de45 100644 --- a/src/Cafe/HW/Latte/Core/LatteShaderCache.cpp +++ b/src/Cafe/HW/Latte/Core/LatteShaderCache.cpp @@ -25,6 +25,9 @@ #include "util/helpers/Serializer.h" #include <wx/msgdlg.h> +#include <audio/IAudioAPI.h> +#include <util/bootSound/BootSoundReader.h> +#include <thread> #if BOOST_OS_WINDOWS #include <psapi.h> @@ -155,6 +158,118 @@ bool LoadTGAFile(const std::vector<uint8>& buffer, TGAFILE *tgaFile) return true; } +class BootSoundPlayer +{ + public: + BootSoundPlayer() = default; + ~BootSoundPlayer() + { + m_stopRequested = true; + } + + void StartSound() + { + if (!m_bootSndPlayThread.joinable()) + { + m_fadeOutRequested = false; + m_stopRequested = false; + m_bootSndPlayThread = std::thread{[this]() { + StreamBootSound(); + }}; + } + } + + void FadeOutSound() + { + m_fadeOutRequested = true; + } + + void ApplyFadeOutEffect(std::span<sint16> samples, uint64& fadeOutSample, uint64 fadeOutDuration) + { + for (size_t i = 0; i < samples.size(); i += 2) + { + const float decibel = (float)fadeOutSample / fadeOutDuration * -60.0f; + const float volumeFactor = pow(10, decibel / 20); + samples[i] *= volumeFactor; + samples[i + 1] *= volumeFactor; + fadeOutSample++; + } + } + + void StreamBootSound() + { + SetThreadName("bootsnd"); + constexpr sint32 sampleRate = 48'000; + constexpr sint32 bitsPerSample = 16; + constexpr sint32 samplesPerBlock = sampleRate / 10; // block is 1/10th of a second + constexpr sint32 nChannels = 2; + static_assert(bitsPerSample % 8 == 0, "bits per sample is not a multiple of 8"); + + AudioAPIPtr bootSndAudioDev; + + try + { + bootSndAudioDev = IAudioAPI::CreateDeviceFromConfig(true, sampleRate, nChannels, samplesPerBlock, bitsPerSample); + if(!bootSndAudioDev) + return; + } + catch (const std::runtime_error& ex) + { + cemuLog_log(LogType::Force, "Failed to initialise audio device for bootup sound"); + return; + } + bootSndAudioDev->SetAudioDelayOverride(4); + bootSndAudioDev->Play(); + + std::string sndPath = fmt::format("{}/meta/{}", CafeSystem::GetMlcStoragePath(CafeSystem::GetForegroundTitleId()), "bootSound.btsnd"); + sint32 fscStatus = FSC_STATUS_UNDEFINED; + + if(!fsc_doesFileExist(sndPath.c_str())) + return; + + FSCVirtualFile* bootSndFileHandle = fsc_open(sndPath.c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus); + if(!bootSndFileHandle) + { + cemuLog_log(LogType::Force, "failed to open bootSound.btsnd"); + return; + } + + constexpr sint32 audioBlockSize = samplesPerBlock * (bitsPerSample/8) * nChannels; + BootSoundReader bootSndFileReader(bootSndFileHandle, audioBlockSize); + + uint64 fadeOutSample = 0; // track how far into the fadeout + constexpr uint64 fadeOutDuration = sampleRate * 2; // fadeout should last 2 seconds + while(fadeOutSample < fadeOutDuration && !m_stopRequested) + { + while (bootSndAudioDev->NeedAdditionalBlocks()) + { + sint16* data = bootSndFileReader.getSamples(); + if(data == nullptr) + { + // break outer loop + m_stopRequested = true; + break; + } + if(m_fadeOutRequested) + ApplyFadeOutEffect({data, samplesPerBlock * nChannels}, fadeOutSample, fadeOutDuration); + + bootSndAudioDev->FeedBlock(data); + } + // sleep for the duration of a single block + std::this_thread::sleep_for(std::chrono::milliseconds(samplesPerBlock / (sampleRate/ 1'000))); + } + + if(bootSndFileHandle) + fsc_close(bootSndFileHandle); + } + + private: + std::thread m_bootSndPlayThread; + std::atomic_bool m_fadeOutRequested = false; + std::atomic_bool m_stopRequested = false; +}; +static BootSoundPlayer g_bootSndPlayer; + void LatteShaderCache_finish() { if (g_renderer->GetType() == RendererAPI::Vulkan) @@ -299,6 +414,9 @@ void LatteShaderCache_Load() loadBackgroundTexture(true, g_shaderCacheLoaderState.textureTVId); loadBackgroundTexture(false, g_shaderCacheLoaderState.textureDRCId); + if(GetConfig().play_boot_sound) + g_bootSndPlayer.StartSound(); + sint32 numLoadedShaders = 0; uint32 loadIndex = 0; @@ -365,6 +483,8 @@ void LatteShaderCache_Load() g_renderer->DeleteTexture(g_shaderCacheLoaderState.textureTVId); if (g_shaderCacheLoaderState.textureDRCId) g_renderer->DeleteTexture(g_shaderCacheLoaderState.textureDRCId); + + g_bootSndPlayer.FadeOutSound(); } void LatteShaderCache_ShowProgress(const std::function <bool(void)>& loadUpdateFunc, bool isPipelines) @@ -805,4 +925,4 @@ void LatteShaderCache_handleDeprecatedCacheFiles(fs::path pathGeneric, fs::path fs::remove(pathGenericPre1_25_0, ec); } } -} +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/snd_core/ax_out.cpp b/src/Cafe/OS/libs/snd_core/ax_out.cpp index 40b9c643..a88807f2 100644 --- a/src/Cafe/OS/libs/snd_core/ax_out.cpp +++ b/src/Cafe/OS/libs/snd_core/ax_out.cpp @@ -396,90 +396,35 @@ namespace snd_core void AXOut_init() { - auto& config = GetConfig(); - const auto audio_api = (IAudioAPI::AudioAPI)config.audio_api; numQueuedFramesSndGeneric = 0; std::unique_lock lock(g_audioMutex); if (!g_tvAudio) { - sint32 channels; - switch (config.tv_channels) + try { - case 0: - channels = 1; // will mix mono sound on both output channels - break; - case 2: - channels = 6; - break; - default: // stereo - channels = 2; - break; + g_tvAudio = IAudioAPI::CreateDeviceFromConfig(true, 48000, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16); } - - IAudioAPI::DeviceDescriptionPtr device_description; - if (IAudioAPI::IsAudioAPIAvailable(audio_api)) + catch (std::runtime_error& ex) { - auto devices = IAudioAPI::GetDevices(audio_api); - const auto it = std::find_if(devices.begin(), devices.end(), [&config](const auto& d) {return d->GetIdentifier() == config.tv_device; }); - if (it != devices.end()) - device_description = *it; - } - - if (device_description) - { - try - { - g_tvAudio = IAudioAPI::CreateDevice((IAudioAPI::AudioAPI)config.audio_api, device_description, 48000, channels, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16); - g_tvAudio->SetVolume(config.tv_volume); - } - catch (std::runtime_error& ex) - { - cemuLog_log(LogType::Force, "can't initialize tv audio: {}", ex.what()); - exit(0); - } + cemuLog_log(LogType::Force, "can't initialize tv audio: {}", ex.what()); + exit(0); } } if (!g_padAudio) { - sint32 channels; - switch (config.pad_channels) + try { - case 0: - channels = 1; // will mix mono sound on both output channels - break; - case 2: - channels = 6; - break; - default: // stereo - channels = 2; - break; + g_padAudio = IAudioAPI::CreateDeviceFromConfig(false, 48000, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16); + if(g_padAudio) + g_padVolume = g_padAudio->GetVolume(); } - - IAudioAPI::DeviceDescriptionPtr device_description; - if (IAudioAPI::IsAudioAPIAvailable(audio_api)) + catch (std::runtime_error& ex) { - auto devices = IAudioAPI::GetDevices(audio_api); - const auto it = std::find_if(devices.begin(), devices.end(), [&config](const auto& d) {return d->GetIdentifier() == config.pad_device; }); - if (it != devices.end()) - device_description = *it; - } - - if (device_description) - { - try - { - g_padAudio = IAudioAPI::CreateDevice((IAudioAPI::AudioAPI)config.audio_api, device_description, 48000, channels, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16); - g_padAudio->SetVolume(config.pad_volume); - g_padVolume = config.pad_volume; - } - catch (std::runtime_error& ex) - { - cemuLog_log(LogType::Force, "can't initialize pad audio: {}", ex.what()); - exit(0); - } + cemuLog_log(LogType::Force, "can't initialize pad audio: {}", ex.what()); + exit(0); } } } diff --git a/src/audio/CubebAPI.cpp b/src/audio/CubebAPI.cpp index 2b4aec41..f98fa601 100644 --- a/src/audio/CubebAPI.cpp +++ b/src/audio/CubebAPI.cpp @@ -114,7 +114,7 @@ CubebAPI::~CubebAPI() bool CubebAPI::NeedAdditionalBlocks() const { std::shared_lock lock(m_mutex); - return m_buffer.size() < s_audioDelay * m_bytesPerBlock; + return m_buffer.size() < GetAudioDelay() * m_bytesPerBlock; } bool CubebAPI::FeedBlock(sint16* data) diff --git a/src/audio/DirectSoundAPI.cpp b/src/audio/DirectSoundAPI.cpp index 8c2f7245..eabd3a7e 100644 --- a/src/audio/DirectSoundAPI.cpp +++ b/src/audio/DirectSoundAPI.cpp @@ -210,7 +210,7 @@ void DirectSoundAPI::SetVolume(sint32 volume) bool DirectSoundAPI::NeedAdditionalBlocks() const { std::shared_lock lock(m_mutex); - return m_buffer.size() < s_audioDelay; + return m_buffer.size() < GetAudioDelay(); } std::vector<DirectSoundAPI::DeviceDescriptionPtr> DirectSoundAPI::GetDevices() diff --git a/src/audio/IAudioAPI.cpp b/src/audio/IAudioAPI.cpp index d078900b..587526ab 100644 --- a/src/audio/IAudioAPI.cpp +++ b/src/audio/IAudioAPI.cpp @@ -97,7 +97,40 @@ bool IAudioAPI::IsAudioAPIAvailable(AudioAPI api) return false; } +AudioAPIPtr IAudioAPI::CreateDeviceFromConfig(bool TV, sint32 rate, sint32 samples_per_block, sint32 bits_per_sample) +{ + auto& config = GetConfig(); + sint32 channels = CemuConfig::AudioChannelsToNChannels(TV ? config.tv_channels : config.pad_channels); + return CreateDeviceFromConfig(TV, rate, channels, samples_per_block, bits_per_sample); +} +AudioAPIPtr IAudioAPI::CreateDeviceFromConfig(bool TV, sint32 rate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample) +{ + AudioAPIPtr audioAPIDev; + + auto& config = GetConfig(); + + const auto audio_api = (IAudioAPI::AudioAPI)config.audio_api; + auto& selectedDevice = TV ? config.tv_device : config.pad_device; + + if(selectedDevice.empty()) + return {}; + + IAudioAPI::DeviceDescriptionPtr device_description; + if (IAudioAPI::IsAudioAPIAvailable(audio_api)) + { + auto devices = IAudioAPI::GetDevices(audio_api); + const auto it = std::find_if(devices.begin(), devices.end(), [&selectedDevice](const auto& d) {return d->GetIdentifier() == selectedDevice; }); + if (it != devices.end()) + device_description = *it; + } + if (!device_description) + throw std::runtime_error("failed to find selected device while trying to create audio device"); + + audioAPIDev = CreateDevice(audio_api, device_description, rate, channels, samples_per_block, bits_per_sample); + audioAPIDev->SetVolume(TV ? config.tv_volume : config.pad_volume); + return audioAPIDev; +} AudioAPIPtr IAudioAPI::CreateDevice(AudioAPI api, const DeviceDescriptionPtr& device, sint32 samplerate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample) { @@ -167,3 +200,12 @@ std::vector<IAudioAPI::DeviceDescriptionPtr> IAudioAPI::GetDevices(AudioAPI api) } } +void IAudioAPI::SetAudioDelayOverride(uint32 delay) +{ + m_audioDelayOverride = delay; +} + +uint32 IAudioAPI::GetAudioDelay() const +{ + return m_audioDelayOverride > 0 ? m_audioDelayOverride : s_audioDelay; +} diff --git a/src/audio/IAudioAPI.h b/src/audio/IAudioAPI.h index 935e3024..8fb510db 100644 --- a/src/audio/IAudioAPI.h +++ b/src/audio/IAudioAPI.h @@ -55,11 +55,15 @@ public: virtual bool FeedBlock(sint16* data) = 0; virtual bool Play() = 0; virtual bool Stop() = 0; + void SetAudioDelayOverride(uint32 delay); + uint32 GetAudioDelay() const; static void PrintLogging(); static void InitializeStatic(); static bool IsAudioAPIAvailable(AudioAPI api); - + + static std::unique_ptr<IAudioAPI> CreateDeviceFromConfig(bool TV, sint32 rate, sint32 samples_per_block, sint32 bits_per_sample); + static std::unique_ptr<IAudioAPI> CreateDeviceFromConfig(bool TV, sint32 rate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample); static std::unique_ptr<IAudioAPI> CreateDevice(AudioAPI api, const DeviceDescriptionPtr& device, sint32 samplerate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample); static std::vector<DeviceDescriptionPtr> GetDevices(AudioAPI api); @@ -75,9 +79,10 @@ protected: bool m_playing = false; static std::array<bool, AudioAPIEnd> s_availableApis; - static uint32 s_audioDelay; + uint32 m_audioDelayOverride = 0; private: + static uint32 s_audioDelay; void InitWFX(sint32 samplerate, sint32 channels, sint32 bits_per_sample); }; diff --git a/src/audio/XAudio27API.cpp b/src/audio/XAudio27API.cpp index 9901b29b..cfb21c67 100644 --- a/src/audio/XAudio27API.cpp +++ b/src/audio/XAudio27API.cpp @@ -33,8 +33,8 @@ XAudio27API::XAudio27API(uint32 device_id, uint32 samplerate, uint32 channels, u m_wfx.Format.nChannels = channels; m_wfx.Format.nSamplesPerSec = samplerate; m_wfx.Format.wBitsPerSample = bits_per_sample; - m_wfx.Format.nBlockAlign = (m_wfx.Format.nChannels * m_wfx.Format.wBitsPerSample) / 8; // must equal (nChannels wBitsPerSample) / 8 - m_wfx.Format.nAvgBytesPerSec = m_wfx.Format.nSamplesPerSec * m_wfx.Format.nBlockAlign; // must equal nSamplesPerSec nBlockAlign. + m_wfx.Format.nBlockAlign = (m_wfx.Format.nChannels * m_wfx.Format.wBitsPerSample) / 8; // must equal (nChannels × wBitsPerSample) / 8 + m_wfx.Format.nAvgBytesPerSec = m_wfx.Format.nSamplesPerSec * m_wfx.Format.nBlockAlign; // must equal nSamplesPerSec × nBlockAlign. m_wfx.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); m_wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; @@ -199,9 +199,7 @@ bool XAudio27API::FeedBlock(sint16* data) // check if we queued too many blocks if(m_blocks_queued >= kBlockCount) { - XAUDIO2_VOICE_STATE state{}; - m_source_voice->GetState(&state); - m_blocks_queued = state.BuffersQueued; + m_blocks_queued = GetQueuedBuffers(); if (m_blocks_queued >= kBlockCount) { @@ -222,7 +220,14 @@ bool XAudio27API::FeedBlock(sint16* data) return true; } +uint32 XAudio27API::GetQueuedBuffers() const +{ + XAUDIO2_VOICE_STATE state{}; + m_source_voice->GetState(&state); + return state.BuffersQueued; +} + bool XAudio27API::NeedAdditionalBlocks() const { - return m_blocks_queued < s_audioDelay; + return GetQueuedBuffers() < GetAudioDelay(); } diff --git a/src/audio/XAudio27API.h b/src/audio/XAudio27API.h index badab8f6..4288dcc7 100644 --- a/src/audio/XAudio27API.h +++ b/src/audio/XAudio27API.h @@ -47,6 +47,8 @@ public: static std::vector<DeviceDescriptionPtr> GetDevices(); private: + uint32 GetQueuedBuffers() const; + struct XAudioDeleter { void operator()(IXAudio2* ptr) const; diff --git a/src/audio/XAudio2API.cpp b/src/audio/XAudio2API.cpp index 512bcccd..c92fd451 100644 --- a/src/audio/XAudio2API.cpp +++ b/src/audio/XAudio2API.cpp @@ -270,9 +270,7 @@ bool XAudio2API::FeedBlock(sint16* data) // check if we queued too many blocks if (m_blocks_queued >= kBlockCount) { - XAUDIO2_VOICE_STATE state{}; - m_source_voice->GetState(&state); - m_blocks_queued = state.BuffersQueued; + m_blocks_queued = GetQueuedBuffers(); if (m_blocks_queued >= kBlockCount) { @@ -293,7 +291,14 @@ bool XAudio2API::FeedBlock(sint16* data) return true; } +uint32 XAudio2API::GetQueuedBuffers() const +{ + XAUDIO2_VOICE_STATE state{}; + m_source_voice->GetState(&state); + return state.BuffersQueued; +} + bool XAudio2API::NeedAdditionalBlocks() const { - return m_blocks_queued < s_audioDelay; + return GetQueuedBuffers() < GetAudioDelay(); } diff --git a/src/audio/XAudio2API.h b/src/audio/XAudio2API.h index 1f7057f0..b5bb0296 100644 --- a/src/audio/XAudio2API.h +++ b/src/audio/XAudio2API.h @@ -46,6 +46,8 @@ public: static const std::vector<DeviceDescriptionPtr>& GetDevices() { return s_devices; } private: + uint32 GetQueuedBuffers() const; + static const std::vector<DeviceDescriptionPtr>& RefreshDevices(); struct XAudioDeleter diff --git a/src/config/CemuConfig.cpp b/src/config/CemuConfig.cpp index 26f420a5..6bb7ac34 100644 --- a/src/config/CemuConfig.cpp +++ b/src/config/CemuConfig.cpp @@ -46,6 +46,7 @@ void CemuConfig::Load(XMLConfigParser& parser) fullscreen = parser.get("fullscreen", fullscreen); proxy_server = parser.get("proxy_server", ""); disable_screensaver = parser.get("disable_screensaver", disable_screensaver); + play_boot_sound = parser.get("play_boot_sound", play_boot_sound); console_language = parser.get("console_language", console_language.GetInitValue()); window_position.x = parser.get("window_position").get("x", -1); @@ -370,6 +371,7 @@ void CemuConfig::Save(XMLConfigParser& parser) config.set<bool>("fullscreen", fullscreen); config.set("proxy_server", proxy_server.GetValue().c_str()); config.set<bool>("disable_screensaver", disable_screensaver); + config.set<bool>("play_boot_sound", play_boot_sound); // config.set("cpu_mode", cpu_mode.GetValue()); //config.set("console_region", console_region.GetValue()); diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index be131266..191614a2 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -380,6 +380,7 @@ struct CemuConfig #endif ConfigValue<bool> disable_screensaver{DISABLE_SCREENSAVER_DEFAULT}; #undef DISABLE_SCREENSAVER_DEFAULT + ConfigValue<bool> play_boot_sound{false}; std::vector<std::string> game_paths; std::mutex game_cache_entries_mutex; @@ -524,7 +525,20 @@ struct CemuConfig ConfigValue<bool> emulate_dimensions_toypad{false}; }emulated_usb_devices{}; - private: + static int AudioChannelsToNChannels(AudioChannels kStereo) + { + switch (kStereo) + { + case 0: + return 1; // will mix mono sound on both output channels + case 2: + return 6; + default: // stereo + return 2; + } + } + + private: GameEntry* GetGameEntryByTitleId(uint64 titleId); GameEntry* CreateGameEntry(uint64 titleId); }; diff --git a/src/gui/GeneralSettings2.cpp b/src/gui/GeneralSettings2.cpp index eaada7cb..9b763229 100644 --- a/src/gui/GeneralSettings2.cpp +++ b/src/gui/GeneralSettings2.cpp @@ -207,8 +207,10 @@ wxPanel* GeneralSettings2::AddGeneralPage(wxNotebook* notebook) #if BOOST_OS_MACOS m_disable_screensaver->Enable(false); #endif - - // InsertEmptyRow(); + m_play_boot_sound = new wxCheckBox(box, wxID_ANY, _("Enable intro sound")); + m_play_boot_sound->SetToolTip(_("Play bootSound file while compiling shaders/pipelines.")); + second_row->Add(m_play_boot_sound, 0, botflag, 5); + CountRowElement(); m_auto_update = new wxCheckBox(box, wxID_ANY, _("Automatically check for updates")); m_auto_update->SetToolTip(_("Automatically checks for new cemu versions on startup")); @@ -936,13 +938,15 @@ void GeneralSettings2::StoreConfig() #if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE) config.feral_gamemode = m_feral_gamemode->IsChecked(); #endif + config.play_boot_sound = m_play_boot_sound->IsChecked(); config.disable_screensaver = m_disable_screensaver->IsChecked(); // Toggle while a game is running if (CafeSystem::IsTitleRunning()) { ScreenSaver::SetInhibit(config.disable_screensaver); } - + + // -1 is default wx widget value -> set to dummy 0 so mainwindow and padwindow will update it config.window_position = m_save_window_position_size->IsChecked() ? Vector2i{ 0,0 } : Vector2i{-1,-1}; config.window_size = m_save_window_position_size->IsChecked() ? Vector2i{ 0,0 } : Vector2i{-1,-1}; @@ -1574,6 +1578,7 @@ void GeneralSettings2::ApplyConfig() m_save_screenshot->SetValue(config.save_screenshot); m_disable_screensaver->SetValue(config.disable_screensaver); + m_play_boot_sound->SetValue(config.play_boot_sound); #if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE) m_feral_gamemode->SetValue(config.feral_gamemode); #endif @@ -1776,20 +1781,7 @@ void GeneralSettings2::UpdateAudioDevice() if (m_game_launched && g_tvAudio) channels = g_tvAudio->GetChannels(); else - { - switch (config.tv_channels) - { - case 0: - channels = 1; - break; - case 2: - channels = 6; - break; - default: // stereo - channels = 2; - break; - } - } + channels = CemuConfig::AudioChannelsToNChannels(config.tv_channels); try { @@ -1824,20 +1816,7 @@ void GeneralSettings2::UpdateAudioDevice() if (m_game_launched && g_padAudio) channels = g_padAudio->GetChannels(); else - { - switch (config.pad_channels) - { - case 0: - channels = 1; - break; - case 2: - channels = 6; - break; - default: // stereo - channels = 2; - break; - } - } + channels = CemuConfig::AudioChannelsToNChannels(config.pad_channels); try { @@ -1873,20 +1852,7 @@ void GeneralSettings2::UpdateAudioDevice() if (m_game_launched && g_inputAudio) channels = g_inputAudio->GetChannels(); else - { - switch (config.input_channels) - { - case 0: - channels = 1; - break; - case 2: - channels = 6; - break; - default: // stereo - channels = 2; - break; - } - } + channels = CemuConfig::AudioChannelsToNChannels(config.input_channels); try { diff --git a/src/gui/GeneralSettings2.h b/src/gui/GeneralSettings2.h index b1ab01e8..7fbfecc1 100644 --- a/src/gui/GeneralSettings2.h +++ b/src/gui/GeneralSettings2.h @@ -43,6 +43,7 @@ private: wxCheckBox* m_discord_presence, *m_fullscreen_menubar; wxCheckBox* m_auto_update, *m_receive_untested_releases, *m_save_screenshot; wxCheckBox* m_disable_screensaver; + wxCheckBox* m_play_boot_sound; #if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE) wxCheckBox* m_feral_gamemode; #endif diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 5ac5ebfd..881b20b6 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -1,5 +1,7 @@ add_library(CemuUtil boost/bluetooth.h + bootSound/BootSoundReader.cpp + bootSound/BootSoundReader.h ChunkedHeap/ChunkedHeap.h containers/flat_hash_map.hpp containers/IntervalBucketContainer.h diff --git a/src/util/bootSound/BootSoundReader.cpp b/src/util/bootSound/BootSoundReader.cpp new file mode 100644 index 00000000..f6b00615 --- /dev/null +++ b/src/util/bootSound/BootSoundReader.cpp @@ -0,0 +1,51 @@ +#include "BootSoundReader.h" +#include "Cafe/CafeSystem.h" + +BootSoundReader::BootSoundReader(FSCVirtualFile* bootsndFile, sint32 blockSize) : bootsndFile(bootsndFile), blockSize(blockSize) +{ + // crash if this constructor is invoked with a blockSize that has a different number of samples per channel + cemu_assert(blockSize % (sizeof(sint16be) * 2) == 0); + + fsc_setFileSeek(bootsndFile, 0); + fsc_readFile(bootsndFile, &muteBits, 4); + fsc_readFile(bootsndFile, &loopPoint, 4); + + buffer.resize(blockSize / sizeof(sint16)); + bufferBE.resize(blockSize / sizeof(sint16be)); + + // workaround: SM3DW has incorrect loop point + const auto titleId = CafeSystem::GetForegroundTitleId(); + if(titleId == 0x0005000010145D00 || titleId == 0x0005000010145C00 || titleId == 0x0005000010106100) + loopPoint = 113074; +} + +sint16* BootSoundReader::getSamples() +{ + size_t totalRead = 0; + const size_t loopPointOffset = 8 + loopPoint * 4; + while (totalRead < blockSize) + { + auto read = fsc_readFile(bootsndFile, bufferBE.data(), blockSize - totalRead); + if (read == 0) + { + cemuLog_log(LogType::Force, "failed to read PCM samples from bootSound.btsnd"); + return nullptr; + } + if (read % (sizeof(sint16be) * 2) != 0) + { + cemuLog_log(LogType::Force, "failed to play bootSound.btsnd: reading PCM data stopped at an odd number of samples (is the file corrupt?)"); + return nullptr; + } + + std::copy_n(bufferBE.begin(), read / sizeof(sint16be), buffer.begin() + (totalRead / sizeof(sint16))); + totalRead += read; + if (totalRead < blockSize) + fsc_setFileSeek(bootsndFile, loopPointOffset); + } + + // handle case where the end of a block of samples lines up with the end of the file + if(fsc_getFileSeek(bootsndFile) == fsc_getFileSize(bootsndFile)) + fsc_setFileSeek(bootsndFile, loopPointOffset); + + return buffer.data(); +} diff --git a/src/util/bootSound/BootSoundReader.h b/src/util/bootSound/BootSoundReader.h new file mode 100644 index 00000000..1953b4be --- /dev/null +++ b/src/util/bootSound/BootSoundReader.h @@ -0,0 +1,20 @@ +#pragma once +#include "Cafe/Filesystem/fsc.h" + +class BootSoundReader +{ + public: + BootSoundReader() = delete; + BootSoundReader(FSCVirtualFile* bootsndFile, sint32 blockSize); + + sint16* getSamples(); + + private: + FSCVirtualFile* bootsndFile{}; + sint32 blockSize{}; + + uint32be muteBits{}; + uint32be loopPoint{}; + std::vector<sint16> buffer{}; + std::vector<sint16be> bufferBE{}; +}; From 2b0cbf7f6b6c34c748585d255ee7756ff592a502 Mon Sep 17 00:00:00 2001 From: Mike Lothian <mike@fireburn.co.uk> Date: Wed, 18 Dec 2024 21:15:42 +0000 Subject: [PATCH 221/299] Fix building against Boost 1.87.0 (#1455) --- src/input/api/DSU/DSUControllerProvider.cpp | 4 +--- src/input/api/DSU/DSUControllerProvider.h | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/input/api/DSU/DSUControllerProvider.cpp b/src/input/api/DSU/DSUControllerProvider.cpp index 37f92774..fa00277c 100644 --- a/src/input/api/DSU/DSUControllerProvider.cpp +++ b/src/input/api/DSU/DSUControllerProvider.cpp @@ -78,9 +78,7 @@ bool DSUControllerProvider::connect() using namespace boost::asio; ip::udp::resolver resolver(m_io_service); - const ip::udp::resolver::query query(ip::udp::v4(), get_settings().ip, fmt::format("{}", get_settings().port), - ip::udp::resolver::query::canonical_name); - m_receiver_endpoint = *resolver.resolve(query); + m_receiver_endpoint = *resolver.resolve(get_settings().ip, fmt::format("{}", get_settings().port)).cbegin(); if (m_socket.is_open()) m_socket.close(); diff --git a/src/input/api/DSU/DSUControllerProvider.h b/src/input/api/DSU/DSUControllerProvider.h index dfa4d7b8..692da619 100644 --- a/src/input/api/DSU/DSUControllerProvider.h +++ b/src/input/api/DSU/DSUControllerProvider.h @@ -102,7 +102,7 @@ private: std::condition_variable m_writer_cond; uint32 m_uid; - boost::asio::io_service m_io_service; + boost::asio::io_context m_io_service; boost::asio::ip::udp::endpoint m_receiver_endpoint; boost::asio::ip::udp::socket m_socket; From 1e30d72658151a5304f5625a8acdf6aae31b6c58 Mon Sep 17 00:00:00 2001 From: capitalistspz <keipitalists@proton.me> Date: Mon, 30 Dec 2024 17:49:51 +0000 Subject: [PATCH 222/299] build: Add ALLOW_PORTABLE flag (#1464) * Add ALLOW_PORTABLE cmake flag * Also check that `portable` is a directory --- CMakeLists.txt | 1 + src/gui/CMakeLists.txt | 4 ++++ src/gui/CemuApp.cpp | 20 +++++++++++++------- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cf04b235..2352389e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.21.1) option(ENABLE_VCPKG "Enable the vcpkg package manager" ON) option(MACOS_BUNDLE "The executable when built on macOS will be created as an application bundle" OFF) +option(ALLOW_PORTABLE "Allow Cemu to be run in portable mode" ON) # used by CI script to set version: set(EMULATOR_VERSION_MAJOR "0" CACHE STRING "") diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index e1a04ec0..7cdc208e 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -178,3 +178,7 @@ endif() if(WIN32) target_link_libraries(CemuGui PRIVATE bthprops) endif() + +if(ALLOW_PORTABLE) + target_compile_definitions(CemuGui PRIVATE CEMU_ALLOW_PORTABLE) +endif () \ No newline at end of file diff --git a/src/gui/CemuApp.cpp b/src/gui/CemuApp.cpp index c4b1f4e4..c3606292 100644 --- a/src/gui/CemuApp.cpp +++ b/src/gui/CemuApp.cpp @@ -88,12 +88,14 @@ void CemuApp::DeterminePaths(std::set<fs::path>& failedWriteAccess) // for Windo fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath())); fs::path portablePath = exePath.parent_path() / "portable"; data_path = exePath.parent_path(); // the data path is always the same as the exe path - if (fs::exists(portablePath, ec)) +#ifdef CEMU_ALLOW_PORTABLE + if (fs::is_directory(portablePath, ec)) { isPortable = true; user_data_path = config_path = cache_path = portablePath; } else +#endif { fs::path roamingPath = GetAppDataRoamingPath() / "Cemu"; user_data_path = config_path = cache_path = roamingPath; @@ -124,12 +126,13 @@ void CemuApp::DeterminePaths(std::set<fs::path>& failedWriteAccess) // for Linux fs::path portablePath = exePath.parent_path() / "portable"; // GetExecutablePath returns the AppImage's temporary mount location wxString appImagePath; - if (wxGetEnv(("APPIMAGE"), &appImagePath)) + if (wxGetEnv("APPIMAGE", &appImagePath)) { exePath = wxHelper::MakeFSPath(appImagePath); portablePath = exePath.parent_path() / "portable"; } - if (fs::exists(portablePath, ec)) +#ifdef CEMU_ALLOW_PORTABLE + if (fs::is_directory(portablePath, ec)) { isPortable = true; user_data_path = config_path = cache_path = portablePath; @@ -137,6 +140,7 @@ void CemuApp::DeterminePaths(std::set<fs::path>& failedWriteAccess) // for Linux data_path = exePath.parent_path(); } else +#endif { SetAppName("Cemu"); wxString appName = GetAppName(); @@ -167,16 +171,18 @@ void CemuApp::DeterminePaths(std::set<fs::path>& failedWriteAccess) // for MacOS fs::path user_data_path, config_path, cache_path, data_path; auto standardPaths = wxStandardPaths::Get(); fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath())); - // If run from an app bundle, use its parent directory - fs::path appPath = exePath.parent_path().parent_path().parent_path(); - fs::path portablePath = appPath.extension() == ".app" ? appPath.parent_path() / "portable" : exePath.parent_path() / "portable"; - if (fs::exists(portablePath, ec)) + // If run from an app bundle, use its parent directory + fs::path appPath = exePath.parent_path().parent_path().parent_path(); + fs::path portablePath = appPath.extension() == ".app" ? appPath.parent_path() / "portable" : exePath.parent_path() / "portable"; +#ifdef CEMU_ALLOW_PORTABLE + if (fs::is_directory(portablePath, ec)) { isPortable = true; user_data_path = config_path = cache_path = portablePath; data_path = exePath.parent_path(); } else +#endif { SetAppName("Cemu"); wxString appName = GetAppName(); From 4b792aa4d2599c2e04865309e34cd3af9de728e4 Mon Sep 17 00:00:00 2001 From: Crementif <26669564+Crementif@users.noreply.github.com> Date: Sat, 4 Jan 2025 20:38:42 +0100 Subject: [PATCH 223/299] debug: Fix shader dumping (#1466) --- src/Cafe/HW/Latte/Core/LatteShader.cpp | 10 ++++------ src/gui/MainWindow.cpp | 4 +--- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/LatteShader.cpp b/src/Cafe/HW/Latte/Core/LatteShader.cpp index 77f16468..d9f0a5dd 100644 --- a/src/Cafe/HW/Latte/Core/LatteShader.cpp +++ b/src/Cafe/HW/Latte/Core/LatteShader.cpp @@ -451,9 +451,8 @@ void LatteShader_DumpShader(uint64 baseHash, uint64 auxHash, LatteDecompilerShad suffix = "gs"; else if (shader->shaderType == LatteConst::ShaderType::Pixel) suffix = "ps"; - fs::path dumpPath = "dump/shaders"; - dumpPath /= fmt::format("{:016x}_{:016x}_{}.txt", baseHash, auxHash, suffix); - FileStream* fs = FileStream::createFile2(dumpPath); + + FileStream* fs = FileStream::createFile2(ActiveSettings::GetUserDataPath("dump/shaders/{:016x}_{:016x}_{}.txt", baseHash, auxHash, suffix)); if (fs) { if (shader->strBuf_shaderSource) @@ -479,9 +478,8 @@ void LatteShader_DumpRawShader(uint64 baseHash, uint64 auxHash, uint32 type, uin suffix = "copy"; else if (type == SHADER_DUMP_TYPE_COMPUTE) suffix = "compute"; - fs::path dumpPath = "dump/shaders"; - dumpPath /= fmt::format("{:016x}_{:016x}_{}.bin", baseHash, auxHash, suffix); - FileStream* fs = FileStream::createFile2(dumpPath); + + FileStream* fs = FileStream::createFile2(ActiveSettings::GetUserDataPath("dump/shaders/{:016x}_{:016x}_{}.bin", baseHash, auxHash, suffix)); if (fs) { fs->writeData(programCode, programLen); diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 69ff4e99..4801706a 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -1113,9 +1113,7 @@ void MainWindow::OnDebugDumpUsedShaders(wxCommandEvent& event) { try { - // create directory - const fs::path path(ActiveSettings::GetUserDataPath()); - fs::create_directories(path / "dump" / "shaders"); + fs::create_directories(ActiveSettings::GetUserDataPath("dump/shaders")); } catch (const std::exception & ex) { From 92021db2307161a2df0cc2cfa4d3498444ab6888 Mon Sep 17 00:00:00 2001 From: Crementif <26669564+Crementif@users.noreply.github.com> Date: Sun, 5 Jan 2025 04:08:13 +0100 Subject: [PATCH 224/299] Use one CPU emulation thread for --force-interpreter (#1467) --- src/Cafe/CafeSystem.cpp | 3 ++- src/Cafe/HW/Espresso/Debugger/Debugger.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Cafe/CafeSystem.cpp b/src/Cafe/CafeSystem.cpp index 88e0ed3d..1bf3755e 100644 --- a/src/Cafe/CafeSystem.cpp +++ b/src/Cafe/CafeSystem.cpp @@ -9,6 +9,7 @@ #include "audio/IAudioAPI.h" #include "audio/IAudioInputAPI.h" #include "config/ActiveSettings.h" +#include "config/LaunchSettings.h" #include "Cafe/TitleList/GameInfo.h" #include "Cafe/GraphicPack/GraphicPack2.h" #include "util/helpers/SystemException.h" @@ -843,7 +844,7 @@ namespace CafeSystem module->TitleStart(); cemu_initForGame(); // enter scheduler - if (ActiveSettings::GetCPUMode() == CPUMode::MulticoreRecompiler) + if (ActiveSettings::GetCPUMode() == CPUMode::MulticoreRecompiler && !LaunchSettings::ForceInterpreter()) coreinit::OSSchedulerBegin(3); else coreinit::OSSchedulerBegin(1); diff --git a/src/Cafe/HW/Espresso/Debugger/Debugger.cpp b/src/Cafe/HW/Espresso/Debugger/Debugger.cpp index 1fed07cd..37e374d6 100644 --- a/src/Cafe/HW/Espresso/Debugger/Debugger.cpp +++ b/src/Cafe/HW/Espresso/Debugger/Debugger.cpp @@ -575,7 +575,7 @@ void debugger_enterTW(PPCInterpreter_t* hCPU) debuggerState.debugSession.stepInto = false; debuggerState.debugSession.stepOver = false; debuggerState.debugSession.run = false; - while (true) + while (debuggerState.debugSession.isTrapped) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); // check for step commands From f61539a2624a8034aadc9eb5ab2555e628234804 Mon Sep 17 00:00:00 2001 From: brysma1 <brysma@proton.me> Date: Tue, 7 Jan 2025 22:22:55 -0500 Subject: [PATCH 225/299] Update build instructions for fedora and add troubleshooting step for alternative architectures (#1468) --- BUILD.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/BUILD.md b/BUILD.md index 41de928e..662be96d 100644 --- a/BUILD.md +++ b/BUILD.md @@ -57,7 +57,7 @@ At Step 3 in [Build Cemu using cmake and clang](#build-cemu-using-cmake-and-clan `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DCMAKE_C_COMPILER=/usr/bin/clang-15 -DCMAKE_CXX_COMPILER=/usr/bin/clang++-15 -G Ninja -DCMAKE_MAKE_PROGRAM=/usr/bin/ninja` #### For Fedora and derivatives: -`sudo dnf install bluez-libs clang cmake cubeb-devel freeglut-devel git glm-devel gtk3-devel kernel-headers libgcrypt-devel libsecret-devel libtool libusb1-devel llvm nasm ninja-build perl-core systemd-devel zlib-devel zlib-static` +`sudo dnf install bluez-libs-devel clang cmake cubeb-devel freeglut-devel git glm-devel gtk3-devel kernel-headers libgcrypt-devel libsecret-devel libtool libusb1-devel llvm nasm ninja-build perl-core systemd-devel wayland-protocols-devel zlib-devel zlib-static` ### Build Cemu @@ -120,6 +120,9 @@ This section refers to running `cmake -S...` (truncated). * Compiling failed during rebuild after `git pull` with an error that mentions RPATH * Add the following and try running the command again: * `-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON` +* Environment variable `VCPKG_FORCE_SYSTEM_BINARIES` must be set. + * Execute the folowing and then try running the command again: + * `export VCPKG_FORCE_SYSTEM_BINARIES=1` * If you are getting a random error, read the [package-name-and-platform]-out.log and [package-name-and-platform]-err.log for the actual reason to see if you might be lacking the headers from a dependency. From 1923b7a7c4bef1555430a4474591d75960a1959b Mon Sep 17 00:00:00 2001 From: rcaridade145 <rcaridade145@gmail.com> Date: Sun, 12 Jan 2025 11:37:56 +0000 Subject: [PATCH 226/299] Vulkan: Added R5_G6_B5_UNORM to supported readback formats (#1430) --- src/Cafe/HW/Latte/Renderer/Vulkan/TextureReadbackVk.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/TextureReadbackVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/TextureReadbackVk.cpp index b055fe7e..bce23b59 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/TextureReadbackVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/TextureReadbackVk.cpp @@ -22,7 +22,7 @@ uint32 LatteTextureReadbackInfoVk::GetImageSize(LatteTextureView* textureView) cemu_assert(textureFormat == VK_FORMAT_R8G8B8A8_UNORM); return baseTexture->width * baseTexture->height * 4; } - else if (textureView->format == Latte::E_GX2SURFFMT::R8_UNORM) + else if (textureView->format == Latte::E_GX2SURFFMT::R8_UNORM ) { cemu_assert(textureFormat == VK_FORMAT_R8_UNORM); return baseTexture->width * baseTexture->height * 1; @@ -79,6 +79,13 @@ uint32 LatteTextureReadbackInfoVk::GetImageSize(LatteTextureView* textureView) // todo - if driver does not support VK_FORMAT_D24_UNORM_S8_UINT this is represented as VK_FORMAT_D32_SFLOAT_S8_UINT which is 8 bytes return baseTexture->width * baseTexture->height * 4; } + else if (textureView->format == Latte::E_GX2SURFFMT::R5_G6_B5_UNORM ) + { + if(textureFormat == VK_FORMAT_R5G6B5_UNORM_PACK16){ + return baseTexture->width * baseTexture->height * 2; + } + return 0; + } else { cemuLog_log(LogType::Force, "Unsupported texture readback format {:04x}", (uint32)textureView->format); From 8dd809d725ece390f9103f689d7665ce2865034f Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 12 Jan 2025 12:39:02 +0100 Subject: [PATCH 227/299] Latte: Implement better index caching (#1443) --- .../HW/Latte/Core/LatteCommandProcessor.cpp | 15 ++ src/Cafe/HW/Latte/Core/LatteIndices.cpp | 112 ++++++++---- src/Cafe/HW/Latte/Core/LatteIndices.h | 2 +- src/Cafe/HW/Latte/Core/LatteOverlay.cpp | 6 + .../HW/Latte/Core/LattePerformanceMonitor.cpp | 3 +- .../HW/Latte/Core/LattePerformanceMonitor.h | 6 + src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp | 1 - .../HW/Latte/Renderer/OpenGL/OpenGLRenderer.h | 17 +- src/Cafe/HW/Latte/Renderer/Renderer.h | 11 +- .../Renderer/Vulkan/VKRMemoryManager.cpp | 149 +++++++++++++-- .../Latte/Renderer/Vulkan/VKRMemoryManager.h | 160 ++++++++++++++--- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 12 +- .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 5 +- .../Renderer/Vulkan/VulkanRendererCore.cpp | 29 +-- src/Common/precompiled.h | 19 ++ src/util/ChunkedHeap/ChunkedHeap.h | 170 +++++++++--------- 16 files changed, 526 insertions(+), 191 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp index 167911b6..a8f81901 100644 --- a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp +++ b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp @@ -141,6 +141,14 @@ private: void LatteCP_processCommandBuffer(DrawPassContext& drawPassCtx); +// called whenever the GPU runs out of commands or hits a wait condition (semaphores, HLE waits) +void LatteCP_signalEnterWait() +{ + // based on the assumption that games won't do a rugpull and swap out buffer data in the middle of an uninterrupted sequence of drawcalls, + // we only flush caches when the GPU goes idle or has to wait for any operation + LatteIndices_invalidateAll(); +} + /* * Read a U32 from the command buffer * If no data is available then wait in a busy loop @@ -466,6 +474,8 @@ LatteCMDPtr LatteCP_itWaitRegMem(LatteCMDPtr cmd, uint32 nWords) const uint32 GPU7_WAIT_MEM_OP_GREATER = 6; const uint32 GPU7_WAIT_MEM_OP_NEVER = 7; + LatteCP_signalEnterWait(); + bool stalls = false; if ((word0 & 0x10) != 0) { @@ -594,6 +604,7 @@ LatteCMDPtr LatteCP_itMemSemaphore(LatteCMDPtr cmd, uint32 nWords) else if(SEM_SIGNAL == 7) { // wait + LatteCP_signalEnterWait(); size_t loopCount = 0; while (true) { @@ -1305,11 +1316,13 @@ void LatteCP_processCommandBuffer(DrawPassContext& drawPassCtx) } case IT_HLE_TRIGGER_SCANBUFFER_SWAP: { + LatteCP_signalEnterWait(); LatteCP_itHLESwapScanBuffer(cmdData, nWords); break; } case IT_HLE_WAIT_FOR_FLIP: { + LatteCP_signalEnterWait(); LatteCP_itHLEWaitForFlip(cmdData, nWords); break; } @@ -1594,12 +1607,14 @@ void LatteCP_ProcessRingbuffer() } case IT_HLE_TRIGGER_SCANBUFFER_SWAP: { + LatteCP_signalEnterWait(); LatteCP_itHLESwapScanBuffer(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 64; break; } case IT_HLE_WAIT_FOR_FLIP: { + LatteCP_signalEnterWait(); LatteCP_itHLEWaitForFlip(cmd, nWords); timerRecheck += CP_TIMER_RECHECK / 1; break; diff --git a/src/Cafe/HW/Latte/Core/LatteIndices.cpp b/src/Cafe/HW/Latte/Core/LatteIndices.cpp index 6e1d7455..aec51725 100644 --- a/src/Cafe/HW/Latte/Core/LatteIndices.cpp +++ b/src/Cafe/HW/Latte/Core/LatteIndices.cpp @@ -1,6 +1,7 @@ #include "Cafe/HW/Latte/Core/LatteConst.h" #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "Cafe/HW/Latte/ISA/RegDefines.h" +#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" #include "Common/cpu_features.h" #if defined(ARCH_X86_64) && defined(__GNUC__) @@ -9,32 +10,53 @@ struct { - const void* lastPtr; - uint32 lastCount; - LattePrimitiveMode lastPrimitiveMode; - LatteIndexType lastIndexType; - // output - uint32 indexMin; - uint32 indexMax; - Renderer::INDEX_TYPE renderIndexType; - uint32 outputCount; - uint32 indexBufferOffset; - uint32 indexBufferIndex; + struct CacheEntry + { + // input data + const void* lastPtr; + uint32 lastCount; + LattePrimitiveMode lastPrimitiveMode; + LatteIndexType lastIndexType; + uint64 lastUsed; + // output + uint32 indexMin; + uint32 indexMax; + Renderer::INDEX_TYPE renderIndexType; + uint32 outputCount; + Renderer::IndexAllocation indexAllocation; + }; + std::array<CacheEntry, 8> entry; + uint64 currentUsageCounter{0}; }LatteIndexCache{}; void LatteIndices_invalidate(const void* memPtr, uint32 size) { - if (LatteIndexCache.lastPtr >= memPtr && (LatteIndexCache.lastPtr < ((uint8*)memPtr + size)) ) + for(auto& entry : LatteIndexCache.entry) { - LatteIndexCache.lastPtr = nullptr; - LatteIndexCache.lastCount = 0; + if (entry.lastPtr >= memPtr && (entry.lastPtr < ((uint8*)memPtr + size)) ) + { + if(entry.lastPtr != nullptr) + g_renderer->indexData_releaseIndexMemory(entry.indexAllocation); + entry.lastPtr = nullptr; + entry.lastCount = 0; + } } } void LatteIndices_invalidateAll() { - LatteIndexCache.lastPtr = nullptr; - LatteIndexCache.lastCount = 0; + for(auto& entry : LatteIndexCache.entry) + { + if (entry.lastPtr != nullptr) + g_renderer->indexData_releaseIndexMemory(entry.indexAllocation); + entry.lastPtr = nullptr; + entry.lastCount = 0; + } +} + +uint64 LatteIndices_GetNextUsageIndex() +{ + return LatteIndexCache.currentUsageCounter++; } uint32 LatteIndices_calculateIndexOutputSize(LattePrimitiveMode primitiveMode, LatteIndexType indexType, uint32 count) @@ -532,7 +554,7 @@ void LatteIndices_alternativeCalculateIndexMinMax(const void* indexData, LatteIn } } -void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 count, LattePrimitiveMode primitiveMode, uint32& indexMin, uint32& indexMax, Renderer::INDEX_TYPE& renderIndexType, uint32& outputCount, uint32& indexBufferOffset, uint32& indexBufferIndex) +void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 count, LattePrimitiveMode primitiveMode, uint32& indexMin, uint32& indexMax, Renderer::INDEX_TYPE& renderIndexType, uint32& outputCount, Renderer::IndexAllocation& indexAllocation) { // what this should do: // [x] use fast SIMD-based index decoding @@ -542,17 +564,18 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 // [ ] better cache implementation, allow to cache across frames // reuse from cache if data didn't change - if (LatteIndexCache.lastPtr == indexData && - LatteIndexCache.lastCount == count && - LatteIndexCache.lastPrimitiveMode == primitiveMode && - LatteIndexCache.lastIndexType == indexType) + auto cacheEntry = std::find_if(LatteIndexCache.entry.begin(), LatteIndexCache.entry.end(), [indexData, count, primitiveMode, indexType](const auto& entry) { - indexMin = LatteIndexCache.indexMin; - indexMax = LatteIndexCache.indexMax; - renderIndexType = LatteIndexCache.renderIndexType; - outputCount = LatteIndexCache.outputCount; - indexBufferOffset = LatteIndexCache.indexBufferOffset; - indexBufferIndex = LatteIndexCache.indexBufferIndex; + return entry.lastPtr == indexData && entry.lastCount == count && entry.lastPrimitiveMode == primitiveMode && entry.lastIndexType == indexType; + }); + if (cacheEntry != LatteIndexCache.entry.end()) + { + indexMin = cacheEntry->indexMin; + indexMax = cacheEntry->indexMax; + renderIndexType = cacheEntry->renderIndexType; + outputCount = cacheEntry->outputCount; + indexAllocation = cacheEntry->indexAllocation; + cacheEntry->lastUsed = LatteIndices_GetNextUsageIndex(); return; } @@ -576,10 +599,12 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 indexMin = 0; indexMax = std::max(count, 1u)-1; renderIndexType = Renderer::INDEX_TYPE::NONE; + indexAllocation = {}; return; // no indices } // query index buffer from renderer - void* indexOutputPtr = g_renderer->indexData_reserveIndexMemory(indexOutputSize, indexBufferOffset, indexBufferIndex); + indexAllocation = g_renderer->indexData_reserveIndexMemory(indexOutputSize); + void* indexOutputPtr = indexAllocation.mem; // decode indices indexMin = std::numeric_limits<uint32>::max(); @@ -704,16 +729,25 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 // recalculate index range but filter out primitive restart index LatteIndices_alternativeCalculateIndexMinMax(indexData, indexType, count, indexMin, indexMax); } - g_renderer->indexData_uploadIndexMemory(indexBufferOffset, indexOutputSize); + g_renderer->indexData_uploadIndexMemory(indexAllocation); + performanceMonitor.cycle[performanceMonitor.cycleIndex].indexDataUploaded += indexOutputSize; + // get least recently used cache entry + auto lruEntry = std::min_element(LatteIndexCache.entry.begin(), LatteIndexCache.entry.end(), [](const auto& a, const auto& b) + { + return a.lastUsed < b.lastUsed; + }); + // invalidate previous allocation + if(lruEntry->lastPtr != nullptr) + g_renderer->indexData_releaseIndexMemory(lruEntry->indexAllocation); // update cache - LatteIndexCache.lastPtr = indexData; - LatteIndexCache.lastCount = count; - LatteIndexCache.lastPrimitiveMode = primitiveMode; - LatteIndexCache.lastIndexType = indexType; - LatteIndexCache.indexMin = indexMin; - LatteIndexCache.indexMax = indexMax; - LatteIndexCache.renderIndexType = renderIndexType; - LatteIndexCache.outputCount = outputCount; - LatteIndexCache.indexBufferOffset = indexBufferOffset; - LatteIndexCache.indexBufferIndex = indexBufferIndex; + lruEntry->lastPtr = indexData; + lruEntry->lastCount = count; + lruEntry->lastPrimitiveMode = primitiveMode; + lruEntry->lastIndexType = indexType; + lruEntry->indexMin = indexMin; + lruEntry->indexMax = indexMax; + lruEntry->renderIndexType = renderIndexType; + lruEntry->outputCount = outputCount; + lruEntry->indexAllocation = indexAllocation; + lruEntry->lastUsed = LatteIndices_GetNextUsageIndex(); } diff --git a/src/Cafe/HW/Latte/Core/LatteIndices.h b/src/Cafe/HW/Latte/Core/LatteIndices.h index 917d7991..8aace24e 100644 --- a/src/Cafe/HW/Latte/Core/LatteIndices.h +++ b/src/Cafe/HW/Latte/Core/LatteIndices.h @@ -4,4 +4,4 @@ void LatteIndices_invalidate(const void* memPtr, uint32 size); void LatteIndices_invalidateAll(); -void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 count, LattePrimitiveMode primitiveMode, uint32& indexMin, uint32& indexMax, Renderer::INDEX_TYPE& renderIndexType, uint32& outputCount, uint32& indexBufferOffset, uint32& indexBufferIndex); \ No newline at end of file +void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 count, LattePrimitiveMode primitiveMode, uint32& indexMin, uint32& indexMax, Renderer::INDEX_TYPE& renderIndexType, uint32& outputCount, Renderer::IndexAllocation& indexAllocation); \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteOverlay.cpp b/src/Cafe/HW/Latte/Core/LatteOverlay.cpp index 238f85e8..e6edb904 100644 --- a/src/Cafe/HW/Latte/Core/LatteOverlay.cpp +++ b/src/Cafe/HW/Latte/Core/LatteOverlay.cpp @@ -107,7 +107,13 @@ void LatteOverlay_renderOverlay(ImVec2& position, ImVec2& pivot, sint32 directio ImGui::Text("VRAM: %dMB / %dMB", g_state.vramUsage, g_state.vramTotal); if (config.overlay.debug) + { + // general debug info + ImGui::Text("--- Debug info ---"); + ImGui::Text("IndexUploadPerFrame: %dKB", (performanceMonitor.stats.indexDataUploadPerFrame+1023)/1024); + // backend specific info g_renderer->AppendOverlayDebugInfo(); + } position.y += (ImGui::GetWindowSize().y + 10.0f) * direction; } diff --git a/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.cpp b/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.cpp index f2767446..14dfe9a9 100644 --- a/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.cpp +++ b/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.cpp @@ -74,7 +74,6 @@ void LattePerformanceMonitor_frameEnd() uniformBankDataUploadedPerFrame /= 1024ULL; uint32 uniformBankCountUploadedPerFrame = (uint32)(uniformBankUploadedCount / (uint64)elapsedFrames); uint64 indexDataUploadPerFrame = (indexDataUploaded / (uint64)elapsedFrames); - indexDataUploadPerFrame /= 1024ULL; double fps = (double)elapsedFrames2S * 1000.0 / (double)totalElapsedTimeFPS; uint32 shaderBindsPerFrame = shaderBindCounter / elapsedFrames; @@ -82,7 +81,7 @@ void LattePerformanceMonitor_frameEnd() uint32 rlps = (uint32)((uint64)recompilerLeaveCount * 1000ULL / (uint64)totalElapsedTime); uint32 tlps = (uint32)((uint64)threadLeaveCount * 1000ULL / (uint64)totalElapsedTime); // set stats - + performanceMonitor.stats.indexDataUploadPerFrame = indexDataUploadPerFrame; // next counter cycle sint32 nextCycleIndex = (performanceMonitor.cycleIndex + 1) % PERFORMANCE_MONITOR_TRACK_CYCLES; performanceMonitor.cycle[nextCycleIndex].drawCallCounter = 0; diff --git a/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.h b/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.h index ac75bb1b..dbc3cff9 100644 --- a/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.h +++ b/src/Cafe/HW/Latte/Core/LattePerformanceMonitor.h @@ -132,6 +132,12 @@ typedef struct LattePerfStatCounter numDrawBarriersPerFrame; LattePerfStatCounter numBeginRenderpassPerFrame; }vk; + + // calculated stats (per frame) + struct + { + uint32 indexDataUploadPerFrame; + }stats; }performanceMonitor_t; extern performanceMonitor_t performanceMonitor; diff --git a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp index 3bb6c7e3..2efef5bf 100644 --- a/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp +++ b/src/Cafe/HW/Latte/Core/LatteRenderTarget.cpp @@ -11,7 +11,6 @@ #include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h" #include "Cafe/GraphicPack/GraphicPack2.h" #include "config/ActiveSettings.h" -#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h" #include "gui/guiWrapper.h" #include "Cafe/OS/libs/erreula/erreula.h" #include "input/InputManager.h" diff --git a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h index 313ea3c0..e29e9d4c 100644 --- a/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h @@ -102,16 +102,21 @@ public: static void SetAttributeArrayState(uint32 index, bool isEnabled, sint32 aluDivisor); static void SetArrayElementBuffer(GLuint arrayElementBuffer); - // index - void* indexData_reserveIndexMemory(uint32 size, uint32& offset, uint32& bufferIndex) override + // index (not used by OpenGL renderer yet) + IndexAllocation indexData_reserveIndexMemory(uint32 size) override { - assert_dbg(); - return nullptr; + cemu_assert_unimplemented(); + return {}; } - void indexData_uploadIndexMemory(uint32 offset, uint32 size) override + void indexData_releaseIndexMemory(IndexAllocation& allocation) override { - assert_dbg(); + cemu_assert_unimplemented(); + } + + void indexData_uploadIndexMemory(IndexAllocation& allocation) override + { + cemu_assert_unimplemented(); } // uniform diff --git a/src/Cafe/HW/Latte/Renderer/Renderer.h b/src/Cafe/HW/Latte/Renderer/Renderer.h index 0b694bb9..77d588b9 100644 --- a/src/Cafe/HW/Latte/Renderer/Renderer.h +++ b/src/Cafe/HW/Latte/Renderer/Renderer.h @@ -138,8 +138,15 @@ public: virtual void draw_endSequence() = 0; // index - virtual void* indexData_reserveIndexMemory(uint32 size, uint32& offset, uint32& bufferIndex) = 0; - virtual void indexData_uploadIndexMemory(uint32 offset, uint32 size) = 0; + struct IndexAllocation + { + void* mem; // pointer to index data inside buffer + void* rendererInternal; // for renderer use + }; + + virtual IndexAllocation indexData_reserveIndexMemory(uint32 size) = 0; + virtual void indexData_releaseIndexMemory(IndexAllocation& allocation) = 0; + virtual void indexData_uploadIndexMemory(IndexAllocation& allocation) = 0; // occlusion queries virtual LatteQueryObject* occlusionQuery_create() = 0; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VKRMemoryManager.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRMemoryManager.cpp index c4f47a2b..3494dbc5 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VKRMemoryManager.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRMemoryManager.cpp @@ -23,11 +23,11 @@ void VKRSynchronizedRingAllocator::allocateAdditionalUploadBuffer(uint32 sizeReq AllocatorBuffer_t newBuffer{}; newBuffer.writeIndex = 0; newBuffer.basePtr = nullptr; - if (m_bufferType == BUFFER_TYPE::STAGING) + if (m_bufferType == VKR_BUFFER_TYPE::STAGING) m_vkrMemMgr->CreateBuffer(bufferAllocSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, newBuffer.vk_buffer, newBuffer.vk_mem); - else if (m_bufferType == BUFFER_TYPE::INDEX) + else if (m_bufferType == VKR_BUFFER_TYPE::INDEX) m_vkrMemMgr->CreateBuffer(bufferAllocSize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, newBuffer.vk_buffer, newBuffer.vk_mem); - else if (m_bufferType == BUFFER_TYPE::STRIDE) + else if (m_bufferType == VKR_BUFFER_TYPE::STRIDE) m_vkrMemMgr->CreateBuffer(bufferAllocSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, newBuffer.vk_buffer, newBuffer.vk_mem); else cemu_assert_debug(false); @@ -53,7 +53,7 @@ VKRSynchronizedRingAllocator::AllocatorReservation_t VKRSynchronizedRingAllocato uint32 distanceToSyncPoint; if (!itr.queue_syncPoints.empty()) { - if(itr.queue_syncPoints.front().offset < itr.writeIndex) + if (itr.queue_syncPoints.front().offset < itr.writeIndex) distanceToSyncPoint = 0xFFFFFFFF; else distanceToSyncPoint = itr.queue_syncPoints.front().offset - itr.writeIndex; @@ -100,7 +100,7 @@ VKRSynchronizedRingAllocator::AllocatorReservation_t VKRSynchronizedRingAllocato void VKRSynchronizedRingAllocator::FlushReservation(AllocatorReservation_t& uploadReservation) { - cemu_assert_debug(m_bufferType == BUFFER_TYPE::STAGING); // only the staging buffer isn't coherent + cemu_assert_debug(m_bufferType == VKR_BUFFER_TYPE::STAGING); // only the staging buffer isn't coherent // todo - use nonCoherentAtomSize for flush size (instead of hardcoded constant) VkMappedMemoryRange flushedRange{}; flushedRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; @@ -167,6 +167,70 @@ void VKRSynchronizedRingAllocator::GetStats(uint32& numBuffers, size_t& totalBuf } } +/* VKRSynchronizedHeapAllocator */ + +VKRSynchronizedHeapAllocator::VKRSynchronizedHeapAllocator(class VKRMemoryManager* vkMemoryManager, VKR_BUFFER_TYPE bufferType, size_t minimumBufferAllocSize) + : m_vkrMemMgr(vkMemoryManager), m_chunkedHeap(bufferType, minimumBufferAllocSize) {}; + +VKRSynchronizedHeapAllocator::AllocatorReservation* VKRSynchronizedHeapAllocator::AllocateBufferMemory(uint32 size, uint32 alignment) +{ + CHAddr addr = m_chunkedHeap.alloc(size, alignment); + m_activeAllocations.emplace_back(addr); + AllocatorReservation* res = m_poolAllocatorReservation.allocObj(); + res->bufferIndex = addr.chunkIndex; + res->bufferOffset = addr.offset; + res->size = size; + res->memPtr = m_chunkedHeap.GetChunkPtr(addr.chunkIndex) + addr.offset; + m_chunkedHeap.GetChunkVkMemInfo(addr.chunkIndex, res->vkBuffer, res->vkMem); + return res; +} + +void VKRSynchronizedHeapAllocator::FreeReservation(AllocatorReservation* uploadReservation) +{ + // put the allocation on a delayed release queue for the current command buffer + uint64 currentCommandBufferId = VulkanRenderer::GetInstance()->GetCurrentCommandBufferId(); + auto it = std::find_if(m_activeAllocations.begin(), m_activeAllocations.end(), [&uploadReservation](const TrackedAllocation& allocation) { return allocation.allocation.chunkIndex == uploadReservation->bufferIndex && allocation.allocation.offset == uploadReservation->bufferOffset; }); + cemu_assert_debug(it != m_activeAllocations.end()); + m_releaseQueue[currentCommandBufferId].emplace_back(it->allocation); + m_activeAllocations.erase(it); + m_poolAllocatorReservation.freeObj(uploadReservation); +} + +void VKRSynchronizedHeapAllocator::FlushReservation(AllocatorReservation* uploadReservation) +{ + if (m_chunkedHeap.RequiresFlush(uploadReservation->bufferIndex)) + { + VkMappedMemoryRange flushedRange{}; + flushedRange.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; + flushedRange.memory = uploadReservation->vkMem; + flushedRange.offset = uploadReservation->bufferOffset; + flushedRange.size = uploadReservation->size; + vkFlushMappedMemoryRanges(VulkanRenderer::GetInstance()->GetLogicalDevice(), 1, &flushedRange); + } +} + +void VKRSynchronizedHeapAllocator::CleanupBuffer(uint64 latestFinishedCommandBufferId) +{ + auto it = m_releaseQueue.begin(); + while (it != m_releaseQueue.end()) + { + if (it->first <= latestFinishedCommandBufferId) + { + // release allocations + for(auto& addr : it->second) + m_chunkedHeap.free(addr); + it = m_releaseQueue.erase(it); + continue; + } + it++; + } +} + +void VKRSynchronizedHeapAllocator::GetStats(uint32& numBuffers, size_t& totalBufferSize, size_t& freeBufferSize) const +{ + m_chunkedHeap.GetStats(numBuffers, totalBufferSize, freeBufferSize); +} + /* VkTextureChunkedHeap */ uint32 VkTextureChunkedHeap::allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize) @@ -175,7 +239,7 @@ uint32 VkTextureChunkedHeap::allocateNewChunk(uint32 chunkIndex, uint32 minimumA m_list_chunkInfo.resize(m_list_chunkInfo.size() + 1); // pad minimumAllocationSize to 32KB alignment - minimumAllocationSize = (minimumAllocationSize + (32*1024-1)) & ~(32 * 1024 - 1); + minimumAllocationSize = (minimumAllocationSize + (32 * 1024 - 1)) & ~(32 * 1024 - 1); uint32 allocationSize = 1024 * 1024 * 128; if (chunkIndex == 0) @@ -189,8 +253,7 @@ uint32 VkTextureChunkedHeap::allocateNewChunk(uint32 chunkIndex, uint32 minimumA std::vector<uint32> deviceLocalMemoryTypeIndices = m_vkrMemoryManager->FindMemoryTypes(m_typeFilter, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); std::vector<uint32> hostLocalMemoryTypeIndices = m_vkrMemoryManager->FindMemoryTypes(m_typeFilter, 0); // remove device local memory types from host local vector - auto pred = [&deviceLocalMemoryTypeIndices](const uint32& v) ->bool - { + auto pred = [&deviceLocalMemoryTypeIndices](const uint32& v) -> bool { return std::find(deviceLocalMemoryTypeIndices.begin(), deviceLocalMemoryTypeIndices.end(), v) != deviceLocalMemoryTypeIndices.end(); }; hostLocalMemoryTypeIndices.erase(std::remove_if(hostLocalMemoryTypeIndices.begin(), hostLocalMemoryTypeIndices.end(), pred), hostLocalMemoryTypeIndices.end()); @@ -206,7 +269,7 @@ uint32 VkTextureChunkedHeap::allocateNewChunk(uint32 chunkIndex, uint32 minimumA allocInfo.memoryTypeIndex = memType; VkDeviceMemory imageMemory; - VkResult r = vkAllocateMemory(m_device, &allocInfo, nullptr, &imageMemory); + VkResult r = vkAllocateMemory(VulkanRenderer::GetInstance()->GetLogicalDevice(), &allocInfo, nullptr, &imageMemory); if (r != VK_SUCCESS) continue; m_list_chunkInfo[chunkIndex].mem = imageMemory; @@ -221,7 +284,7 @@ uint32 VkTextureChunkedHeap::allocateNewChunk(uint32 chunkIndex, uint32 minimumA allocInfo.memoryTypeIndex = memType; VkDeviceMemory imageMemory; - VkResult r = vkAllocateMemory(m_device, &allocInfo, nullptr, &imageMemory); + VkResult r = vkAllocateMemory(VulkanRenderer::GetInstance()->GetLogicalDevice(), &allocInfo, nullptr, &imageMemory); if (r != VK_SUCCESS) continue; m_list_chunkInfo[chunkIndex].mem = imageMemory; @@ -238,6 +301,68 @@ uint32 VkTextureChunkedHeap::allocateNewChunk(uint32 chunkIndex, uint32 minimumA return 0; } +/* VkBufferChunkedHeap */ + +VKRBuffer* VKRBuffer::Create(VKR_BUFFER_TYPE bufferType, size_t bufferSize, VkMemoryPropertyFlags properties) +{ + auto* memMgr = VulkanRenderer::GetInstance()->GetMemoryManager(); + VkBuffer buffer; + VkDeviceMemory bufferMemory; + bool allocSuccess; + if (bufferType == VKR_BUFFER_TYPE::STAGING) + allocSuccess = memMgr->CreateBuffer2(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, properties, buffer, bufferMemory); + else if (bufferType == VKR_BUFFER_TYPE::INDEX) + allocSuccess = memMgr->CreateBuffer2(bufferSize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, properties, buffer, bufferMemory); + else if (bufferType == VKR_BUFFER_TYPE::STRIDE) + allocSuccess = memMgr->CreateBuffer2(bufferSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, properties, buffer, bufferMemory); + else + cemu_assert_debug(false); + if (!allocSuccess) + return nullptr; + + VKRBuffer* bufferObj = new VKRBuffer(buffer, bufferMemory); + // if host visible, then map buffer + void* data = nullptr; + if (properties & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) + { + vkMapMemory(VulkanRenderer::GetInstance()->GetLogicalDevice(), bufferMemory, 0, bufferSize, 0, &data); + bufferObj->m_requiresFlush = !HAS_FLAG(properties, VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + } + bufferObj->m_mappedMemory = (uint8*)data; + return bufferObj; +} + +VKRBuffer::~VKRBuffer() +{ + if (m_mappedMemory) + vkUnmapMemory(VulkanRenderer::GetInstance()->GetLogicalDevice(), m_bufferMemory); + if (m_bufferMemory != VK_NULL_HANDLE) + vkFreeMemory(VulkanRenderer::GetInstance()->GetLogicalDevice(), m_bufferMemory, nullptr); + if (m_buffer != VK_NULL_HANDLE) + vkDestroyBuffer(VulkanRenderer::GetInstance()->GetLogicalDevice(), m_buffer, nullptr); +} + +VkBufferChunkedHeap::~VkBufferChunkedHeap() +{ + for (auto& chunk : m_chunkBuffers) + delete chunk; +} + +uint32 VkBufferChunkedHeap::allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize) +{ + size_t allocationSize = std::max<size_t>(m_minimumBufferAllocationSize, minimumAllocationSize); + VKRBuffer* buffer = VKRBuffer::Create(m_bufferType, allocationSize, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + if(!buffer) + buffer = VKRBuffer::Create(m_bufferType, allocationSize, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); + if(!buffer) + VulkanRenderer::GetInstance()->UnrecoverableError("Failed to allocate buffer memory for VkBufferChunkedHeap"); + cemu_assert_debug(buffer); + cemu_assert_debug(m_chunkBuffers.size() == chunkIndex); + m_chunkBuffers.emplace_back(buffer); + // todo - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT might be worth it? + return allocationSize; +} + uint32_t VKRMemoryManager::FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) const { VkPhysicalDeviceMemoryProperties memProperties; @@ -423,7 +548,7 @@ bool VKRMemoryManager::CreateBufferFromHostMemory(void* hostPointer, VkDeviceSiz importHostMem.sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_HOST_POINTER_INFO_EXT; importHostMem.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT; importHostMem.pHostPointer = hostPointer; - // VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT or + // VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT or // VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_MAPPED_FOREIGN_MEMORY_BIT_EXT // whats the difference ? @@ -469,7 +594,7 @@ VkImageMemAllocation* VKRMemoryManager::imageMemoryAllocate(VkImage image) auto it = map_textureHeap.find(typeFilter); if (it == map_textureHeap.end()) { - texHeap = new VkTextureChunkedHeap(this, typeFilter, m_vkr->GetLogicalDevice()); + texHeap = new VkTextureChunkedHeap(this, typeFilter); map_textureHeap.emplace(typeFilter, texHeap); } else diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VKRMemoryManager.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRMemoryManager.h index bf2d919b..08af5882 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VKRMemoryManager.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRMemoryManager.h @@ -2,6 +2,36 @@ #include "Cafe/HW/Latte/Renderer/Renderer.h" #include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h" #include "util/ChunkedHeap/ChunkedHeap.h" +#include "util/helpers/MemoryPool.h" + +enum class VKR_BUFFER_TYPE +{ + STAGING, // staging upload buffer + INDEX, // buffer for index data + STRIDE, // buffer for stride-adjusted vertex data +}; + +class VKRBuffer +{ + public: + static VKRBuffer* Create(VKR_BUFFER_TYPE bufferType, size_t bufferSize, VkMemoryPropertyFlags properties); + ~VKRBuffer(); + + VkBuffer GetVkBuffer() const { return m_buffer; } + VkDeviceMemory GetVkBufferMemory() const { return m_bufferMemory; } + + uint8* GetPtr() const { return m_mappedMemory; } + + bool RequiresFlush() const { return m_requiresFlush; } + + private: + VKRBuffer(VkBuffer buffer, VkDeviceMemory bufferMem) : m_buffer(buffer), m_bufferMemory(bufferMem) { }; + + VkBuffer m_buffer; + VkDeviceMemory m_bufferMemory; + uint8* m_mappedMemory; + bool m_requiresFlush{false}; +}; struct VkImageMemAllocation { @@ -14,18 +44,16 @@ struct VkImageMemAllocation uint32 getAllocationSize() { return allocationSize; } }; -class VkTextureChunkedHeap : private ChunkedHeap +class VkTextureChunkedHeap : private ChunkedHeap<> { public: - VkTextureChunkedHeap(class VKRMemoryManager* memoryManager, uint32 typeFilter, VkDevice device) : m_vkrMemoryManager(memoryManager), m_typeFilter(typeFilter), m_device(device) { }; + VkTextureChunkedHeap(class VKRMemoryManager* memoryManager, uint32 typeFilter) : m_vkrMemoryManager(memoryManager), m_typeFilter(typeFilter) { }; struct ChunkInfo { VkDeviceMemory mem; }; - uint32 allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize) override; - CHAddr allocMem(uint32 size, uint32 alignment) { if (alignment < 4) @@ -43,11 +71,6 @@ public: this->free(addr); } - void setDevice(VkDevice dev) - { - m_device = dev; - } - VkDeviceMemory getChunkMem(uint32 index) { if (index >= m_list_chunkInfo.size()) @@ -57,28 +80,73 @@ public: void getStatistics(uint32& totalHeapSize, uint32& allocatedBytes) const { - totalHeapSize = numHeapBytes; - allocatedBytes = numAllocatedBytes; + totalHeapSize = m_numHeapBytes; + allocatedBytes = m_numAllocatedBytes; } - VkDevice m_device; + private: + uint32 allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize) override; + uint32 m_typeFilter{ 0xFFFFFFFF }; class VKRMemoryManager* m_vkrMemoryManager; std::vector<ChunkInfo> m_list_chunkInfo; }; +class VkBufferChunkedHeap : private ChunkedHeap<> +{ + public: + VkBufferChunkedHeap(VKR_BUFFER_TYPE bufferType, size_t minimumBufferAllocationSize) : m_bufferType(bufferType), m_minimumBufferAllocationSize(minimumBufferAllocationSize) { }; + ~VkBufferChunkedHeap(); + + using ChunkedHeap::alloc; + using ChunkedHeap::free; + + uint8* GetChunkPtr(uint32 index) const + { + if (index >= m_chunkBuffers.size()) + return nullptr; + return m_chunkBuffers[index]->GetPtr(); + } + + void GetChunkVkMemInfo(uint32 index, VkBuffer& buffer, VkDeviceMemory& mem) + { + if (index >= m_chunkBuffers.size()) + { + buffer = VK_NULL_HANDLE; + mem = VK_NULL_HANDLE; + return; + } + buffer = m_chunkBuffers[index]->GetVkBuffer(); + mem = m_chunkBuffers[index]->GetVkBufferMemory(); + } + + void GetStats(uint32& numBuffers, size_t& totalBufferSize, size_t& freeBufferSize) const + { + numBuffers = m_chunkBuffers.size(); + totalBufferSize = m_numHeapBytes; + freeBufferSize = m_numHeapBytes - m_numAllocatedBytes; + } + + bool RequiresFlush(uint32 index) const + { + if (index >= m_chunkBuffers.size()) + return false; + return m_chunkBuffers[index]->RequiresFlush(); + } + + private: + uint32 allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize) override; + + VKR_BUFFER_TYPE m_bufferType; + std::vector<VKRBuffer*> m_chunkBuffers; + size_t m_minimumBufferAllocationSize; +}; + // a circular ring-buffer which tracks and releases memory per command-buffer class VKRSynchronizedRingAllocator { public: - enum class BUFFER_TYPE - { - STAGING, // staging upload buffer - INDEX, // buffer for index data - STRIDE, // buffer for stride-adjusted vertex data - }; - - VKRSynchronizedRingAllocator(class VulkanRenderer* vkRenderer, class VKRMemoryManager* vkMemoryManager, BUFFER_TYPE bufferType, uint32 minimumBufferAllocSize) : m_vkr(vkRenderer), m_vkrMemMgr(vkMemoryManager), m_bufferType(bufferType), m_minimumBufferAllocSize(minimumBufferAllocSize) {}; + VKRSynchronizedRingAllocator(class VulkanRenderer* vkRenderer, class VKRMemoryManager* vkMemoryManager, VKR_BUFFER_TYPE bufferType, uint32 minimumBufferAllocSize) : m_vkr(vkRenderer), m_vkrMemMgr(vkMemoryManager), m_bufferType(bufferType), m_minimumBufferAllocSize(minimumBufferAllocSize) {}; VKRSynchronizedRingAllocator(const VKRSynchronizedRingAllocator&) = delete; // disallow copy struct BufferSyncPoint_t @@ -126,13 +194,53 @@ private: const class VulkanRenderer* m_vkr; const class VKRMemoryManager* m_vkrMemMgr; - const BUFFER_TYPE m_bufferType; + const VKR_BUFFER_TYPE m_bufferType; const uint32 m_minimumBufferAllocSize; std::vector<AllocatorBuffer_t> m_buffers; }; +// heap style allocator with released memory being freed after the current command buffer finishes +class VKRSynchronizedHeapAllocator +{ + struct TrackedAllocation + { + TrackedAllocation(CHAddr allocation) : allocation(allocation) {}; + CHAddr allocation; + }; + + public: + VKRSynchronizedHeapAllocator(class VKRMemoryManager* vkMemoryManager, VKR_BUFFER_TYPE bufferType, size_t minimumBufferAllocSize); + VKRSynchronizedHeapAllocator(const VKRSynchronizedHeapAllocator&) = delete; // disallow copy + + struct AllocatorReservation + { + VkBuffer vkBuffer; + VkDeviceMemory vkMem; + uint8* memPtr; + uint32 bufferOffset; + uint32 size; + uint32 bufferIndex; + }; + + AllocatorReservation* AllocateBufferMemory(uint32 size, uint32 alignment); + void FreeReservation(AllocatorReservation* uploadReservation); + void FlushReservation(AllocatorReservation* uploadReservation); + + void CleanupBuffer(uint64 latestFinishedCommandBufferId); + + void GetStats(uint32& numBuffers, size_t& totalBufferSize, size_t& freeBufferSize) const; + private: + const class VKRMemoryManager* m_vkrMemMgr; + VkBufferChunkedHeap m_chunkedHeap; + // allocations + std::vector<TrackedAllocation> m_activeAllocations; + MemoryPool<AllocatorReservation> m_poolAllocatorReservation{32}; + // release queue + std::unordered_map<uint64, std::vector<CHAddr>> m_releaseQueue; +}; + void LatteIndices_invalidateAll(); class VKRMemoryManager @@ -140,9 +248,9 @@ class VKRMemoryManager friend class VKRSynchronizedRingAllocator; public: VKRMemoryManager(class VulkanRenderer* renderer) : - m_stagingBuffer(renderer, this, VKRSynchronizedRingAllocator::BUFFER_TYPE::STAGING, 32u * 1024 * 1024), - m_indexBuffer(renderer, this, VKRSynchronizedRingAllocator::BUFFER_TYPE::INDEX, 4u * 1024 * 1024), - m_vertexStrideMetalBuffer(renderer, this, VKRSynchronizedRingAllocator::BUFFER_TYPE::STRIDE, 4u * 1024 * 1024) + m_stagingBuffer(renderer, this, VKR_BUFFER_TYPE::STAGING, 32u * 1024 * 1024), + m_indexBuffer(this, VKR_BUFFER_TYPE::INDEX, 4u * 1024 * 1024), + m_vertexStrideMetalBuffer(renderer, this, VKR_BUFFER_TYPE::STRIDE, 4u * 1024 * 1024) { m_vkr = renderer; } @@ -167,7 +275,7 @@ public: } VKRSynchronizedRingAllocator& getStagingAllocator() { return m_stagingBuffer; }; // allocator for texture/attribute/uniform uploads - VKRSynchronizedRingAllocator& getIndexAllocator() { return m_indexBuffer; }; // allocator for index data + VKRSynchronizedHeapAllocator& GetIndexAllocator() { return m_indexBuffer; }; // allocator for index data VKRSynchronizedRingAllocator& getMetalStrideWorkaroundAllocator() { return m_vertexStrideMetalBuffer; }; // allocator for stride-adjusted vertex data void cleanupBuffers(uint64 latestFinishedCommandBufferId) @@ -202,6 +310,6 @@ public: private: class VulkanRenderer* m_vkr; VKRSynchronizedRingAllocator m_stagingBuffer; - VKRSynchronizedRingAllocator m_indexBuffer; + VKRSynchronizedHeapAllocator m_indexBuffer; VKRSynchronizedRingAllocator m_vertexStrideMetalBuffer; }; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index eae6daf2..66369c10 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -681,6 +681,9 @@ VulkanRenderer::~VulkanRenderer() vkDestroyDebugUtilsMessengerEXT(m_instance, m_debugCallback, nullptr); } + // destroy memory manager + delete memoryManager; + // destroy instance, devices if (m_instance != VK_NULL_HANDLE) { @@ -692,9 +695,6 @@ VulkanRenderer::~VulkanRenderer() vkDestroyInstance(m_instance, nullptr); } - // destroy memory manager - delete memoryManager; - // crashes? //glslang::FinalizeProcess(); } @@ -3701,7 +3701,7 @@ void VulkanRenderer::bufferCache_copyStreamoutToMainBuffer(uint32 srcOffset, uin void VulkanRenderer::AppendOverlayDebugInfo() { - ImGui::Text("--- Vulkan info ---"); + ImGui::Text("--- Vulkan debug info ---"); ImGui::Text("GfxPipelines %u", performanceMonitor.vk.numGraphicPipelines.get()); ImGui::Text("DescriptorSets %u", performanceMonitor.vk.numDescriptorSets.get()); ImGui::Text("DS ImgSamplers %u", performanceMonitor.vk.numDescriptorSamplerTextures.get()); @@ -3719,7 +3719,7 @@ void VulkanRenderer::AppendOverlayDebugInfo() ImGui::Text("BeginRP/f %u", performanceMonitor.vk.numBeginRenderpassPerFrame.get()); ImGui::Text("Barriers/f %u", performanceMonitor.vk.numDrawBarriersPerFrame.get()); - ImGui::Text("--- Cache info ---"); + ImGui::Text("--- Cache debug info ---"); uint32 bufferCacheHeapSize = 0; uint32 bufferCacheAllocationSize = 0; @@ -3739,7 +3739,7 @@ void VulkanRenderer::AppendOverlayDebugInfo() ImGui::SameLine(60.0f); ImGui::Text("%06uKB / %06uKB Buffers: %u", ((uint32)(totalSize - freeSize) + 1023) / 1024, ((uint32)totalSize + 1023) / 1024, (uint32)numBuffers); - memoryManager->getIndexAllocator().GetStats(numBuffers, totalSize, freeSize); + memoryManager->GetIndexAllocator().GetStats(numBuffers, totalSize, freeSize); ImGui::Text("Index"); ImGui::SameLine(60.0f); ImGui::Text("%06uKB / %06uKB Buffers: %u", ((uint32)(totalSize - freeSize) + 1023) / 1024, ((uint32)totalSize + 1023) / 1024, (uint32)numBuffers); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index 52c1c6ed..5ef4558d 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -328,8 +328,9 @@ public: RendererShader* shader_create(RendererShader::ShaderType type, uint64 baseHash, uint64 auxHash, const std::string& source, bool isGameShader, bool isGfxPackShader) override; - void* indexData_reserveIndexMemory(uint32 size, uint32& offset, uint32& bufferIndex) override; - void indexData_uploadIndexMemory(uint32 offset, uint32 size) override; + IndexAllocation indexData_reserveIndexMemory(uint32 size) override; + void indexData_releaseIndexMemory(IndexAllocation& allocation) override; + void indexData_uploadIndexMemory(IndexAllocation& allocation) override; // externally callable void GetTextureFormatInfoVK(Latte::E_GX2SURFFMT format, bool isDepth, Latte::E_DIM dim, sint32 width, sint32 height, FormatInfoVK* formatInfoOut); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp index dd39bd88..198a32cb 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp @@ -357,18 +357,20 @@ PipelineInfo* VulkanRenderer::draw_getOrCreateGraphicsPipeline(uint32 indexCount return draw_createGraphicsPipeline(indexCount); } -void* VulkanRenderer::indexData_reserveIndexMemory(uint32 size, uint32& offset, uint32& bufferIndex) +Renderer::IndexAllocation VulkanRenderer::indexData_reserveIndexMemory(uint32 size) { - auto& indexAllocator = this->memoryManager->getIndexAllocator(); - auto resv = indexAllocator.AllocateBufferMemory(size, 32); - offset = resv.bufferOffset; - bufferIndex = resv.bufferIndex; - return resv.memPtr; + VKRSynchronizedHeapAllocator::AllocatorReservation* resv = memoryManager->GetIndexAllocator().AllocateBufferMemory(size, 32); + return { resv->memPtr, resv }; } -void VulkanRenderer::indexData_uploadIndexMemory(uint32 offset, uint32 size) +void VulkanRenderer::indexData_releaseIndexMemory(IndexAllocation& allocation) { - // does nothing since the index buffer memory is coherent + memoryManager->GetIndexAllocator().FreeReservation((VKRSynchronizedHeapAllocator::AllocatorReservation*)allocation.rendererInternal); +} + +void VulkanRenderer::indexData_uploadIndexMemory(IndexAllocation& allocation) +{ + memoryManager->GetIndexAllocator().FlushReservation((VKRSynchronizedHeapAllocator::AllocatorReservation*)allocation.rendererInternal); } float s_vkUniformData[512 * 4]; @@ -1413,14 +1415,15 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 uint32 hostIndexCount; uint32 indexMin = 0; uint32 indexMax = 0; - uint32 indexBufferOffset = 0; - uint32 indexBufferIndex = 0; - LatteIndices_decode(memory_getPointerFromVirtualOffset(indexDataMPTR), indexType, count, primitiveMode, indexMin, indexMax, hostIndexType, hostIndexCount, indexBufferOffset, indexBufferIndex); - + Renderer::IndexAllocation indexAllocation; + LatteIndices_decode(memory_getPointerFromVirtualOffset(indexDataMPTR), indexType, count, primitiveMode, indexMin, indexMax, hostIndexType, hostIndexCount, indexAllocation); + VKRSynchronizedHeapAllocator::AllocatorReservation* indexReservation = (VKRSynchronizedHeapAllocator::AllocatorReservation*)indexAllocation.rendererInternal; // update index binding bool isPrevIndexData = false; if (hostIndexType != INDEX_TYPE::NONE) { + uint32 indexBufferIndex = indexReservation->bufferIndex; + uint32 indexBufferOffset = indexReservation->bufferOffset; if (m_state.activeIndexBufferOffset != indexBufferOffset || m_state.activeIndexBufferIndex != indexBufferIndex || m_state.activeIndexType != hostIndexType) { m_state.activeIndexType = hostIndexType; @@ -1433,7 +1436,7 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 vkType = VK_INDEX_TYPE_UINT32; else cemu_assert(false); - vkCmdBindIndexBuffer(m_state.currentCommandBuffer, memoryManager->getIndexAllocator().GetBufferByIndex(indexBufferIndex), indexBufferOffset, vkType); + vkCmdBindIndexBuffer(m_state.currentCommandBuffer, indexReservation->vkBuffer, indexBufferOffset, vkType); } else isPrevIndexData = true; diff --git a/src/Common/precompiled.h b/src/Common/precompiled.h index d4df4343..3dfeaf74 100644 --- a/src/Common/precompiled.h +++ b/src/Common/precompiled.h @@ -274,6 +274,25 @@ inline uint64 _udiv128(uint64 highDividend, uint64 lowDividend, uint64 divisor, #define NOEXPORT __attribute__ ((visibility ("hidden"))) #endif +#if defined(_MSC_VER) +#define FORCE_INLINE __forceinline +#elif defined(__GNUC__) || defined(__clang__) +#define FORCE_INLINE inline __attribute__((always_inline)) +#else +#define FORCE_INLINE +#endif + +FORCE_INLINE inline int BSF(uint32 v) // returns index of first bit set, counting from LSB. If v is 0 then result is undefined +{ +#if defined(_MSC_VER) + return _tzcnt_u32(v); // TZCNT requires BMI1. But if not supported it will execute as BSF +#elif defined(__GNUC__) || defined(__clang__) + return __builtin_ctz(v); +#else + return std::countr_zero(v); +#endif +} + // On aarch64 we handle some of the x86 intrinsics by implementing them as wrappers #if defined(__aarch64__) diff --git a/src/util/ChunkedHeap/ChunkedHeap.h b/src/util/ChunkedHeap/ChunkedHeap.h index abc45429..21a1b868 100644 --- a/src/util/ChunkedHeap/ChunkedHeap.h +++ b/src/util/ChunkedHeap/ChunkedHeap.h @@ -1,35 +1,39 @@ #pragma once +#include <util/helpers/MemoryPool.h> + struct CHAddr { uint32 offset; uint32 chunkIndex; + void* internal; // AllocRange - CHAddr(uint32 _offset, uint32 _chunkIndex) : offset(_offset), chunkIndex(_chunkIndex) {}; + CHAddr(uint32 _offset, uint32 _chunkIndex, void* internal = nullptr) : offset(_offset), chunkIndex(_chunkIndex), internal(internal) {}; CHAddr() : offset(0xFFFFFFFF), chunkIndex(0xFFFFFFFF) {}; bool isValid() { return chunkIndex != 0xFFFFFFFF; }; static CHAddr getInvalid() { return CHAddr(0xFFFFFFFF, 0xFFFFFFFF); }; }; +template<uint32 TMinimumAlignment = 32> class ChunkedHeap { - struct allocRange_t + struct AllocRange { - allocRange_t* nextFree{}; - allocRange_t* prevFree{}; - allocRange_t* prevOrdered{}; - allocRange_t* nextOrdered{}; + AllocRange* nextFree{}; + AllocRange* prevFree{}; + AllocRange* prevOrdered{}; + AllocRange* nextOrdered{}; uint32 offset; uint32 chunkIndex; uint32 size; bool isFree; - allocRange_t(uint32 _offset, uint32 _chunkIndex, uint32 _size, bool _isFree) : offset(_offset), chunkIndex(_chunkIndex), size(_size), isFree(_isFree), nextFree(nullptr) {}; + AllocRange(uint32 _offset, uint32 _chunkIndex, uint32 _size, bool _isFree) : offset(_offset), chunkIndex(_chunkIndex), size(_size), isFree(_isFree), nextFree(nullptr) {}; }; - struct chunk_t + struct Chunk { - std::unordered_map<uint32, allocRange_t*> map_allocatedRange; + uint32 size; }; public: @@ -47,45 +51,32 @@ public: _free(addr); } - virtual uint32 allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize) - { - return 0; - } + virtual uint32 allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize) = 0; private: unsigned ulog2(uint32 v) { - static const unsigned MUL_DE_BRUIJN_BIT[] = - { - 0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, - 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31 - }; - - v |= v >> 1; - v |= v >> 2; - v |= v >> 4; - v |= v >> 8; - v |= v >> 16; - - return MUL_DE_BRUIJN_BIT[(v * 0x07C4ACDDu) >> 27]; + cemu_assert_debug(v != 0); + return 31 - std::countl_zero(v); } - void trackFreeRange(allocRange_t* range) + void trackFreeRange(AllocRange* range) { // get index of msb cemu_assert_debug(range->size != 0); // size of zero is not allowed uint32 bucketIndex = ulog2(range->size); - range->nextFree = bucketFreeRange[bucketIndex]; - if (bucketFreeRange[bucketIndex]) - bucketFreeRange[bucketIndex]->prevFree = range; + range->nextFree = m_bucketFreeRange[bucketIndex]; + if (m_bucketFreeRange[bucketIndex]) + m_bucketFreeRange[bucketIndex]->prevFree = range; range->prevFree = nullptr; - bucketFreeRange[bucketIndex] = range; + m_bucketFreeRange[bucketIndex] = range; + m_bucketUseMask |= (1u << bucketIndex); } - void forgetFreeRange(allocRange_t* range, uint32 bucketIndex) + void forgetFreeRange(AllocRange* range, uint32 bucketIndex) { - allocRange_t* prevRange = range->prevFree; - allocRange_t* nextRange = range->nextFree; + AllocRange* prevRange = range->prevFree; + AllocRange* nextRange = range->nextFree; if (prevRange) { prevRange->nextFree = nextRange; @@ -94,36 +85,42 @@ private: } else { - if (bucketFreeRange[bucketIndex] != range) - assert_dbg(); - bucketFreeRange[bucketIndex] = nextRange; + cemu_assert_debug(m_bucketFreeRange[bucketIndex] == range); + m_bucketFreeRange[bucketIndex] = nextRange; if (nextRange) nextRange->prevFree = nullptr; + else + m_bucketUseMask &= ~(1u << bucketIndex); } } bool allocateChunk(uint32 minimumAllocationSize) { - uint32 chunkIndex = (uint32)list_chunks.size(); - list_chunks.emplace_back(new chunk_t()); + uint32 chunkIndex = (uint32)m_chunks.size(); + m_chunks.emplace_back(); uint32 chunkSize = allocateNewChunk(chunkIndex, minimumAllocationSize); + cemu_assert_debug((chunkSize%TMinimumAlignment) == 0); // chunk size should be a multiple of the minimum alignment if (chunkSize == 0) return false; - allocRange_t* range = new allocRange_t(0, chunkIndex, chunkSize, true); + cemu_assert_debug(chunkSize < 0x80000000u); // chunk size must be below 2GB + AllocRange* range = m_allocEntriesPool.allocObj(0, chunkIndex, chunkSize, true); trackFreeRange(range); - numHeapBytes += chunkSize; + m_numHeapBytes += chunkSize; return true; } - void _allocFrom(allocRange_t* range, uint32 bucketIndex, uint32 allocOffset, uint32 allocSize) + void _allocFrom(AllocRange* range, uint32 bucketIndex, uint32 allocOffset, uint32 allocSize) { + cemu_assert_debug(allocSize > 0); // remove the range from the chain of free ranges forgetFreeRange(range, bucketIndex); // split head, allocation and tail into separate ranges - if (allocOffset > range->offset) + uint32 headBytes = allocOffset - range->offset; + if (headBytes > 0) { // alignment padding -> create free range - allocRange_t* head = new allocRange_t(range->offset, range->chunkIndex, allocOffset - range->offset, true); + cemu_assert_debug(headBytes >= TMinimumAlignment); + AllocRange* head = m_allocEntriesPool.allocObj(range->offset, range->chunkIndex, headBytes, true); trackFreeRange(head); if (range->prevOrdered) range->prevOrdered->nextOrdered = head; @@ -131,10 +128,12 @@ private: head->nextOrdered = range; range->prevOrdered = head; } - if ((allocOffset + allocSize) < (range->offset + range->size)) // todo - create only if it's more than a couple of bytes? + uint32 tailBytes = (range->offset + range->size) - (allocOffset + allocSize); + if (tailBytes > 0) { // tail -> create free range - allocRange_t* tail = new allocRange_t((allocOffset + allocSize), range->chunkIndex, (range->offset + range->size) - (allocOffset + allocSize), true); + cemu_assert_debug(tailBytes >= TMinimumAlignment); + AllocRange* tail = m_allocEntriesPool.allocObj((allocOffset + allocSize), range->chunkIndex, tailBytes, true); trackFreeRange(tail); if (range->nextOrdered) range->nextOrdered->prevOrdered = tail; @@ -149,36 +148,51 @@ private: CHAddr _alloc(uint32 size, uint32 alignment) { + cemu_assert_debug(size <= (0x7FFFFFFFu-TMinimumAlignment)); + // make sure size is not zero and align it + if(size == 0) [[unlikely]] + size = TMinimumAlignment; + else + size = (size + (TMinimumAlignment - 1)) & ~(TMinimumAlignment - 1); // find smallest bucket to scan uint32 alignmentM1 = alignment - 1; uint32 bucketIndex = ulog2(size); - while (bucketIndex < 32) + // check if the bucket is available + if( !(m_bucketUseMask & (1u << bucketIndex)) ) { - allocRange_t* range = bucketFreeRange[bucketIndex]; + // skip to next non-empty bucket + uint32 nextIndex = BSF(m_bucketUseMask>>bucketIndex); + bucketIndex += nextIndex; + } + while (bucketIndex < 31) + { + AllocRange* range = m_bucketFreeRange[bucketIndex]; while (range) { if (range->size >= size) { // verify if aligned allocation fits uint32 alignedOffset = (range->offset + alignmentM1) & ~alignmentM1; - uint32 alignmentLoss = alignedOffset - range->offset; - if (alignmentLoss < range->size && (range->size - alignmentLoss) >= size) + uint32 endOffset = alignedOffset + size; + if((range->offset+range->size) >= endOffset) { _allocFrom(range, bucketIndex, alignedOffset, size); - list_chunks[range->chunkIndex]->map_allocatedRange.emplace(alignedOffset, range); - numAllocatedBytes += size; - return CHAddr(alignedOffset, range->chunkIndex); + m_numAllocatedBytes += size; + return CHAddr(alignedOffset, range->chunkIndex, range); } } range = range->nextFree; } - bucketIndex++; // try higher bucket + // check next non-empty bucket or skip to end + bucketIndex++; + uint32 emptyBuckets = BSF(m_bucketUseMask>>bucketIndex); + bucketIndex += emptyBuckets; } - if(allocationLimitReached) + if(m_allocationLimitReached) return CHAddr(0xFFFFFFFF, 0xFFFFFFFF); if (!allocateChunk(size)) { - allocationLimitReached = true; + m_allocationLimitReached = true; return CHAddr(0xFFFFFFFF, 0xFFFFFFFF); } return _alloc(size, alignment); @@ -186,24 +200,16 @@ private: void _free(CHAddr addr) { - auto it = list_chunks[addr.chunkIndex]->map_allocatedRange.find(addr.offset); - if (it == list_chunks[addr.chunkIndex]->map_allocatedRange.end()) + if(!addr.internal) { cemuLog_log(LogType::Force, "Internal heap error. {:08x} {:08x}", addr.chunkIndex, addr.offset); - cemuLog_log(LogType::Force, "Debug info:"); - for (auto& rangeItr : list_chunks[addr.chunkIndex]->map_allocatedRange) - { - cemuLog_log(LogType::Force, "{:08x} {:08x}", rangeItr.second->offset, rangeItr.second->size); - } return; } - - allocRange_t* range = it->second; - numAllocatedBytes -= it->second->size; - list_chunks[range->chunkIndex]->map_allocatedRange.erase(it); + AllocRange* range = (AllocRange*)addr.internal; + m_numAllocatedBytes -= range->size; // try merge left or right - allocRange_t* prevRange = range->prevOrdered; - allocRange_t* nextRange = range->nextOrdered; + AllocRange* prevRange = range->prevOrdered; + AllocRange* nextRange = range->nextOrdered; if (prevRange && prevRange->isFree) { if (nextRange && nextRange->isFree) @@ -216,8 +222,8 @@ private: forgetFreeRange(prevRange, ulog2(prevRange->size)); prevRange->size = newSize; trackFreeRange(prevRange); - delete range; - delete nextRange; + m_allocEntriesPool.freeObj(range); + m_allocEntriesPool.freeObj(nextRange); } else { @@ -228,7 +234,7 @@ private: forgetFreeRange(prevRange, ulog2(prevRange->size)); prevRange->size = newSize; trackFreeRange(prevRange); - delete range; + m_allocEntriesPool.freeObj(range); } } else if (nextRange && nextRange->isFree) @@ -242,7 +248,7 @@ private: range->prevOrdered->nextOrdered = nextRange; nextRange->prevOrdered = range->prevOrdered; trackFreeRange(nextRange); - delete range; + m_allocEntriesPool.freeObj(range); } else { @@ -265,7 +271,7 @@ private: for (uint32 i = 0; i < 32; i++) { - allocRange_t* ar = bucketFreeRange[i]; + AllocRange* ar = m_bucketFreeRange[i]; while (ar) { availableRange_t dbgRange; @@ -278,7 +284,7 @@ private: if (itr.chunkIndex != dbgRange.chunkIndex) continue; if (itr.offset < (dbgRange.offset + dbgRange.size) && (itr.offset + itr.size) >(dbgRange.offset)) - assert_dbg(); + cemu_assert_error(); } availRanges.emplace_back(dbgRange); @@ -290,14 +296,16 @@ private: } private: - std::vector<chunk_t*> list_chunks; - allocRange_t* bucketFreeRange[32]{}; - bool allocationLimitReached = false; + std::vector<Chunk> m_chunks; + uint32 m_bucketUseMask{0x80000000}; // bitmask indicating non-empty buckets. MSB always set to provide an upper bound for BSF instruction + AllocRange* m_bucketFreeRange[32]{}; // we are only using 31 entries since the MSB is reserved (thus chunks equal or larger than 2^31 are not allowed) + bool m_allocationLimitReached = false; + MemoryPool<AllocRange> m_allocEntriesPool{64}; public: // statistics - uint32 numHeapBytes{}; // total size of the heap - uint32 numAllocatedBytes{}; + uint32 m_numHeapBytes{}; // total size of the heap + uint32 m_numAllocatedBytes{}; }; class VGenericHeap @@ -633,7 +641,7 @@ public: uint32 getCurrentBlockOffset() const { return m_currentBlockOffset; } uint8* getCurrentBlockPtr() const { return m_currentBlockPtr; } - + private: void allocateAdditionalChunk() { From 0a5908502147c9d35189bdbfe38ba6d5bebd0f76 Mon Sep 17 00:00:00 2001 From: Joshua de Reeper <joshua@dereeper.co.nz> Date: Sun, 12 Jan 2025 13:33:24 +0000 Subject: [PATCH 228/299] nsyshid: Make Libusb the Windows backend (#1471) --- CMakeLists.txt | 17 - src/Cafe/CMakeLists.txt | 21 +- .../OS/libs/nsyshid/AttachDefaultBackends.cpp | 23 - src/Cafe/OS/libs/nsyshid/Backend.h | 36 +- src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp | 186 +++++--- src/Cafe/OS/libs/nsyshid/BackendLibusb.h | 25 +- .../OS/libs/nsyshid/BackendWindowsHID.cpp | 444 ------------------ src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h | 66 --- src/Cafe/OS/libs/nsyshid/Dimensions.cpp | 9 +- src/Cafe/OS/libs/nsyshid/Dimensions.h | 6 +- src/Cafe/OS/libs/nsyshid/Infinity.cpp | 11 +- src/Cafe/OS/libs/nsyshid/Infinity.h | 8 +- src/Cafe/OS/libs/nsyshid/Skylander.cpp | 21 +- src/Cafe/OS/libs/nsyshid/Skylander.h | 8 +- src/Cafe/OS/libs/nsyshid/nsyshid.cpp | 304 ++++++++---- 15 files changed, 412 insertions(+), 773 deletions(-) delete mode 100644 src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp delete mode 100644 src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2352389e..560728f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -124,23 +124,6 @@ if (WIN32) endif() option(ENABLE_CUBEB "Enabled cubeb backend" ON) -# usb hid backends -if (WIN32) - option(ENABLE_NSYSHID_WINDOWS_HID "Enables the native Windows HID backend for nsyshid" ON) -endif () -# libusb and windows hid backends shouldn't be active at the same time; otherwise we'd see all devices twice! -if (NOT ENABLE_NSYSHID_WINDOWS_HID) - option(ENABLE_NSYSHID_LIBUSB "Enables the libusb backend for nsyshid" ON) -else () - set(ENABLE_NSYSHID_LIBUSB OFF CACHE BOOL "" FORCE) -endif () -if (ENABLE_NSYSHID_WINDOWS_HID) - add_compile_definitions(NSYSHID_ENABLE_BACKEND_WINDOWS_HID) -endif () -if (ENABLE_NSYSHID_LIBUSB) - add_compile_definitions(NSYSHID_ENABLE_BACKEND_LIBUSB) -endif () - option(ENABLE_WXWIDGETS "Build with wxWidgets UI (Currently required)" ON) set(THREADS_PREFER_PTHREAD_FLAG true) diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index 76dba007..d51d58d5 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -463,8 +463,6 @@ add_library(CemuCafe OS/libs/nsyshid/BackendEmulated.h OS/libs/nsyshid/BackendLibusb.cpp OS/libs/nsyshid/BackendLibusb.h - OS/libs/nsyshid/BackendWindowsHID.cpp - OS/libs/nsyshid/BackendWindowsHID.h OS/libs/nsyshid/Dimensions.cpp OS/libs/nsyshid/Dimensions.h OS/libs/nsyshid/Infinity.cpp @@ -569,15 +567,16 @@ if (ENABLE_WAYLAND) target_link_libraries(CemuCafe PUBLIC Wayland::Client) endif() -if (ENABLE_NSYSHID_LIBUSB) - if (ENABLE_VCPKG) - find_package(PkgConfig REQUIRED) - pkg_check_modules(libusb REQUIRED IMPORTED_TARGET libusb-1.0) - target_link_libraries(CemuCafe PRIVATE PkgConfig::libusb) - else () - find_package(libusb MODULE REQUIRED) - target_link_libraries(CemuCafe PRIVATE libusb::libusb) - endif () +if (ENABLE_VCPKG) + if(WIN32) + set(PKG_CONFIG_EXECUTABLE "${VCPKG_INSTALLED_DIR}/x64-windows/tools/pkgconf/pkgconf.exe") + endif() + find_package(PkgConfig REQUIRED) + pkg_check_modules(libusb REQUIRED IMPORTED_TARGET libusb-1.0) + target_link_libraries(CemuCafe PRIVATE PkgConfig::libusb) +else () + find_package(libusb MODULE REQUIRED) + target_link_libraries(CemuCafe PRIVATE libusb::libusb) endif () if (ENABLE_WXWIDGETS) diff --git a/src/Cafe/OS/libs/nsyshid/AttachDefaultBackends.cpp b/src/Cafe/OS/libs/nsyshid/AttachDefaultBackends.cpp index fc8e496c..67eb0240 100644 --- a/src/Cafe/OS/libs/nsyshid/AttachDefaultBackends.cpp +++ b/src/Cafe/OS/libs/nsyshid/AttachDefaultBackends.cpp @@ -1,24 +1,12 @@ #include "nsyshid.h" #include "Backend.h" #include "BackendEmulated.h" - -#if NSYSHID_ENABLE_BACKEND_LIBUSB - #include "BackendLibusb.h" -#endif - -#if NSYSHID_ENABLE_BACKEND_WINDOWS_HID - -#include "BackendWindowsHID.h" - -#endif - namespace nsyshid::backend { void AttachDefaultBackends() { -#if NSYSHID_ENABLE_BACKEND_LIBUSB // add libusb backend { auto backendLibusb = std::make_shared<backend::libusb::BackendLibusb>(); @@ -27,17 +15,6 @@ namespace nsyshid::backend AttachBackend(backendLibusb); } } -#endif // NSYSHID_ENABLE_BACKEND_LIBUSB -#if NSYSHID_ENABLE_BACKEND_WINDOWS_HID - // add windows hid backend - { - auto backendWindowsHID = std::make_shared<backend::windows::BackendWindowsHID>(); - if (backendWindowsHID->IsInitialisedOk()) - { - AttachBackend(backendWindowsHID); - } - } -#endif // NSYSHID_ENABLE_BACKEND_WINDOWS_HID // add emulated backend { auto backendEmulated = std::make_shared<backend::emulated::BackendEmulated>(); diff --git a/src/Cafe/OS/libs/nsyshid/Backend.h b/src/Cafe/OS/libs/nsyshid/Backend.h index 12362773..67dad4fe 100644 --- a/src/Cafe/OS/libs/nsyshid/Backend.h +++ b/src/Cafe/OS/libs/nsyshid/Backend.h @@ -1,5 +1,4 @@ -#ifndef CEMU_NSYSHID_BACKEND_H -#define CEMU_NSYSHID_BACKEND_H +#pragma once #include <list> #include <memory> @@ -26,9 +25,9 @@ namespace nsyshid struct TransferCommand { uint8* data; - sint32 length; + uint32 length; - TransferCommand(uint8* data, sint32 length) + TransferCommand(uint8* data, uint32 length) : data(data), length(length) { } @@ -39,7 +38,7 @@ namespace nsyshid { sint32 bytesRead; - ReadMessage(uint8* data, sint32 length, sint32 bytesRead) + ReadMessage(uint8* data, uint32 length, sint32 bytesRead) : bytesRead(bytesRead), TransferCommand(data, length) { } @@ -50,7 +49,7 @@ namespace nsyshid { sint32 bytesWritten; - WriteMessage(uint8* data, sint32 length, sint32 bytesWritten) + WriteMessage(uint8* data, uint32 length, sint32 bytesWritten) : bytesWritten(bytesWritten), TransferCommand(data, length) { } @@ -59,14 +58,11 @@ namespace nsyshid struct ReportMessage final : TransferCommand { - uint8* reportData; - sint32 length; - uint8* originalData; - sint32 originalLength; + uint8 reportType; + uint8 reportId; - ReportMessage(uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength) - : reportData(reportData), length(length), originalData(originalData), - originalLength(originalLength), TransferCommand(reportData, length) + ReportMessage(uint8 reportType, uint8 reportId, uint8* data, uint32 length) + : reportType(reportType), reportId(reportId), TransferCommand(data, length) { } using TransferCommand::TransferCommand; @@ -77,7 +73,8 @@ namespace nsyshid static_assert(offsetof(HID_t, ifIndex) == 0xC, ""); static_assert(offsetof(HID_t, protocol) == 0xE, ""); - class Device { + class Device + { public: Device() = delete; @@ -131,16 +128,21 @@ namespace nsyshid virtual bool GetDescriptor(uint8 descType, uint8 descIndex, - uint8 lang, + uint16 lang, uint8* output, uint32 outputMaxLength) = 0; + virtual bool SetIdle(uint8 ifIndex, + uint8 reportId, + uint8 duration) = 0; + virtual bool SetProtocol(uint8 ifIndex, uint8 protocol) = 0; virtual bool SetReport(ReportMessage* message) = 0; }; - class Backend { + class Backend + { public: Backend(); @@ -188,5 +190,3 @@ namespace nsyshid void AttachDefaultBackends(); } } // namespace nsyshid - -#endif // CEMU_NSYSHID_BACKEND_H diff --git a/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp b/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp index ab355136..a7deef90 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp +++ b/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp @@ -1,7 +1,5 @@ #include "BackendLibusb.h" -#if NSYSHID_ENABLE_BACKEND_LIBUSB - namespace nsyshid::backend::libusb { BackendLibusb::BackendLibusb() @@ -16,7 +14,7 @@ namespace nsyshid::backend::libusb { m_ctx = nullptr; cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb: failed to initialize libusb, return code: {}", - m_initReturnCode); + m_initReturnCode); return; } @@ -35,8 +33,8 @@ namespace nsyshid::backend::libusb if (ret != LIBUSB_SUCCESS) { cemuLog_logDebug(LogType::Force, - "nsyshid::BackendLibusb: failed to register hotplug callback with return code {}", - ret); + "nsyshid::BackendLibusb: failed to register hotplug callback with return code {}", + ret); } else { @@ -53,8 +51,8 @@ namespace nsyshid::backend::libusb if (ret != 0) { cemuLog_logDebug(LogType::Force, - "nsyshid::BackendLibusb: hotplug thread: error handling events: {}", - ret); + "nsyshid::BackendLibusb: hotplug thread: error handling events: {}", + ret); std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } } @@ -139,8 +137,8 @@ namespace nsyshid::backend::libusb case LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED: { cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb::OnHotplug(): device arrived: {:04x}:{:04x}", - desc.idVendor, - desc.idProduct); + desc.idVendor, + desc.idProduct); auto device = CheckAndCreateDevice(dev); if (device != nullptr) { @@ -167,8 +165,8 @@ namespace nsyshid::backend::libusb case LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT: { cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb::OnHotplug(): device left: {:04x}:{:04x}", - desc.idVendor, - desc.idProduct); + desc.idVendor, + desc.idProduct); auto device = FindLibusbDevice(dev); if (device != nullptr) { @@ -204,7 +202,7 @@ namespace nsyshid::backend::libusb if (ret < 0) { cemuLog_logDebug(LogType::Force, - "nsyshid::BackendLibusb::FindLibusbDevice(): failed to get device descriptor"); + "nsyshid::BackendLibusb::FindLibusbDevice(): failed to get device descriptor"); return nullptr; } uint8 busNumber = libusb_get_bus_number(dev); @@ -269,7 +267,7 @@ namespace nsyshid::backend::libusb if (desc.idVendor == 0x0e6f && desc.idProduct == 0x0241) { cemuLog_logDebug(LogType::Force, - "nsyshid::BackendLibusb::CheckAndCreateDevice(): lego dimensions portal detected"); + "nsyshid::BackendLibusb::CheckAndCreateDevice(): lego dimensions portal detected"); } auto device = std::make_shared<DeviceLibusb>(m_ctx, desc.idVendor, @@ -446,12 +444,13 @@ namespace nsyshid::backend::libusb } this->m_handleInUseCounter = 0; } + + int ret = ClaimAllInterfaces(0); + + if (ret != 0) { - int ret = ClaimAllInterfaces(0); - if (ret != 0) - { - cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::open(): cannot claim interface"); - } + cemuLog_log(LogType::Force, "nsyshid::DeviceLibusb::open(): cannot claim interface for config 0"); + return false; } } @@ -475,7 +474,7 @@ namespace nsyshid::backend::libusb { m_handleInUseCounterDecremented.wait(lock); } - libusb_release_interface(handle, 0); + ReleaseAllInterfacesForCurrentConfig(); libusb_close(handle); m_handleInUseCounter = -1; m_handleInUseCounterDecremented.notify_all(); @@ -493,21 +492,26 @@ namespace nsyshid::backend::libusb if (!handleLock->IsValid()) { cemuLog_logDebug(LogType::Force, - "nsyshid::DeviceLibusb::read(): cannot read from a non-opened device\n"); + "nsyshid::DeviceLibusb::read(): cannot read from a non-opened device\n"); return ReadResult::Error; } + for (int i = 0; i < m_config_descriptors.size(); i++) + { + ClaimAllInterfaces(i); + } + const unsigned int timeout = 50; int actualLength = 0; int ret = 0; do { - ret = libusb_bulk_transfer(handleLock->GetHandle(), - this->m_libusbEndpointIn, - message->data, - message->length, - &actualLength, - timeout); + ret = libusb_interrupt_transfer(handleLock->GetHandle(), + this->m_libusbEndpointIn, + message->data, + message->length, + &actualLength, + timeout); } while (ret == LIBUSB_ERROR_TIMEOUT && actualLength == 0 && IsOpened()); @@ -521,8 +525,8 @@ namespace nsyshid::backend::libusb return ReadResult::Success; } cemuLog_logDebug(LogType::Force, - "nsyshid::DeviceLibusb::read(): failed with error code: {}", - ret); + "nsyshid::DeviceLibusb::read(): failed at endpoint 0x{:02x} with error message: {}", this->m_libusbEndpointIn, + libusb_error_name(ret)); return ReadResult::Error; } @@ -532,18 +536,23 @@ namespace nsyshid::backend::libusb if (!handleLock->IsValid()) { cemuLog_logDebug(LogType::Force, - "nsyshid::DeviceLibusb::write(): cannot write to a non-opened device\n"); + "nsyshid::DeviceLibusb::write(): cannot write to a non-opened device\n"); return WriteResult::Error; } + for (int i = 0; i < m_config_descriptors.size(); i++) + { + ClaimAllInterfaces(i); + } + message->bytesWritten = 0; int actualLength = 0; - int ret = libusb_bulk_transfer(handleLock->GetHandle(), - this->m_libusbEndpointOut, - message->data, - message->length, - &actualLength, - 0); + int ret = libusb_interrupt_transfer(handleLock->GetHandle(), + this->m_libusbEndpointOut, + message->data, + message->length, + &actualLength, + 0); if (ret == 0) { @@ -556,14 +565,14 @@ namespace nsyshid::backend::libusb return WriteResult::Success; } cemuLog_logDebug(LogType::Force, - "nsyshid::DeviceLibusb::write(): failed with error code: {}", - ret); + "nsyshid::DeviceLibusb::write(): failed with error code: {}", + ret); return WriteResult::Error; } bool DeviceLibusb::GetDescriptor(uint8 descType, uint8 descIndex, - uint8 lang, + uint16 lang, uint8* output, uint32 outputMaxLength) { @@ -579,7 +588,6 @@ namespace nsyshid::backend::libusb struct libusb_config_descriptor* conf = nullptr; libusb_device* dev = libusb_get_device(handleLock->GetHandle()); int ret = libusb_get_active_config_descriptor(dev, &conf); - if (ret == 0) { std::vector<uint8> configurationDescriptor(conf->wTotalLength); @@ -656,7 +664,6 @@ namespace nsyshid::backend::libusb extraReadPointer += bLength; } } - for (int endpointIndex = 0; endpointIndex < altsetting.bNumEndpoints; endpointIndex++) { // endpoint descriptor @@ -681,24 +688,61 @@ namespace nsyshid::backend::libusb uint32 bytesWritten = currentWritePtr - &configurationDescriptor[0]; libusb_free_config_descriptor(conf); cemu_assert_debug(bytesWritten <= conf->wTotalLength); - memcpy(output, &configurationDescriptor[0], std::min<uint32>(outputMaxLength, bytesWritten)); return true; } - else - { - cemuLog_logDebug(LogType::Force, - "nsyshid::DeviceLibusb::getDescriptor(): failed to get config descriptor with error code: {}", - ret); - return false; - } } else { - cemu_assert_unimplemented(); + uint16 wValue = uint16(descType) << 8 | uint16(descIndex); + // HID Get_Descriptor requests are handled via libusb_control_transfer + int ret = libusb_control_transfer(handleLock->GetHandle(), + LIBUSB_REQUEST_TYPE_STANDARD | LIBUSB_ENDPOINT_IN, + LIBUSB_REQUEST_GET_DESCRIPTOR, + wValue, + lang, + output, + outputMaxLength, + 0); + if (ret != outputMaxLength) + { + cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::GetDescriptor(): Control Transfer Failed: {}", libusb_error_name(ret)); + return false; + } } - return false; + return true; + } + + bool DeviceLibusb::SetIdle(uint8 ifIndex, + uint8 reportId, + uint8 duration) + { + auto handleLock = AquireHandleLock(); + if (!handleLock->IsValid()) + { + cemuLog_log(LogType::Force, "nsyshid::DeviceLibusb::SetIdle(): device is not opened"); + return false; + } + + uint16 wValue = uint16(duration) << 8 | uint16(reportId); + + // HID Set_Idle requests are handled via libusb_control_transfer + int ret = libusb_control_transfer(handleLock->GetHandle(), + LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT, + HID_CLASS_SET_IDLE, // Defined in HID Class Specific Requests (7.2) + wValue, + ifIndex, + nullptr, + 0, + 0); + + if (ret != 0) + { + cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetIdle(): Control Transfer Failed: {}", libusb_error_name(ret)); + return false; + } + return true; } template<typename Configs, typename Function> @@ -767,18 +811,22 @@ namespace nsyshid::backend::libusb cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetProtocol(): device is not opened"); return false; } - if (m_interfaceIndex != ifIndex) - m_interfaceIndex = ifIndex; - ReleaseAllInterfacesForCurrentConfig(); - int ret = libusb_set_configuration(AquireHandleLock()->GetHandle(), protocol); - if (ret == LIBUSB_SUCCESS) - ret = ClaimAllInterfaces(protocol); + int ret = libusb_control_transfer(handleLock->GetHandle(), + LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT, + HID_CLASS_SET_PROTOCOL, // Defined in HID Class Specific Requests (7.2) + protocol, + ifIndex, + nullptr, + 0, + 0); - if (ret == LIBUSB_SUCCESS) - return true; - - return false; + if (ret != 0) + { + cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetProtocol(): Control Transfer Failed: {}", libusb_error_name(ret)); + return false; + } + return true; } bool DeviceLibusb::SetReport(ReportMessage* message) @@ -790,18 +838,20 @@ namespace nsyshid::backend::libusb return false; } + uint16 wValue = uint16(message->reportType) << 8 | uint16(message->reportId); + int ret = libusb_control_transfer(handleLock->GetHandle(), LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT, - LIBUSB_REQUEST_SET_CONFIGURATION, - 512, - 0, - message->originalData, - message->originalLength, + HID_CLASS_SET_REPORT, // Defined in HID Class Specific Requests (7.2) + wValue, + m_interfaceIndex, + message->data, + uint16(message->length & 0xFFFF), 0); - if (ret != message->originalLength) + if (ret != message->length) { - cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetReport(): Control Transfer Failed: {}", libusb_error_name(ret)); + cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetReport(): Control Transfer Failed at interface {} : {}", m_interfaceIndex, libusb_error_name(ret)); return false; } return true; @@ -854,5 +904,3 @@ namespace nsyshid::backend::libusb return m_handle; } } // namespace nsyshid::backend::libusb - -#endif // NSYSHID_ENABLE_BACKEND_LIBUSB diff --git a/src/Cafe/OS/libs/nsyshid/BackendLibusb.h b/src/Cafe/OS/libs/nsyshid/BackendLibusb.h index a7b23769..6b2d8e1a 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendLibusb.h +++ b/src/Cafe/OS/libs/nsyshid/BackendLibusb.h @@ -1,15 +1,20 @@ -#ifndef CEMU_NSYSHID_BACKEND_LIBUSB_H -#define CEMU_NSYSHID_BACKEND_LIBUSB_H - #include "nsyshid.h" -#if NSYSHID_ENABLE_BACKEND_LIBUSB - #include <libusb-1.0/libusb.h> #include "Backend.h" namespace nsyshid::backend::libusb { + enum : uint8 + { + HID_CLASS_GET_REPORT = 0x01, + HID_CLASS_GET_IDLE = 0x02, + HID_CLASS_GET_PROTOCOL = 0x03, + HID_CLASS_SET_REPORT = 0x09, + HID_CLASS_SET_IDLE = 0x0A, + HID_CLASS_SET_PROTOCOL = 0x0B + }; + class BackendLibusb : public nsyshid::Backend { public: BackendLibusb(); @@ -75,10 +80,14 @@ namespace nsyshid::backend::libusb bool GetDescriptor(uint8 descType, uint8 descIndex, - uint8 lang, + uint16 lang, uint8* output, uint32 outputMaxLength) override; + bool SetIdle(uint8 ifIndex, + uint8 reportId, + uint8 duration) override; + bool SetProtocol(uint8 ifIndex, uint8 protocol) override; int ClaimAllInterfaces(uint8 config_num); @@ -134,7 +143,3 @@ namespace nsyshid::backend::libusb std::unique_ptr<HandleLock> AquireHandleLock(); }; } // namespace nsyshid::backend::libusb - -#endif // NSYSHID_ENABLE_BACKEND_LIBUSB - -#endif // CEMU_NSYSHID_BACKEND_LIBUSB_H diff --git a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp deleted file mode 100644 index 267111b2..00000000 --- a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp +++ /dev/null @@ -1,444 +0,0 @@ -#include "BackendWindowsHID.h" - -#if NSYSHID_ENABLE_BACKEND_WINDOWS_HID - -#include <setupapi.h> -#include <initguid.h> -#include <hidsdi.h> - -#pragma comment(lib, "Setupapi.lib") -#pragma comment(lib, "hid.lib") - -DEFINE_GUID(GUID_DEVINTERFACE_HID, - 0x4D1E55B2L, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30); - -namespace nsyshid::backend::windows -{ - BackendWindowsHID::BackendWindowsHID() - { - } - - void BackendWindowsHID::AttachVisibleDevices() - { - // add all currently connected devices - HDEVINFO hDevInfo; - SP_DEVICE_INTERFACE_DATA DevIntfData; - PSP_DEVICE_INTERFACE_DETAIL_DATA DevIntfDetailData; - SP_DEVINFO_DATA DevData; - - DWORD dwSize, dwMemberIdx; - - hDevInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_HID, NULL, 0, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); - - if (hDevInfo != INVALID_HANDLE_VALUE) - { - DevIntfData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); - dwMemberIdx = 0; - - SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVINTERFACE_HID, - dwMemberIdx, &DevIntfData); - - while (GetLastError() != ERROR_NO_MORE_ITEMS) - { - DevData.cbSize = sizeof(DevData); - SetupDiGetDeviceInterfaceDetail( - hDevInfo, &DevIntfData, NULL, 0, &dwSize, NULL); - - DevIntfDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, - dwSize); - DevIntfDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); - - if (SetupDiGetDeviceInterfaceDetail(hDevInfo, &DevIntfData, - DevIntfDetailData, dwSize, &dwSize, &DevData)) - { - HANDLE hHIDDevice = OpenDevice(DevIntfDetailData->DevicePath); - if (hHIDDevice != INVALID_HANDLE_VALUE) - { - auto device = CheckAndCreateDevice(DevIntfDetailData->DevicePath, hHIDDevice); - if (device != nullptr) - { - if (IsDeviceWhitelisted(device->m_vendorId, device->m_productId)) - { - if (!AttachDevice(device)) - { - cemuLog_log(LogType::Force, - "nsyshid::BackendWindowsHID: failed to attach device: {:04x}:{:04x}", - device->m_vendorId, - device->m_productId); - } - } - } - CloseHandle(hHIDDevice); - } - } - HeapFree(GetProcessHeap(), 0, DevIntfDetailData); - // next - SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVINTERFACE_HID, ++dwMemberIdx, &DevIntfData); - } - SetupDiDestroyDeviceInfoList(hDevInfo); - } - } - - BackendWindowsHID::~BackendWindowsHID() - { - } - - bool BackendWindowsHID::IsInitialisedOk() - { - return true; - } - - std::shared_ptr<Device> BackendWindowsHID::CheckAndCreateDevice(wchar_t* devicePath, HANDLE hDevice) - { - HIDD_ATTRIBUTES hidAttr; - hidAttr.Size = sizeof(HIDD_ATTRIBUTES); - if (HidD_GetAttributes(hDevice, &hidAttr) == FALSE) - return nullptr; - - auto device = std::make_shared<DeviceWindowsHID>(hidAttr.VendorID, - hidAttr.ProductID, - 1, - 2, - 0, - _wcsdup(devicePath)); - // get additional device info - sint32 maxPacketInputLength = -1; - sint32 maxPacketOutputLength = -1; - PHIDP_PREPARSED_DATA ppData = nullptr; - if (HidD_GetPreparsedData(hDevice, &ppData)) - { - HIDP_CAPS caps; - if (HidP_GetCaps(ppData, &caps) == HIDP_STATUS_SUCCESS) - { - // length includes the report id byte - maxPacketInputLength = caps.InputReportByteLength - 1; - maxPacketOutputLength = caps.OutputReportByteLength - 1; - } - HidD_FreePreparsedData(ppData); - } - if (maxPacketInputLength <= 0 || maxPacketInputLength >= 0xF000) - { - cemuLog_logDebug(LogType::Force, "HID: Input packet length not available or out of range (length = {})", maxPacketInputLength); - maxPacketInputLength = 0x20; - } - if (maxPacketOutputLength <= 0 || maxPacketOutputLength >= 0xF000) - { - cemuLog_logDebug(LogType::Force, "HID: Output packet length not available or out of range (length = {})", maxPacketOutputLength); - maxPacketOutputLength = 0x20; - } - - device->m_maxPacketSizeRX = maxPacketInputLength; - device->m_maxPacketSizeTX = maxPacketOutputLength; - - return device; - } - - DeviceWindowsHID::DeviceWindowsHID(uint16 vendorId, - uint16 productId, - uint8 interfaceIndex, - uint8 interfaceSubClass, - uint8 protocol, - wchar_t* devicePath) - : Device(vendorId, - productId, - interfaceIndex, - interfaceSubClass, - protocol), - m_devicePath(devicePath), - m_hFile(INVALID_HANDLE_VALUE) - { - } - - DeviceWindowsHID::~DeviceWindowsHID() - { - if (m_hFile != INVALID_HANDLE_VALUE) - { - CloseHandle(m_hFile); - m_hFile = INVALID_HANDLE_VALUE; - } - } - - bool DeviceWindowsHID::Open() - { - if (IsOpened()) - { - return true; - } - m_hFile = OpenDevice(m_devicePath); - if (m_hFile == INVALID_HANDLE_VALUE) - { - return false; - } - HidD_SetNumInputBuffers(m_hFile, 2); // don't cache too many reports - return true; - } - - void DeviceWindowsHID::Close() - { - if (m_hFile != INVALID_HANDLE_VALUE) - { - CloseHandle(m_hFile); - m_hFile = INVALID_HANDLE_VALUE; - } - } - - bool DeviceWindowsHID::IsOpened() - { - return m_hFile != INVALID_HANDLE_VALUE; - } - - Device::ReadResult DeviceWindowsHID::Read(ReadMessage* message) - { - message->bytesRead = 0; - DWORD bt; - OVERLAPPED ovlp = {0}; - ovlp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - - uint8* tempBuffer = (uint8*)malloc(message->length + 1); - sint32 transferLength = 0; // minus report byte - - _debugPrintHex("HID_READ_BEFORE", message->data, message->length); - - cemuLog_logDebug(LogType::Force, "HidRead Begin (Length 0x{:08x})", message->length); - BOOL readResult = ReadFile(this->m_hFile, tempBuffer, message->length + 1, &bt, &ovlp); - if (readResult != FALSE) - { - // sometimes we get the result immediately - if (bt == 0) - transferLength = 0; - else - transferLength = bt - 1; - cemuLog_logDebug(LogType::Force, "HidRead Result received immediately (error 0x{:08x}) Length 0x{:08x}", - GetLastError(), transferLength); - } - else - { - // wait for result - cemuLog_logDebug(LogType::Force, "HidRead WaitForResult (error 0x{:08x})", GetLastError()); - // async hid read is never supposed to return unless there is a response? Lego Dimensions stops HIDRead calls as soon as one of them fails with a non-zero error (which includes time out) - DWORD r = WaitForSingleObject(ovlp.hEvent, 2000 * 100); - if (r == WAIT_TIMEOUT) - { - cemuLog_logDebug(LogType::Force, "HidRead internal timeout (error 0x{:08x})", GetLastError()); - // return -108 in case of timeout - free(tempBuffer); - CloseHandle(ovlp.hEvent); - return ReadResult::ErrorTimeout; - } - - cemuLog_logDebug(LogType::Force, "HidRead WaitHalfComplete"); - GetOverlappedResult(this->m_hFile, &ovlp, &bt, false); - if (bt == 0) - transferLength = 0; - else - transferLength = bt - 1; - cemuLog_logDebug(LogType::Force, "HidRead WaitComplete Length: 0x{:08x}", transferLength); - } - sint32 returnCode = 0; - ReadResult result = ReadResult::Success; - if (bt != 0) - { - memcpy(message->data, tempBuffer + 1, transferLength); - sint32 hidReadLength = transferLength; - - char debugOutput[1024] = {0}; - for (sint32 i = 0; i < transferLength; i++) - { - sprintf(debugOutput + i * 3, "%02x ", tempBuffer[1 + i]); - } - cemuLog_logDebug(LogType::Force, "HIDRead data: {}", debugOutput); - - message->bytesRead = transferLength; - result = ReadResult::Success; - } - else - { - cemuLog_log(LogType::Force, "Failed HID read"); - result = ReadResult::Error; - } - free(tempBuffer); - CloseHandle(ovlp.hEvent); - return result; - } - - Device::WriteResult DeviceWindowsHID::Write(WriteMessage* message) - { - message->bytesWritten = 0; - DWORD bt; - OVERLAPPED ovlp = {0}; - ovlp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - - uint8* tempBuffer = (uint8*)malloc(message->length + 1); - memcpy(tempBuffer + 1, message->data, message->length); - tempBuffer[0] = 0; // report byte? - - cemuLog_logDebug(LogType::Force, "HidWrite Begin (Length 0x{:08x})", message->length); - BOOL writeResult = WriteFile(this->m_hFile, tempBuffer, message->length + 1, &bt, &ovlp); - if (writeResult != FALSE) - { - // sometimes we get the result immediately - cemuLog_logDebug(LogType::Force, "HidWrite Result received immediately (error 0x{:08x}) Length 0x{:08x}", - GetLastError()); - } - else - { - // wait for result - cemuLog_logDebug(LogType::Force, "HidWrite WaitForResult (error 0x{:08x})", GetLastError()); - // todo - check for error type - DWORD r = WaitForSingleObject(ovlp.hEvent, 2000); - if (r == WAIT_TIMEOUT) - { - cemuLog_logDebug(LogType::Force, "HidWrite internal timeout"); - // return -108 in case of timeout - free(tempBuffer); - CloseHandle(ovlp.hEvent); - return WriteResult::ErrorTimeout; - } - - cemuLog_logDebug(LogType::Force, "HidWrite WaitHalfComplete"); - GetOverlappedResult(this->m_hFile, &ovlp, &bt, false); - cemuLog_logDebug(LogType::Force, "HidWrite WaitComplete"); - } - - free(tempBuffer); - CloseHandle(ovlp.hEvent); - - if (bt != 0) - { - message->bytesWritten = message->length; - return WriteResult::Success; - } - return WriteResult::Error; - } - - bool DeviceWindowsHID::GetDescriptor(uint8 descType, - uint8 descIndex, - uint8 lang, - uint8* output, - uint32 outputMaxLength) - { - if (!IsOpened()) - { - cemuLog_logDebug(LogType::Force, "nsyshid::DeviceWindowsHID::getDescriptor(): device is not opened"); - return false; - } - if (descType == 0x02) - { - uint8 configurationDescriptor[0x29]; - - uint8* currentWritePtr; - - // configuration descriptor - currentWritePtr = configurationDescriptor + 0; - *(uint8*)(currentWritePtr + 0) = 9; // bLength - *(uint8*)(currentWritePtr + 1) = 2; // bDescriptorType - *(uint16be*)(currentWritePtr + 2) = 0x0029; // wTotalLength - *(uint8*)(currentWritePtr + 4) = 1; // bNumInterfaces - *(uint8*)(currentWritePtr + 5) = 1; // bConfigurationValue - *(uint8*)(currentWritePtr + 6) = 0; // iConfiguration - *(uint8*)(currentWritePtr + 7) = 0x80; // bmAttributes - *(uint8*)(currentWritePtr + 8) = 0xFA; // MaxPower - currentWritePtr = currentWritePtr + 9; - // configuration descriptor - *(uint8*)(currentWritePtr + 0) = 9; // bLength - *(uint8*)(currentWritePtr + 1) = 0x04; // bDescriptorType - *(uint8*)(currentWritePtr + 2) = 0; // bInterfaceNumber - *(uint8*)(currentWritePtr + 3) = 0; // bAlternateSetting - *(uint8*)(currentWritePtr + 4) = 2; // bNumEndpoints - *(uint8*)(currentWritePtr + 5) = 3; // bInterfaceClass - *(uint8*)(currentWritePtr + 6) = 0; // bInterfaceSubClass - *(uint8*)(currentWritePtr + 7) = 0; // bInterfaceProtocol - *(uint8*)(currentWritePtr + 8) = 0; // iInterface - currentWritePtr = currentWritePtr + 9; - // configuration descriptor - *(uint8*)(currentWritePtr + 0) = 9; // bLength - *(uint8*)(currentWritePtr + 1) = 0x21; // bDescriptorType - *(uint16be*)(currentWritePtr + 2) = 0x0111; // bcdHID - *(uint8*)(currentWritePtr + 4) = 0x00; // bCountryCode - *(uint8*)(currentWritePtr + 5) = 0x01; // bNumDescriptors - *(uint8*)(currentWritePtr + 6) = 0x22; // bDescriptorType - *(uint16be*)(currentWritePtr + 7) = 0x001D; // wDescriptorLength - currentWritePtr = currentWritePtr + 9; - // endpoint descriptor 1 - *(uint8*)(currentWritePtr + 0) = 7; // bLength - *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType - *(uint8*)(currentWritePtr + 2) = 0x81; // bEndpointAddress - *(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes - *(uint16be*)(currentWritePtr + 4) = - this->m_maxPacketSizeRX; // wMaxPacketSize - *(uint8*)(currentWritePtr + 6) = 0x01; // bInterval - currentWritePtr = currentWritePtr + 7; - // endpoint descriptor 2 - *(uint8*)(currentWritePtr + 0) = 7; // bLength - *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType - *(uint8*)(currentWritePtr + 2) = 0x02; // bEndpointAddress - *(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes - *(uint16be*)(currentWritePtr + 4) = - this->m_maxPacketSizeTX; // wMaxPacketSize - *(uint8*)(currentWritePtr + 6) = 0x01; // bInterval - currentWritePtr = currentWritePtr + 7; - - cemu_assert_debug((currentWritePtr - configurationDescriptor) == 0x29); - - memcpy(output, configurationDescriptor, - std::min<uint32>(outputMaxLength, sizeof(configurationDescriptor))); - return true; - } - else - { - cemu_assert_unimplemented(); - } - return false; - } - - bool DeviceWindowsHID::SetProtocol(uint8 ifIndex, uint8 protocol) - { - // ToDo: implement this - // pretend that everything is fine - return true; - } - - bool DeviceWindowsHID::SetReport(ReportMessage* message) - { - sint32 retryCount = 0; - while (true) - { - BOOL r = HidD_SetOutputReport(this->m_hFile, message->reportData, message->length); - if (r != FALSE) - break; - Sleep(20); // retry - retryCount++; - if (retryCount >= 50) - { - cemuLog_log(LogType::Force, "nsyshid::DeviceWindowsHID::SetReport(): HID SetReport failed"); - return false; - } - } - return true; - } - - HANDLE OpenDevice(wchar_t* devicePath) - { - return CreateFile(devicePath, - GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_READ | - FILE_SHARE_WRITE, - NULL, - OPEN_EXISTING, - FILE_FLAG_OVERLAPPED, - NULL); - } - - void _debugPrintHex(std::string prefix, uint8* data, size_t len) - { - char debugOutput[1024] = {0}; - len = std::min(len, (size_t)100); - for (sint32 i = 0; i < len; i++) - { - sprintf(debugOutput + i * 3, "%02x ", data[i]); - } - cemuLog_logDebug(LogType::Force, "[{}] Data: {}", prefix, debugOutput); - } -} // namespace nsyshid::backend::windows - -#endif // NSYSHID_ENABLE_BACKEND_WINDOWS_HID diff --git a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h deleted file mode 100644 index 9a8a78e9..00000000 --- a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef CEMU_NSYSHID_BACKEND_WINDOWS_HID_H -#define CEMU_NSYSHID_BACKEND_WINDOWS_HID_H - -#include "nsyshid.h" - -#if NSYSHID_ENABLE_BACKEND_WINDOWS_HID - -#include "Backend.h" - -namespace nsyshid::backend::windows -{ - class BackendWindowsHID : public nsyshid::Backend { - public: - BackendWindowsHID(); - - ~BackendWindowsHID(); - - bool IsInitialisedOk() override; - - protected: - void AttachVisibleDevices() override; - - private: - std::shared_ptr<Device> CheckAndCreateDevice(wchar_t* devicePath, HANDLE hDevice); - }; - - class DeviceWindowsHID : public nsyshid::Device { - public: - DeviceWindowsHID(uint16 vendorId, - uint16 productId, - uint8 interfaceIndex, - uint8 interfaceSubClass, - uint8 protocol, - wchar_t* devicePath); - - ~DeviceWindowsHID(); - - bool Open() override; - - void Close() override; - - bool IsOpened() override; - - ReadResult Read(ReadMessage* message) override; - - WriteResult Write(WriteMessage* message) override; - - bool GetDescriptor(uint8 descType, uint8 descIndex, uint8 lang, uint8* output, uint32 outputMaxLength) override; - - bool SetProtocol(uint8 ifIndex, uint8 protocol) override; - - bool SetReport(ReportMessage* message) override; - - private: - wchar_t* m_devicePath; - HANDLE m_hFile; - }; - - HANDLE OpenDevice(wchar_t* devicePath); - - void _debugPrintHex(std::string prefix, uint8* data, size_t len); -} // namespace nsyshid::backend::windows - -#endif // NSYSHID_ENABLE_BACKEND_WINDOWS_HID - -#endif // CEMU_NSYSHID_BACKEND_WINDOWS_HID_H diff --git a/src/Cafe/OS/libs/nsyshid/Dimensions.cpp b/src/Cafe/OS/libs/nsyshid/Dimensions.cpp index 8a2acc76..b23560f1 100644 --- a/src/Cafe/OS/libs/nsyshid/Dimensions.cpp +++ b/src/Cafe/OS/libs/nsyshid/Dimensions.cpp @@ -426,7 +426,7 @@ namespace nsyshid bool DimensionsToypadDevice::GetDescriptor(uint8 descType, uint8 descIndex, - uint8 lang, + uint16 lang, uint8* output, uint32 outputMaxLength) { @@ -489,6 +489,13 @@ namespace nsyshid return true; } + bool DimensionsToypadDevice::SetIdle(uint8 ifIndex, + uint8 reportId, + uint8 duration) + { + return true; + } + bool DimensionsToypadDevice::SetProtocol(uint8 ifIndex, uint8 protocol) { cemuLog_log(LogType::Force, "Toypad Protocol"); diff --git a/src/Cafe/OS/libs/nsyshid/Dimensions.h b/src/Cafe/OS/libs/nsyshid/Dimensions.h index d5a2a529..00ceff9e 100644 --- a/src/Cafe/OS/libs/nsyshid/Dimensions.h +++ b/src/Cafe/OS/libs/nsyshid/Dimensions.h @@ -25,10 +25,14 @@ namespace nsyshid bool GetDescriptor(uint8 descType, uint8 descIndex, - uint8 lang, + uint16 lang, uint8* output, uint32 outputMaxLength) override; + bool SetIdle(uint8 ifIndex, + uint8 reportId, + uint8 duration) override; + bool SetProtocol(uint8 ifIndex, uint8 protocol) override; bool SetReport(ReportMessage* message) override; diff --git a/src/Cafe/OS/libs/nsyshid/Infinity.cpp b/src/Cafe/OS/libs/nsyshid/Infinity.cpp index ac793109..94ef817e 100644 --- a/src/Cafe/OS/libs/nsyshid/Infinity.cpp +++ b/src/Cafe/OS/libs/nsyshid/Infinity.cpp @@ -387,7 +387,7 @@ namespace nsyshid bool InfinityBaseDevice::GetDescriptor(uint8 descType, uint8 descIndex, - uint8 lang, + uint16 lang, uint8* output, uint32 outputMaxLength) { @@ -450,6 +450,13 @@ namespace nsyshid return true; } + bool InfinityBaseDevice::SetIdle(uint8 ifIndex, + uint8 reportId, + uint8 duration) + { + return true; + } + bool InfinityBaseDevice::SetProtocol(uint8 ifIndex, uint8 protocol) { return true; @@ -492,7 +499,7 @@ namespace nsyshid return response; } - void InfinityUSB::SendCommand(uint8* buf, sint32 originalLength) + void InfinityUSB::SendCommand(uint8* buf, uint32 length) { const uint8 command = buf[2]; const uint8 sequence = buf[3]; diff --git a/src/Cafe/OS/libs/nsyshid/Infinity.h b/src/Cafe/OS/libs/nsyshid/Infinity.h index aa98fd15..81942abd 100644 --- a/src/Cafe/OS/libs/nsyshid/Infinity.h +++ b/src/Cafe/OS/libs/nsyshid/Infinity.h @@ -26,10 +26,14 @@ namespace nsyshid bool GetDescriptor(uint8 descType, uint8 descIndex, - uint8 lang, + uint16 lang, uint8* output, uint32 outputMaxLength) override; + bool SetIdle(uint8 ifIndex, + uint8 reportId, + uint8 duration) override; + bool SetProtocol(uint8 ifIndex, uint8 protocol) override; bool SetReport(ReportMessage* message) override; @@ -53,7 +57,7 @@ namespace nsyshid void Save(); }; - void SendCommand(uint8* buf, sint32 originalLength); + void SendCommand(uint8* buf, uint32 length); std::array<uint8, 32> GetStatus(); void GetBlankResponse(uint8 sequence, std::array<uint8, 32>& replyBuf); diff --git a/src/Cafe/OS/libs/nsyshid/Skylander.cpp b/src/Cafe/OS/libs/nsyshid/Skylander.cpp index 1b4515ef..9fab17b6 100644 --- a/src/Cafe/OS/libs/nsyshid/Skylander.cpp +++ b/src/Cafe/OS/libs/nsyshid/Skylander.cpp @@ -564,7 +564,7 @@ namespace nsyshid bool SkylanderPortalDevice::GetDescriptor(uint8 descType, uint8 descIndex, - uint8 lang, + uint16 lang, uint8* output, uint32 outputMaxLength) { @@ -583,7 +583,7 @@ namespace nsyshid *(uint8*)(currentWritePtr + 7) = 0x80; // bmAttributes *(uint8*)(currentWritePtr + 8) = 0xFA; // MaxPower currentWritePtr = currentWritePtr + 9; - // configuration descriptor + // interface descriptor *(uint8*)(currentWritePtr + 0) = 9; // bLength *(uint8*)(currentWritePtr + 1) = 0x04; // bDescriptorType *(uint8*)(currentWritePtr + 2) = 0; // bInterfaceNumber @@ -594,7 +594,7 @@ namespace nsyshid *(uint8*)(currentWritePtr + 7) = 0; // bInterfaceProtocol *(uint8*)(currentWritePtr + 8) = 0; // iInterface currentWritePtr = currentWritePtr + 9; - // configuration descriptor + // HID descriptor *(uint8*)(currentWritePtr + 0) = 9; // bLength *(uint8*)(currentWritePtr + 1) = 0x21; // bDescriptorType *(uint16be*)(currentWritePtr + 2) = 0x0111; // bcdHID @@ -608,7 +608,7 @@ namespace nsyshid *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType *(uint8*)(currentWritePtr + 2) = 0x81; // bEndpointAddress *(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes - *(uint16be*)(currentWritePtr + 4) = 0x40; // wMaxPacketSize + *(uint16be*)(currentWritePtr + 4) = 0x0040; // wMaxPacketSize *(uint8*)(currentWritePtr + 6) = 0x01; // bInterval currentWritePtr = currentWritePtr + 7; // endpoint descriptor 2 @@ -616,7 +616,7 @@ namespace nsyshid *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType *(uint8*)(currentWritePtr + 2) = 0x02; // bEndpointAddress *(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes - *(uint16be*)(currentWritePtr + 4) = 0x40; // wMaxPacketSize + *(uint16be*)(currentWritePtr + 4) = 0x0040; // wMaxPacketSize *(uint8*)(currentWritePtr + 6) = 0x01; // bInterval currentWritePtr = currentWritePtr + 7; @@ -627,6 +627,13 @@ namespace nsyshid return true; } + bool SkylanderPortalDevice::SetIdle(uint8 ifIndex, + uint8 reportId, + uint8 duration) + { + return true; + } + bool SkylanderPortalDevice::SetProtocol(uint8 ifIndex, uint8 protocol) { return true; @@ -634,12 +641,12 @@ namespace nsyshid bool SkylanderPortalDevice::SetReport(ReportMessage* message) { - g_skyportal.ControlTransfer(message->originalData, message->originalLength); + g_skyportal.ControlTransfer(message->data, message->length); std::this_thread::sleep_for(std::chrono::milliseconds(1)); return true; } - void SkylanderUSB::ControlTransfer(uint8* buf, sint32 originalLength) + void SkylanderUSB::ControlTransfer(uint8* buf, uint32 length) { std::array<uint8, 64> interruptResponse = {}; switch (buf[0]) diff --git a/src/Cafe/OS/libs/nsyshid/Skylander.h b/src/Cafe/OS/libs/nsyshid/Skylander.h index 986ef185..9b9580b0 100644 --- a/src/Cafe/OS/libs/nsyshid/Skylander.h +++ b/src/Cafe/OS/libs/nsyshid/Skylander.h @@ -26,10 +26,14 @@ namespace nsyshid bool GetDescriptor(uint8 descType, uint8 descIndex, - uint8 lang, + uint16 lang, uint8* output, uint32 outputMaxLength) override; + bool SetIdle(uint8 ifIndex, + uint8 reportId, + uint8 duration) override; + bool SetProtocol(uint8 ifIndex, uint8 protocol) override; bool SetReport(ReportMessage* message) override; @@ -70,7 +74,7 @@ namespace nsyshid uint8 blue = 0; }; - void ControlTransfer(uint8* buf, sint32 originalLength); + void ControlTransfer(uint8* buf, uint32 length); void Activate(); void Deactivate(); diff --git a/src/Cafe/OS/libs/nsyshid/nsyshid.cpp b/src/Cafe/OS/libs/nsyshid/nsyshid.cpp index 99a736d9..2fe6da07 100644 --- a/src/Cafe/OS/libs/nsyshid/nsyshid.cpp +++ b/src/Cafe/OS/libs/nsyshid/nsyshid.cpp @@ -305,47 +305,37 @@ namespace nsyshid osLib_returnFromFunction(hCPU, 0); } - void export_HIDGetDescriptor(PPCInterpreter_t* hCPU) + void _debugPrintHex(const std::string prefix, const uint8* data, size_t size) { - ppcDefineParamU32(hidHandle, 0); // r3 - ppcDefineParamU8(descType, 1); // r4 - ppcDefineParamU8(descIndex, 2); // r5 - ppcDefineParamU8(lang, 3); // r6 - ppcDefineParamUStr(output, 4); // r7 - ppcDefineParamU32(outputMaxLength, 5); // r8 - ppcDefineParamMPTR(cbFuncMPTR, 6); // r9 - ppcDefineParamMPTR(cbParamMPTR, 7); // r10 + constexpr size_t BYTES_PER_LINE = 16; - int returnValue = -1; - std::shared_ptr<Device> device = GetDeviceByHandle(hidHandle, true); - if (device) + std::string out; + for (size_t row_start = 0; row_start < size; row_start += BYTES_PER_LINE) { - memset(output, 0, outputMaxLength); - if (device->GetDescriptor(descType, descIndex, lang, output, outputMaxLength)) + out += fmt::format("{:06x}: ", row_start); + for (size_t i = 0; i < BYTES_PER_LINE; ++i) { - returnValue = 0; + if (row_start + i < size) + { + out += fmt::format("{:02x} ", data[row_start + i]); + } + else + { + out += " "; + } } - else + out += " "; + for (size_t i = 0; i < BYTES_PER_LINE; ++i) { - returnValue = -1; + if (row_start + i < size) + { + char c = static_cast<char>(data[row_start + i]); + out += std::isprint(c, std::locale::classic()) ? c : '.'; + } } + out += "\n"; } - else - { - cemu_assert_suspicious(); - } - osLib_returnFromFunction(hCPU, returnValue); - } - - void _debugPrintHex(std::string prefix, uint8* data, size_t len) - { - char debugOutput[1024] = {0}; - len = std::min(len, (size_t)100); - for (sint32 i = 0; i < len; i++) - { - sprintf(debugOutput + i * 3, "%02x ", data[i]); - } - cemuLog_logDebug(LogType::Force, "[{}] Data: {}", prefix, debugOutput); + cemuLog_logDebug(LogType::Force, "[{}] Data: \n{}", prefix, out); } void DoHIDTransferCallback(MPTR callbackFuncMPTR, MPTR callbackParamMPTR, uint32 hidHandle, uint32 errorCode, @@ -354,26 +344,152 @@ namespace nsyshid coreinitAsyncCallback_add(callbackFuncMPTR, 5, hidHandle, errorCode, buffer, length, callbackParamMPTR); } - void export_HIDSetIdle(PPCInterpreter_t* hCPU) + void _hidGetDescriptorAsync(std::shared_ptr<Device> device, uint8 descType, uint8 descIndex, uint16 lang, uint8* output, uint32 outputMaxLength, MPTR callbackFuncMPTR, MPTR callbackParamMPTR) { - ppcDefineParamU32(hidHandle, 0); // r3 - ppcDefineParamU32(ifIndex, 1); // r4 - ppcDefineParamU32(ukn, 2); // r5 - ppcDefineParamU32(duration, 3); // r6 - ppcDefineParamMPTR(callbackFuncMPTR, 4); // r7 - ppcDefineParamMPTR(callbackParamMPTR, 5); // r8 - cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetIdle(...)"); - - // todo - if (callbackFuncMPTR) + if (device->GetDescriptor(descType, descIndex, lang, output, outputMaxLength)) { - DoHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidHandle, 0, MPTR_NULL, 0); + DoHIDTransferCallback(callbackFuncMPTR, + callbackParamMPTR, + device->m_hid->handle, + 0, + 0, + 0); } else { - cemu_assert_unimplemented(); + DoHIDTransferCallback(callbackFuncMPTR, + callbackParamMPTR, + device->m_hid->handle, + -1, + 0, + 0); + } + } + + void export_HIDGetDescriptor(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(hidHandle, 0); // r3 + ppcDefineParamU8(descType, 1); // r4 + ppcDefineParamU8(descIndex, 2); // r5 + ppcDefineParamU16(lang, 3); // r6 + ppcDefineParamUStr(output, 4); // r7 + ppcDefineParamU32(outputMaxLength, 5); // r8 + ppcDefineParamMPTR(cbFuncMPTR, 6); // r9 + ppcDefineParamMPTR(cbParamMPTR, 7); // r10 + cemuLog_logDebug(LogType::Force, "nsyshid.HIDGetDescriptor(0x{:08x}, 0x{:02x}, 0x{:02x}, 0x{:04x}, 0x{:x}, 0x{:08x}, 0x{:08x}, 0x{:08x})", + hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7], hCPU->gpr[8], hCPU->gpr[9], hCPU->gpr[10]); + + std::shared_ptr<Device> device = GetDeviceByHandle(hidHandle, true); + if (device == nullptr) + { + cemuLog_log(LogType::Force, "nsyshid.HIDGetDescriptor(): Unable to find device with hid handle {}", hidHandle); + osLib_returnFromFunction(hCPU, -1); + return; + } + + // issue request (synchronous or asynchronous) + sint32 returnCode = 0; + if (cbFuncMPTR == MPTR_NULL) + { + // synchronous + returnCode = -1; + if (device->GetDescriptor(descType, descIndex, lang, output, outputMaxLength)) + { + returnCode = outputMaxLength; + } + } + else + { + // asynchronous + std::thread(&_hidGetDescriptorAsync, device, descType, descIndex, lang, output, outputMaxLength, cbFuncMPTR, cbParamMPTR) + .detach(); + returnCode = 0; + } + osLib_returnFromFunction(hCPU, returnCode); + } + + void _hidSetIdleAsync(std::shared_ptr<Device> device, uint8 ifIndex, uint8 reportId, uint8 duration, MPTR callbackFuncMPTR, MPTR callbackParamMPTR) + { + if (device->SetIdle(ifIndex, reportId, duration)) + { + DoHIDTransferCallback(callbackFuncMPTR, + callbackParamMPTR, + device->m_hid->handle, + 0, + 0, + 0); + } + else + { + DoHIDTransferCallback(callbackFuncMPTR, + callbackParamMPTR, + device->m_hid->handle, + -1, + 0, + 0); + } + } + + void export_HIDSetIdle(PPCInterpreter_t* hCPU) + { + ppcDefineParamU32(hidHandle, 0); // r3 + ppcDefineParamU8(ifIndex, 1); // r4 + ppcDefineParamU8(reportId, 2); // r5 + ppcDefineParamU8(duration, 3); // r6 + ppcDefineParamMPTR(callbackFuncMPTR, 4); // r7 + ppcDefineParamMPTR(callbackParamMPTR, 5); // r8 + cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetIdle(0x{:08x}, 0x{:02x}, 0x{:02x}, 0x{:02x}, 0x{:08x}, 0x{:08x})", hCPU->gpr[3], + hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7], hCPU->gpr[8]); + + std::shared_ptr<Device> device = GetDeviceByHandle(hidHandle, true); + if (device == nullptr) + { + cemuLog_log(LogType::Force, "nsyshid.HIDSetIdle(): Unable to find device with hid handle {}", hidHandle); + osLib_returnFromFunction(hCPU, -1); + return; + } + + // issue request (synchronous or asynchronous) + sint32 returnCode = 0; + if (callbackFuncMPTR == MPTR_NULL) + { + // synchronous + returnCode = -1; + if (device->SetIdle(ifIndex, reportId, duration)) + { + returnCode = 0; + } + } + else + { + // asynchronous + std::thread(&_hidSetIdleAsync, device, ifIndex, reportId, duration, callbackFuncMPTR, callbackParamMPTR) + .detach(); + returnCode = 0; + } + osLib_returnFromFunction(hCPU, returnCode); + } + + void _hidSetProtocolAsync(std::shared_ptr<Device> device, uint8 ifIndex, uint8 protocol, MPTR callbackFuncMPTR, MPTR callbackParamMPTR) + { + if (device->SetProtocol(ifIndex, protocol)) + { + DoHIDTransferCallback(callbackFuncMPTR, + callbackParamMPTR, + device->m_hid->handle, + 0, + 0, + 0); + } + else + { + DoHIDTransferCallback(callbackFuncMPTR, + callbackParamMPTR, + device->m_hid->handle, + -1, + 0, + 0); } - osLib_returnFromFunction(hCPU, 0); // for non-async version, return number of bytes transferred } void export_HIDSetProtocol(PPCInterpreter_t* hCPU) @@ -383,51 +499,51 @@ namespace nsyshid ppcDefineParamU8(protocol, 2); // r5 ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6 ppcDefineParamMPTR(callbackParamMPTR, 4); // r7 - cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetProtocol(...)"); + cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetProtocol(0x{:08x}, 0x{:02x}, 0x{:02x}, 0x{:08x}, 0x{:08x})", hCPU->gpr[3], + hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]); std::shared_ptr<Device> device = GetDeviceByHandle(hidHandle, true); - sint32 returnCode = -1; - if (device) + if (device == nullptr) { - if (!device->IsOpened()) + cemuLog_log(LogType::Force, "nsyshid.HIDSetProtocol(): Unable to find device with hid handle {}", hidHandle); + osLib_returnFromFunction(hCPU, -1); + return; + } + // issue request (synchronous or asynchronous) + sint32 returnCode = 0; + if (callbackFuncMPTR == MPTR_NULL) + { + // synchronous + returnCode = -1; + if (device->SetProtocol(ifIndex, protocol)) { - cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetProtocol(): error: device is not opened"); - } - else - { - if (device->SetProtocol(ifIndex, protocol)) - { - returnCode = 0; - } + returnCode = 0; } } else { - cemu_assert_suspicious(); - } - - if (callbackFuncMPTR) - { - DoHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidHandle, 0, MPTR_NULL, 0); + // asynchronous + std::thread(&_hidSetProtocolAsync, device, ifIndex, protocol, callbackFuncMPTR, callbackParamMPTR) + .detach(); + returnCode = 0; } osLib_returnFromFunction(hCPU, returnCode); } // handler for async HIDSetReport transfers - void _hidSetReportAsync(std::shared_ptr<Device> device, uint8* reportData, sint32 length, - uint8* originalData, - sint32 originalLength, MPTR callbackFuncMPTR, MPTR callbackParamMPTR) + void _hidSetReportAsync(std::shared_ptr<Device> device, uint8 reportType, uint8 reportId, uint8* data, uint32 length, + MPTR callbackFuncMPTR, MPTR callbackParamMPTR) { cemuLog_logDebug(LogType::Force, "_hidSetReportAsync begin"); - ReportMessage message(reportData, length, originalData, originalLength); + ReportMessage message(reportType, reportId, data, length); if (device->SetReport(&message)) { DoHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, device->m_hid->handle, 0, - memory_getVirtualOffsetFromPointer(originalData), - originalLength); + memory_getVirtualOffsetFromPointer(data), + length); } else { @@ -435,24 +551,22 @@ namespace nsyshid callbackParamMPTR, device->m_hid->handle, -1, - memory_getVirtualOffsetFromPointer(originalData), - 0); + memory_getVirtualOffsetFromPointer(data), + length); } - free(reportData); } // handler for synchronous HIDSetReport transfers - sint32 _hidSetReportSync(std::shared_ptr<Device> device, uint8* reportData, sint32 length, - uint8* originalData, sint32 originalLength, coreinit::OSEvent* event) + sint32 _hidSetReportSync(std::shared_ptr<Device> device, uint8 reportType, uint8 reportId, + uint8* data, uint32 length, coreinit::OSEvent* event) { - _debugPrintHex("_hidSetReportSync Begin", reportData, length); + _debugPrintHex("_hidSetReportSync Begin", data, length); sint32 returnCode = 0; - ReportMessage message(reportData, length, originalData, originalLength); + ReportMessage message(reportType, reportId, data, length); if (device->SetReport(&message)) { - returnCode = originalLength; + returnCode = length; } - free(reportData); cemuLog_logDebug(LogType::Force, "_hidSetReportSync end. returnCode: {}", returnCode); coreinit::OSSignalEvent(event); return returnCode; @@ -461,19 +575,19 @@ namespace nsyshid void export_HIDSetReport(PPCInterpreter_t* hCPU) { ppcDefineParamU32(hidHandle, 0); // r3 - ppcDefineParamU32(reportRelatedUkn, 1); // r4 - ppcDefineParamU32(reportId, 2); // r5 + ppcDefineParamU8(reportType, 1); // r4 + ppcDefineParamU8(reportId, 2); // r5 ppcDefineParamUStr(data, 3); // r6 ppcDefineParamU32(dataLength, 4); // r7 ppcDefineParamMPTR(callbackFuncMPTR, 5); // r8 ppcDefineParamMPTR(callbackParamMPTR, 6); // r9 - cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetReport({},0x{:02x},0x{:02x},...)", hidHandle, reportRelatedUkn, - reportId); + cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetReport(0x{:08x}, 0x{:02x}, 0x{:02x}, 0x{:08x}, 0x{:08x}, 0x{:08x}, 0x{:08x})", hCPU->gpr[3], + hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7], hCPU->gpr[8], hCPU->gpr[9]); _debugPrintHex("HIDSetReport", data, dataLength); #ifdef CEMU_DEBUG_ASSERT - if (reportRelatedUkn != 2 || reportId != 0) + if (reportType != 2 || reportId != 0) assert_dbg(); #endif @@ -485,15 +599,6 @@ namespace nsyshid return; } - // prepare report data - // note: Currently we need to pad the data to 0x20 bytes for it to work (plus one extra byte for HidD_SetOutputReport) - // Does IOSU pad data to 0x20 byte? Also check if this is specific to Skylanders portal - sint32 paddedLength = (dataLength + 0x1F) & ~0x1F; - uint8* reportData = (uint8*)malloc(paddedLength + 1); - memset(reportData, 0, paddedLength + 1); - reportData[0] = 0; - memcpy(reportData + 1, data, dataLength); - // issue request (synchronous or asynchronous) sint32 returnCode = 0; if (callbackFuncMPTR == MPTR_NULL) @@ -501,15 +606,14 @@ namespace nsyshid // synchronous StackAllocator<coreinit::OSEvent> event; coreinit::OSInitEvent(&event, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); - std::future<sint32> res = std::async(std::launch::async, &_hidSetReportSync, device, reportData, - paddedLength + 1, data, dataLength, &event); + std::future<sint32> res = std::async(std::launch::async, &_hidSetReportSync, device, reportType, reportId, data, dataLength, &event); coreinit::OSWaitEvent(&event); returnCode = res.get(); } else { // asynchronous - std::thread(&_hidSetReportAsync, device, reportData, paddedLength + 1, data, dataLength, + std::thread(&_hidSetReportAsync, device, reportType, reportId, data, dataLength, callbackFuncMPTR, callbackParamMPTR) .detach(); returnCode = 0; @@ -586,7 +690,7 @@ namespace nsyshid ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6 ppcDefineParamMPTR(callbackParamMPTR, 4); // r7 cemuLog_logDebug(LogType::Force, "nsyshid.HIDRead(0x{:x},0x{:08x},0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], - hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]); + hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]); std::shared_ptr<Device> device = GetDeviceByHandle(hidHandle, true); if (device == nullptr) @@ -683,7 +787,7 @@ namespace nsyshid ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6 ppcDefineParamMPTR(callbackParamMPTR, 4); // r7 cemuLog_logDebug(LogType::Force, "nsyshid.HIDWrite(0x{:x},0x{:08x},0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], - hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]); + hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]); std::shared_ptr<Device> device = GetDeviceByHandle(hidHandle, true); if (device == nullptr) @@ -718,7 +822,7 @@ namespace nsyshid ppcDefineParamTypePtr(ukn0, uint32be, 1); ppcDefineParamTypePtr(ukn1, uint32be, 2); cemuLog_logDebug(LogType::Force, "nsyshid.HIDDecodeError(0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], - hCPU->gpr[4], hCPU->gpr[5]); + hCPU->gpr[4], hCPU->gpr[5]); // todo *ukn0 = 0x3FF; From 07cd402531852537fb109f8dcd63d6627b856087 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 12 Jan 2025 18:33:15 +0100 Subject: [PATCH 229/299] Update precompiled.h --- src/Common/precompiled.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Common/precompiled.h b/src/Common/precompiled.h index 3dfeaf74..bda75cef 100644 --- a/src/Common/precompiled.h +++ b/src/Common/precompiled.h @@ -279,10 +279,10 @@ inline uint64 _udiv128(uint64 highDividend, uint64 lowDividend, uint64 divisor, #elif defined(__GNUC__) || defined(__clang__) #define FORCE_INLINE inline __attribute__((always_inline)) #else -#define FORCE_INLINE +#define FORCE_INLINE inline #endif -FORCE_INLINE inline int BSF(uint32 v) // returns index of first bit set, counting from LSB. If v is 0 then result is undefined +FORCE_INLINE int BSF(uint32 v) // returns index of first bit set, counting from LSB. If v is 0 then result is undefined { #if defined(_MSC_VER) return _tzcnt_u32(v); // TZCNT requires BMI1. But if not supported it will execute as BSF @@ -616,4 +616,4 @@ namespace stdx scope_exit& operator=(scope_exit) = delete; void release() { m_released = true;} }; -} \ No newline at end of file +} From eab1b24320454ef60d6172dd41dfa8379b3015ac Mon Sep 17 00:00:00 2001 From: Joshua de Reeper <joshua@dereeper.co.nz> Date: Sun, 12 Jan 2025 19:20:48 +0000 Subject: [PATCH 230/299] nsyshid: Initialise interface index as 0 (#1473) --- src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp b/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp index a7deef90..b5dd0e0f 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp +++ b/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp @@ -272,7 +272,7 @@ namespace nsyshid::backend::libusb auto device = std::make_shared<DeviceLibusb>(m_ctx, desc.idVendor, desc.idProduct, - 1, + 0, 2, 0, libusb_get_bus_number(dev), From 4ac65159efd14bec4cb0c74dde5b11100ad7f7d6 Mon Sep 17 00:00:00 2001 From: Alexandre Bouvier <contact@amb.tf> Date: Thu, 16 Jan 2025 11:54:29 +0000 Subject: [PATCH 231/299] Fix building against fmt 11.1.0 (#1474) --- src/Cafe/GameProfile/GameProfile.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cafe/GameProfile/GameProfile.cpp b/src/Cafe/GameProfile/GameProfile.cpp index ee92107a..2a83b3fe 100644 --- a/src/Cafe/GameProfile/GameProfile.cpp +++ b/src/Cafe/GameProfile/GameProfile.cpp @@ -147,7 +147,7 @@ bool gameProfile_loadEnumOption(IniParser& iniParser, const char* optionName, T& } // test enum name - if(boost::iequals(fmt::format("{}", v), *option_value)) + if(boost::iequals(fmt::format("{}", fmt::underlying(v)), *option_value)) { option = v; return true; From 5bd253a1f84b5c410023744edf46896547f770d8 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Thu, 23 Jan 2025 17:29:12 +0100 Subject: [PATCH 232/299] Revert "Fix building against fmt 11.1.0 (#1474)" Reverting commit 4ac65159efd14bec4cb0c74dde5b11100ad7f7d6 because game profile enums use the stringifying formatters from config.h and are not supposed to store raw integers --- src/Cafe/GameProfile/GameProfile.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cafe/GameProfile/GameProfile.cpp b/src/Cafe/GameProfile/GameProfile.cpp index 2a83b3fe..ee92107a 100644 --- a/src/Cafe/GameProfile/GameProfile.cpp +++ b/src/Cafe/GameProfile/GameProfile.cpp @@ -147,7 +147,7 @@ bool gameProfile_loadEnumOption(IniParser& iniParser, const char* optionName, T& } // test enum name - if(boost::iequals(fmt::format("{}", fmt::underlying(v)), *option_value)) + if(boost::iequals(fmt::format("{}", v), *option_value)) { option = v; return true; From 372c314f0630a6d0ae1cb303f696071efbc72717 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Thu, 23 Jan 2025 21:02:09 +0100 Subject: [PATCH 233/299] fix building with fmt11 and GCC --- src/Cafe/GameProfile/GameProfile.cpp | 2 +- src/config/CemuConfig.h | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Cafe/GameProfile/GameProfile.cpp b/src/Cafe/GameProfile/GameProfile.cpp index ee92107a..ea303226 100644 --- a/src/Cafe/GameProfile/GameProfile.cpp +++ b/src/Cafe/GameProfile/GameProfile.cpp @@ -140,7 +140,7 @@ bool gameProfile_loadEnumOption(IniParser& iniParser, const char* optionName, T& for(const T& v : T()) { // test integer option - if (boost::iequals(fmt::format("{}", static_cast<typename std::underlying_type<T>::type>(v)), *option_value)) + if (boost::iequals(fmt::format("{}", fmt::underlying(v)), *option_value)) { option = v; return true; diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index 191614a2..62665f6d 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -192,7 +192,7 @@ ENABLE_ENUM_ITERATORS(CrashDump, CrashDump::Disabled, CrashDump::Enabled); #endif template <> -struct fmt::formatter<PrecompiledShaderOption> : formatter<string_view> { +struct fmt::formatter<const PrecompiledShaderOption> : formatter<string_view> { template <typename FormatContext> auto format(const PrecompiledShaderOption c, FormatContext &ctx) const { string_view name; @@ -207,7 +207,7 @@ struct fmt::formatter<PrecompiledShaderOption> : formatter<string_view> { } }; template <> -struct fmt::formatter<AccurateShaderMulOption> : formatter<string_view> { +struct fmt::formatter<const AccurateShaderMulOption> : formatter<string_view> { template <typename FormatContext> auto format(const AccurateShaderMulOption c, FormatContext &ctx) const { string_view name; @@ -221,7 +221,7 @@ struct fmt::formatter<AccurateShaderMulOption> : formatter<string_view> { } }; template <> -struct fmt::formatter<CPUMode> : formatter<string_view> { +struct fmt::formatter<const CPUMode> : formatter<string_view> { template <typename FormatContext> auto format(const CPUMode c, FormatContext &ctx) const { string_view name; @@ -238,7 +238,7 @@ struct fmt::formatter<CPUMode> : formatter<string_view> { } }; template <> -struct fmt::formatter<CPUModeLegacy> : formatter<string_view> { +struct fmt::formatter<const CPUModeLegacy> : formatter<string_view> { template <typename FormatContext> auto format(const CPUModeLegacy c, FormatContext &ctx) const { string_view name; @@ -255,7 +255,7 @@ struct fmt::formatter<CPUModeLegacy> : formatter<string_view> { } }; template <> -struct fmt::formatter<CafeConsoleRegion> : formatter<string_view> { +struct fmt::formatter<const CafeConsoleRegion> : formatter<string_view> { template <typename FormatContext> auto format(const CafeConsoleRegion v, FormatContext &ctx) const { string_view name; @@ -276,7 +276,7 @@ struct fmt::formatter<CafeConsoleRegion> : formatter<string_view> { } }; template <> -struct fmt::formatter<CafeConsoleLanguage> : formatter<string_view> { +struct fmt::formatter<const CafeConsoleLanguage> : formatter<string_view> { template <typename FormatContext> auto format(const CafeConsoleLanguage v, FormatContext &ctx) { string_view name; @@ -302,7 +302,7 @@ struct fmt::formatter<CafeConsoleLanguage> : formatter<string_view> { #if BOOST_OS_WINDOWS template <> -struct fmt::formatter<CrashDump> : formatter<string_view> { +struct fmt::formatter<const CrashDump> : formatter<string_view> { template <typename FormatContext> auto format(const CrashDump v, FormatContext &ctx) { string_view name; @@ -319,7 +319,7 @@ struct fmt::formatter<CrashDump> : formatter<string_view> { }; #elif BOOST_OS_UNIX template <> -struct fmt::formatter<CrashDump> : formatter<string_view> { +struct fmt::formatter<const CrashDump> : formatter<string_view> { template <typename FormatContext> auto format(const CrashDump v, FormatContext &ctx) { string_view name; From 4f9eea07e045e0d8b0e14dfced29795641cd128e Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Thu, 23 Jan 2025 20:35:43 +0100 Subject: [PATCH 234/299] CI: Update action version --- .github/workflows/generate_pot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/generate_pot.yml b/.github/workflows/generate_pot.yml index 7dfa86f8..b057d441 100644 --- a/.github/workflows/generate_pot.yml +++ b/.github/workflows/generate_pot.yml @@ -35,7 +35,7 @@ jobs: -o cemu.pot - name: Upload artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: POT file path: ./cemu.pot From e834515f430c8d8eaee6be6e1a5a4f16fcbca8d9 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Thu, 23 Jan 2025 21:20:03 +0100 Subject: [PATCH 235/299] Vulkan: Improve post-shutdown cleanup and minor improvements (#1401) --- src/Cafe/HW/Latte/Core/LatteShaderCache.cpp | 5 +- src/Cafe/HW/Latte/Core/LatteThread.cpp | 1 + .../HW/Latte/Renderer/RendererOuputShader.cpp | 20 +++- .../HW/Latte/Renderer/RendererOuputShader.h | 9 +- .../Renderer/Vulkan/RendererShaderVk.cpp | 3 + .../Latte/Renderer/Vulkan/SwapchainInfoVk.cpp | 2 +- .../Latte/Renderer/Vulkan/SwapchainInfoVk.h | 2 +- .../Renderer/Vulkan/VKRMemoryManager.cpp | 79 ++++++--------- .../Latte/Renderer/Vulkan/VKRMemoryManager.h | 11 +-- src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h | 3 + .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 96 ++++++++++++++----- .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 8 +- .../Renderer/Vulkan/VulkanRendererCore.cpp | 30 ++---- .../Renderer/Vulkan/VulkanSurfaceCopy.cpp | 30 +++++- src/imgui/imgui_impl_vulkan.cpp | 9 ++ 15 files changed, 190 insertions(+), 118 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/LatteShaderCache.cpp b/src/Cafe/HW/Latte/Core/LatteShaderCache.cpp index 9b24de45..0d427e34 100644 --- a/src/Cafe/HW/Latte/Core/LatteShaderCache.cpp +++ b/src/Cafe/HW/Latte/Core/LatteShaderCache.cpp @@ -485,6 +485,9 @@ void LatteShaderCache_Load() g_renderer->DeleteTexture(g_shaderCacheLoaderState.textureDRCId); g_bootSndPlayer.FadeOutSound(); + + if(Latte_GetStopSignal()) + LatteThread_Exit(); } void LatteShaderCache_ShowProgress(const std::function <bool(void)>& loadUpdateFunc, bool isPipelines) @@ -625,8 +628,6 @@ void LatteShaderCache_LoadVulkanPipelineCache(uint64 cacheTitleId) g_shaderCacheLoaderState.loadedPipelines = 0; LatteShaderCache_ShowProgress(LatteShaderCache_updatePipelineLoadingProgress, true); pipelineCache.EndLoading(); - if(Latte_GetStopSignal()) - LatteThread_Exit(); } bool LatteShaderCache_updatePipelineLoadingProgress() diff --git a/src/Cafe/HW/Latte/Core/LatteThread.cpp b/src/Cafe/HW/Latte/Core/LatteThread.cpp index 8874ecf4..92a7fdbb 100644 --- a/src/Cafe/HW/Latte/Core/LatteThread.cpp +++ b/src/Cafe/HW/Latte/Core/LatteThread.cpp @@ -257,6 +257,7 @@ void LatteThread_Exit() LatteSHRC_UnloadAll(); // close disk cache LatteShaderCache_Close(); + RendererOutputShader::ShutdownStatic(); // destroy renderer but make sure that g_renderer remains valid until the destructor has finished if (g_renderer) { diff --git a/src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp b/src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp index 409dc24f..3a00c36a 100644 --- a/src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp +++ b/src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp @@ -118,8 +118,8 @@ RendererOutputShader::RendererOutputShader(const std::string& vertex_source, con { auto finalFragmentSrc = PrependFragmentPreamble(fragment_source); - m_vertex_shader = g_renderer->shader_create(RendererShader::ShaderType::kVertex, 0, 0, vertex_source, false, false); - m_fragment_shader = g_renderer->shader_create(RendererShader::ShaderType::kFragment, 0, 0, finalFragmentSrc, false, false); + m_vertex_shader.reset(g_renderer->shader_create(RendererShader::ShaderType::kVertex, 0, 0, vertex_source, false, false)); + m_fragment_shader.reset(g_renderer->shader_create(RendererShader::ShaderType::kFragment, 0, 0, finalFragmentSrc, false, false)); m_vertex_shader->PreponeCompilation(true); m_fragment_shader->PreponeCompilation(true); @@ -169,8 +169,8 @@ void RendererOutputShader::SetUniformParameters(const LatteTextureView& texture_ shader->SetUniform2fv(locations.m_loc_outputResolution, res, 1); } }; - setUniforms(m_vertex_shader, m_uniformLocations[0]); - setUniforms(m_fragment_shader, m_uniformLocations[1]); + setUniforms(m_vertex_shader.get(), m_uniformLocations[0]); + setUniforms(m_fragment_shader.get(), m_uniformLocations[1]); } RendererOutputShader* RendererOutputShader::s_copy_shader; @@ -325,3 +325,15 @@ void RendererOutputShader::InitializeStatic() s_hermit_shader = new RendererOutputShader(vertex_source, s_hermite_shader_source); s_hermit_shader_ud = new RendererOutputShader(vertex_source_ud, s_hermite_shader_source); } + +void RendererOutputShader::ShutdownStatic() +{ + delete s_copy_shader; + delete s_copy_shader_ud; + + delete s_bicubic_shader; + delete s_bicubic_shader_ud; + + delete s_hermit_shader; + delete s_hermit_shader_ud; +} diff --git a/src/Cafe/HW/Latte/Renderer/RendererOuputShader.h b/src/Cafe/HW/Latte/Renderer/RendererOuputShader.h index 61b24c20..b12edf8b 100644 --- a/src/Cafe/HW/Latte/Renderer/RendererOuputShader.h +++ b/src/Cafe/HW/Latte/Renderer/RendererOuputShader.h @@ -21,15 +21,16 @@ public: RendererShader* GetVertexShader() const { - return m_vertex_shader; + return m_vertex_shader.get(); } RendererShader* GetFragmentShader() const { - return m_fragment_shader; + return m_fragment_shader.get(); } static void InitializeStatic(); + static void ShutdownStatic(); static RendererOutputShader* s_copy_shader; static RendererOutputShader* s_copy_shader_ud; @@ -46,8 +47,8 @@ public: static std::string PrependFragmentPreamble(const std::string& shaderSrc); protected: - RendererShader* m_vertex_shader; - RendererShader* m_fragment_shader; + std::unique_ptr<RendererShader> m_vertex_shader; + std::unique_ptr<RendererShader> m_fragment_shader; struct UniformLocations { diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp index 50f2c2d6..8a66c81b 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/RendererShaderVk.cpp @@ -211,6 +211,9 @@ RendererShaderVk::~RendererShaderVk() { while (!list_pipelineInfo.empty()) delete list_pipelineInfo[0]; + + VkDevice vkDev = VulkanRenderer::GetInstance()->GetLogicalDevice(); + vkDestroyShaderModule(vkDev, m_shader_module, nullptr); } void RendererShaderVk::Init() diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp index 56a3ab12..47dafc98 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.cpp @@ -60,7 +60,7 @@ void SwapchainInfoVk::Create() VkAttachmentDescription colorAttachment = {}; colorAttachment.format = m_surfaceFormat.format; colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; - colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.h b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.h index 7023f291..c4977c08 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/SwapchainInfoVk.h @@ -70,6 +70,7 @@ struct SwapchainInfoVk VkSurfaceFormatKHR m_surfaceFormat{}; VkSwapchainKHR m_swapchain{}; Vector2i m_desiredExtent{}; + VkExtent2D m_actualExtent{}; uint32 swapchainImageIndex = (uint32)-1; uint64 m_presentId = 1; uint64 m_queueDepth = 0; // number of frames with pending presentation requests @@ -92,5 +93,4 @@ private: VkSemaphore m_currentSemaphore = VK_NULL_HANDLE; std::array<uint32, 2> m_swapchainQueueFamilyIndices; - VkExtent2D m_actualExtent{}; }; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VKRMemoryManager.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRMemoryManager.cpp index 3494dbc5..54ca20cf 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VKRMemoryManager.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRMemoryManager.cpp @@ -4,6 +4,14 @@ /* VKRSynchronizedMemoryBuffer */ +VKRSynchronizedRingAllocator::~VKRSynchronizedRingAllocator() +{ + for(auto& buf : m_buffers) + { + m_vkrMemMgr->DeleteBuffer(buf.vk_buffer, buf.vk_mem); + } +} + void VKRSynchronizedRingAllocator::addUploadBufferSyncPoint(AllocatorBuffer_t& buffer, uint32 offset) { auto cmdBufferId = m_vkr->GetCurrentCommandBufferId(); @@ -233,6 +241,15 @@ void VKRSynchronizedHeapAllocator::GetStats(uint32& numBuffers, size_t& totalBuf /* VkTextureChunkedHeap */ +VkTextureChunkedHeap::~VkTextureChunkedHeap() +{ + VkDevice device = VulkanRenderer::GetInstance()->GetLogicalDevice(); + for (auto& i : m_list_chunkInfo) + { + vkFreeMemory(device, i.mem, nullptr); + } +} + uint32 VkTextureChunkedHeap::allocateNewChunk(uint32 chunkIndex, uint32 minimumAllocationSize) { cemu_assert_debug(m_list_chunkInfo.size() == chunkIndex); @@ -310,11 +327,11 @@ VKRBuffer* VKRBuffer::Create(VKR_BUFFER_TYPE bufferType, size_t bufferSize, VkMe VkDeviceMemory bufferMemory; bool allocSuccess; if (bufferType == VKR_BUFFER_TYPE::STAGING) - allocSuccess = memMgr->CreateBuffer2(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, properties, buffer, bufferMemory); + allocSuccess = memMgr->CreateBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, properties, buffer, bufferMemory); else if (bufferType == VKR_BUFFER_TYPE::INDEX) - allocSuccess = memMgr->CreateBuffer2(bufferSize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, properties, buffer, bufferMemory); + allocSuccess = memMgr->CreateBuffer(bufferSize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, properties, buffer, bufferMemory); else if (bufferType == VKR_BUFFER_TYPE::STRIDE) - allocSuccess = memMgr->CreateBuffer2(bufferSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, properties, buffer, bufferMemory); + allocSuccess = memMgr->CreateBuffer(bufferSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, properties, buffer, bufferMemory); else cemu_assert_debug(false); if (!allocSuccess) @@ -363,28 +380,14 @@ uint32 VkBufferChunkedHeap::allocateNewChunk(uint32 chunkIndex, uint32 minimumAl return allocationSize; } -uint32_t VKRMemoryManager::FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) const -{ - VkPhysicalDeviceMemoryProperties memProperties; - vkGetPhysicalDeviceMemoryProperties(m_vkr->GetPhysicalDevice(), &memProperties); - - for (uint32 i = 0; i < memProperties.memoryTypeCount; i++) - { - if ((typeFilter & (1 << i)) != 0 && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) - return i; - } - m_vkr->UnrecoverableError(fmt::format("failed to find suitable memory type ({0:#08x} {1:#08x})", typeFilter, properties).c_str()); - return 0; -} - -bool VKRMemoryManager::FindMemoryType2(uint32 typeFilter, VkMemoryPropertyFlags properties, uint32& memoryIndex) const +bool VKRMemoryManager::FindMemoryType(uint32 typeFilter, VkMemoryPropertyFlags properties, uint32& memoryIndex) const { VkPhysicalDeviceMemoryProperties memProperties; vkGetPhysicalDeviceMemoryProperties(m_vkr->GetPhysicalDevice(), &memProperties); for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) { - if (typeFilter & (1 << i) && memProperties.memoryTypes[i].propertyFlags == properties) + if (typeFilter & (1 << i) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { memoryIndex = i; return true; @@ -455,31 +458,7 @@ size_t VKRMemoryManager::GetTotalMemoryForBufferType(VkBufferUsageFlags usage, V return total; } -void VKRMemoryManager::CreateBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const -{ - VkBufferCreateInfo bufferInfo{}; - bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - bufferInfo.usage = usage; - bufferInfo.size = size; - bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - if (vkCreateBuffer(m_vkr->GetLogicalDevice(), &bufferInfo, nullptr, &buffer) != VK_SUCCESS) - m_vkr->UnrecoverableError("Failed to create buffer"); - - VkMemoryRequirements memRequirements; - vkGetBufferMemoryRequirements(m_vkr->GetLogicalDevice(), buffer, &memRequirements); - - VkMemoryAllocateInfo allocInfo{}; - allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; - allocInfo.allocationSize = memRequirements.size; - allocInfo.memoryTypeIndex = FindMemoryType(memRequirements.memoryTypeBits, properties); - - if (vkAllocateMemory(m_vkr->GetLogicalDevice(), &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) - m_vkr->UnrecoverableError("Failed to allocate buffer memory"); - if (vkBindBufferMemory(m_vkr->GetLogicalDevice(), buffer, bufferMemory, 0) != VK_SUCCESS) - m_vkr->UnrecoverableError("Failed to bind buffer memory"); -} - -bool VKRMemoryManager::CreateBuffer2(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const +bool VKRMemoryManager::CreateBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const { VkBufferCreateInfo bufferInfo{}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; @@ -488,7 +467,7 @@ bool VKRMemoryManager::CreateBuffer2(VkDeviceSize size, VkBufferUsageFlags usage bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; if (vkCreateBuffer(m_vkr->GetLogicalDevice(), &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { - cemuLog_log(LogType::Force, "Failed to create buffer (CreateBuffer2)"); + cemuLog_log(LogType::Force, "Failed to create buffer (CreateBuffer)"); return false; } @@ -498,7 +477,7 @@ bool VKRMemoryManager::CreateBuffer2(VkDeviceSize size, VkBufferUsageFlags usage VkMemoryAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; allocInfo.allocationSize = memRequirements.size; - if (!FindMemoryType2(memRequirements.memoryTypeBits, properties, allocInfo.memoryTypeIndex)) + if (!FindMemoryType(memRequirements.memoryTypeBits, properties, allocInfo.memoryTypeIndex)) { vkDestroyBuffer(m_vkr->GetLogicalDevice(), buffer, nullptr); return false; @@ -511,7 +490,7 @@ bool VKRMemoryManager::CreateBuffer2(VkDeviceSize size, VkBufferUsageFlags usage if (vkBindBufferMemory(m_vkr->GetLogicalDevice(), buffer, bufferMemory, 0) != VK_SUCCESS) { vkDestroyBuffer(m_vkr->GetLogicalDevice(), buffer, nullptr); - cemuLog_log(LogType::Force, "Failed to bind buffer (CreateBuffer2)"); + cemuLog_log(LogType::Force, "Failed to bind buffer (CreateBuffer)"); return false; } return true; @@ -533,7 +512,7 @@ bool VKRMemoryManager::CreateBufferFromHostMemory(void* hostPointer, VkDeviceSiz if (vkCreateBuffer(m_vkr->GetLogicalDevice(), &bufferInfo, nullptr, &buffer) != VK_SUCCESS) { - cemuLog_log(LogType::Force, "Failed to create buffer (CreateBuffer2)"); + cemuLog_log(LogType::Force, "Failed to create buffer (CreateBuffer)"); return false; } @@ -554,7 +533,7 @@ bool VKRMemoryManager::CreateBufferFromHostMemory(void* hostPointer, VkDeviceSiz allocInfo.pNext = &importHostMem; - if (!FindMemoryType2(memRequirements.memoryTypeBits, properties, allocInfo.memoryTypeIndex)) + if (!FindMemoryType(memRequirements.memoryTypeBits, properties, allocInfo.memoryTypeIndex)) { vkDestroyBuffer(m_vkr->GetLogicalDevice(), buffer, nullptr); return false; @@ -598,7 +577,7 @@ VkImageMemAllocation* VKRMemoryManager::imageMemoryAllocate(VkImage image) map_textureHeap.emplace(typeFilter, texHeap); } else - texHeap = it->second; + texHeap = it->second.get(); // alloc mem from heap uint32 allocationSize = (uint32)memRequirements.size; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VKRMemoryManager.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRMemoryManager.h index 08af5882..b81b65ea 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VKRMemoryManager.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRMemoryManager.h @@ -48,6 +48,7 @@ class VkTextureChunkedHeap : private ChunkedHeap<> { public: VkTextureChunkedHeap(class VKRMemoryManager* memoryManager, uint32 typeFilter) : m_vkrMemoryManager(memoryManager), m_typeFilter(typeFilter) { }; + ~VkTextureChunkedHeap(); struct ChunkInfo { @@ -148,6 +149,7 @@ class VKRSynchronizedRingAllocator public: VKRSynchronizedRingAllocator(class VulkanRenderer* vkRenderer, class VKRMemoryManager* vkMemoryManager, VKR_BUFFER_TYPE bufferType, uint32 minimumBufferAllocSize) : m_vkr(vkRenderer), m_vkrMemMgr(vkMemoryManager), m_bufferType(bufferType), m_minimumBufferAllocSize(minimumBufferAllocSize) {}; VKRSynchronizedRingAllocator(const VKRSynchronizedRingAllocator&) = delete; // disallow copy + ~VKRSynchronizedRingAllocator(); struct BufferSyncPoint_t { @@ -256,7 +258,7 @@ public: } // texture memory management - std::unordered_map<uint32, VkTextureChunkedHeap*> map_textureHeap; // one heap per memory type + std::unordered_map<uint32, std::unique_ptr<VkTextureChunkedHeap>> map_textureHeap; // one heap per memory type std::vector<uint8> m_textureUploadBuffer; // texture upload buffer @@ -286,9 +288,7 @@ public: m_vertexStrideMetalBuffer.CleanupBuffer(latestFinishedCommandBufferId); } - // memory helpers - uint32_t FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) const; - bool FindMemoryType2(uint32 typeFilter, VkMemoryPropertyFlags properties, uint32& memoryIndex) const; // searches for exact properties. Can gracefully fail without throwing exception (returns false) + bool FindMemoryType(uint32 typeFilter, VkMemoryPropertyFlags properties, uint32& memoryIndex) const; // searches for exact properties. Can gracefully fail without throwing exception (returns false) std::vector<uint32> FindMemoryTypes(uint32_t typeFilter, VkMemoryPropertyFlags properties) const; // image memory allocation @@ -298,8 +298,7 @@ public: // buffer management size_t GetTotalMemoryForBufferType(VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, size_t minimumBufferSize = 16 * 1024 * 1024); - void CreateBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const; - bool CreateBuffer2(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const; // same as CreateBuffer but doesn't throw exception on failure + bool CreateBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const; // same as CreateBuffer but doesn't throw exception on failure bool CreateBufferFromHostMemory(void* hostPointer, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory) const; void DeleteBuffer(VkBuffer& buffer, VkDeviceMemory& deviceMem) const; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h index 6bde2a0b..ae2a62f2 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h @@ -165,6 +165,7 @@ VKFUNC_DEVICE(vkCmdDraw); VKFUNC_DEVICE(vkCmdCopyBufferToImage); VKFUNC_DEVICE(vkCmdCopyImageToBuffer); VKFUNC_DEVICE(vkCmdClearColorImage); +VKFUNC_DEVICE(vkCmdClearAttachments); VKFUNC_DEVICE(vkCmdBindIndexBuffer); VKFUNC_DEVICE(vkCmdBindVertexBuffers); VKFUNC_DEVICE(vkCmdDrawIndexed); @@ -198,6 +199,7 @@ VKFUNC_DEVICE(vkCmdEndTransformFeedbackEXT); // query VKFUNC_DEVICE(vkCreateQueryPool); +VKFUNC_DEVICE(vkDestroyQueryPool); VKFUNC_DEVICE(vkCmdResetQueryPool); VKFUNC_DEVICE(vkCmdBeginQuery); VKFUNC_DEVICE(vkCmdEndQuery); @@ -236,6 +238,7 @@ VKFUNC_DEVICE(vkAllocateDescriptorSets); VKFUNC_DEVICE(vkFreeDescriptorSets); VKFUNC_DEVICE(vkUpdateDescriptorSets); VKFUNC_DEVICE(vkCreateDescriptorPool); +VKFUNC_DEVICE(vkDestroyDescriptorPool); VKFUNC_DEVICE(vkDestroyDescriptorSetLayout); #undef VKFUNC_INIT diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 66369c10..4ff2faac 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -439,7 +439,7 @@ VulkanRenderer::VulkanRenderer() GetDeviceFeatures(); // init memory manager - memoryManager = new VKRMemoryManager(this); + memoryManager.reset(new VKRMemoryManager(this)); try { @@ -577,15 +577,15 @@ VulkanRenderer::VulkanRenderer() void* bufferPtr; // init ringbuffer for uniform vars m_uniformVarBufferMemoryIsCoherent = false; - if (memoryManager->CreateBuffer2(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory)) + if (memoryManager->CreateBuffer(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory)) m_uniformVarBufferMemoryIsCoherent = true; - else if (memoryManager->CreateBuffer2(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory)) + else if (memoryManager->CreateBuffer(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory)) m_uniformVarBufferMemoryIsCoherent = true; // unified memory - else if (memoryManager->CreateBuffer2(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory)) + else if (memoryManager->CreateBuffer(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory)) m_uniformVarBufferMemoryIsCoherent = true; else { - memoryManager->CreateBuffer2(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory); + memoryManager->CreateBuffer(UNIFORMVAR_RINGBUFFER_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, m_uniformVarBuffer, m_uniformVarBufferMemory); } if (!m_uniformVarBufferMemoryIsCoherent) @@ -628,6 +628,31 @@ VulkanRenderer::~VulkanRenderer() m_pipeline_cache_semaphore.notify(); m_pipeline_cache_save_thread.join(); + vkDestroyPipelineCache(m_logicalDevice, m_pipeline_cache, nullptr); + + if(!m_backbufferBlitDescriptorSetCache.empty()) + { + std::vector<VkDescriptorSet> freeVector; + freeVector.reserve(m_backbufferBlitDescriptorSetCache.size()); + std::transform(m_backbufferBlitDescriptorSetCache.begin(), m_backbufferBlitDescriptorSetCache.end(), std::back_inserter(freeVector), [](auto& i) { + return i.second; + }); + vkFreeDescriptorSets(m_logicalDevice, m_descriptorPool, freeVector.size(), freeVector.data()); + } + + vkDestroyDescriptorPool(m_logicalDevice, m_descriptorPool, nullptr); + + for(auto& i : m_backbufferBlitPipelineCache) + { + vkDestroyPipeline(m_logicalDevice, i.second, nullptr); + } + m_backbufferBlitPipelineCache = {}; + + if(m_occlusionQueries.queryPool != VK_NULL_HANDLE) + vkDestroyQueryPool(m_logicalDevice, m_occlusionQueries.queryPool, nullptr); + + vkDestroyDescriptorSetLayout(m_logicalDevice, m_swapchainDescriptorSetLayout, nullptr); + // shut down imgui ImGui_ImplVulkan_Shutdown(); @@ -640,10 +665,6 @@ VulkanRenderer::~VulkanRenderer() memoryManager->DeleteBuffer(m_xfbRingBuffer, m_xfbRingBufferMemory); memoryManager->DeleteBuffer(m_occlusionQueries.bufferQueryResults, m_occlusionQueries.memoryQueryResults); memoryManager->DeleteBuffer(m_bufferCache, m_bufferCacheMemory); - // texture memory - // todo - // upload buffers - // todo m_padSwapchainInfo = nullptr; m_mainSwapchainInfo = nullptr; @@ -666,6 +687,12 @@ VulkanRenderer::~VulkanRenderer() it = VK_NULL_HANDLE; } + for(auto& sem : m_commandBufferSemaphores) + { + vkDestroySemaphore(m_logicalDevice, sem, nullptr); + sem = VK_NULL_HANDLE; + } + if (m_pipelineLayout != VK_NULL_HANDLE) vkDestroyPipelineLayout(m_logicalDevice, m_pipelineLayout, nullptr); @@ -681,8 +708,11 @@ VulkanRenderer::~VulkanRenderer() vkDestroyDebugUtilsMessengerEXT(m_instance, m_debugCallback, nullptr); } + while(!m_destructionQueue.empty()) + ProcessDestructionQueue(); + // destroy memory manager - delete memoryManager; + memoryManager.reset(); // destroy instance, devices if (m_instance != VK_NULL_HANDLE) @@ -825,7 +855,14 @@ void VulkanRenderer::HandleScreenshotRequest(LatteTextureView* texView, bool pad VkMemoryAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; allocInfo.allocationSize = memRequirements.size; - allocInfo.memoryTypeIndex = memoryManager->FindMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + uint32 memIndex; + bool foundMemory = memoryManager->FindMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, memIndex); + if(!foundMemory) + { + cemuLog_log(LogType::Force, "Screenshot request failed due to incompatible vulkan memory types."); + return; + } + allocInfo.memoryTypeIndex = memIndex; if (vkAllocateMemory(m_logicalDevice, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS) { @@ -1608,6 +1645,7 @@ void VulkanRenderer::Initialize() void VulkanRenderer::Shutdown() { + DeleteFontTextures(); Renderer::Shutdown(); SubmitCommandBuffer(); WaitDeviceIdle(); @@ -1808,7 +1846,6 @@ void VulkanRenderer::ImguiEnd() vkCmdEndRenderPass(m_state.currentCommandBuffer); } -std::vector<LatteTextureVk*> g_imgui_textures; // TODO manage better ImTextureID VulkanRenderer::GenerateTexture(const std::vector<uint8>& data, const Vector2i& size) { try @@ -1838,6 +1875,7 @@ void VulkanRenderer::DeleteTexture(ImTextureID id) void VulkanRenderer::DeleteFontTextures() { + WaitDeviceIdle(); ImGui_ImplVulkan_DestroyFontsTexture(); } @@ -1876,7 +1914,7 @@ void VulkanRenderer::InitFirstCommandBuffer() vkResetFences(m_logicalDevice, 1, &m_cmd_buffer_fences[m_commandBufferIndex]); VkCommandBufferBeginInfo beginInfo{}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; vkBeginCommandBuffer(m_state.currentCommandBuffer, &beginInfo); vkCmdSetViewport(m_state.currentCommandBuffer, 0, 1, &m_state.currentViewport); @@ -1998,7 +2036,7 @@ void VulkanRenderer::SubmitCommandBuffer(VkSemaphore signalSemaphore, VkSemaphor VkCommandBufferBeginInfo beginInfo{}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; vkBeginCommandBuffer(m_state.currentCommandBuffer, &beginInfo); // make sure some states are set for this command buffer @@ -2519,9 +2557,8 @@ VkPipeline VulkanRenderer::backbufferBlit_createGraphicsPipeline(VkDescriptorSet hash += (uint64)(chainInfo.m_usesSRGB); hash += ((uint64)padView) << 1; - static std::unordered_map<uint64, VkPipeline> s_pipeline_cache; - const auto it = s_pipeline_cache.find(hash); - if (it != s_pipeline_cache.cend()) + const auto it = m_backbufferBlitPipelineCache.find(hash); + if (it != m_backbufferBlitPipelineCache.cend()) return it->second; std::vector<VkPipelineShaderStageCreateInfo> shaderStages; @@ -2625,7 +2662,7 @@ VkPipeline VulkanRenderer::backbufferBlit_createGraphicsPipeline(VkDescriptorSet throw std::runtime_error(fmt::format("Failed to create graphics pipeline: {}", result)); } - s_pipeline_cache[hash] = pipeline; + m_backbufferBlitPipelineCache[hash] = pipeline; m_pipeline_cache_semaphore.notify(); return pipeline; @@ -2922,9 +2959,6 @@ void VulkanRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu LatteTextureViewVk* texViewVk = (LatteTextureViewVk*)texView; draw_endRenderPass(); - if (clearBackground) - ClearColorbuffer(padView); - // barrier for input texture VkMemoryBarrier memoryBarrier{}; memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; @@ -2961,6 +2995,16 @@ void VulkanRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu vkCmdBeginRenderPass(m_state.currentCommandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + if (clearBackground) + { + VkClearAttachment clearAttachment{}; + clearAttachment.clearValue = {0,0,0,0}; + clearAttachment.colorAttachment = 0; + clearAttachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + VkClearRect clearExtent = {{{0,0},chainInfo.m_actualExtent}, 0, 1}; + vkCmdClearAttachments(m_state.currentCommandBuffer, 1, &clearAttachment, 1, &clearExtent); + } + vkCmdBindPipeline(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); m_state.currentPipeline = pipeline; @@ -3025,9 +3069,8 @@ VkDescriptorSet VulkanRenderer::backbufferBlit_createDescriptorSet(VkDescriptorS hash += (uint64)texViewVk->GetViewRGBA(); hash += (uint64)texViewVk->GetDefaultTextureSampler(useLinearTexFilter); - static std::unordered_map<uint64, VkDescriptorSet> s_set_cache; - const auto it = s_set_cache.find(hash); - if (it != s_set_cache.cend()) + const auto it = m_backbufferBlitDescriptorSetCache.find(hash); + if (it != m_backbufferBlitDescriptorSetCache.cend()) return it->second; VkDescriptorSetAllocateInfo allocInfo = {}; @@ -3058,7 +3101,7 @@ VkDescriptorSet VulkanRenderer::backbufferBlit_createDescriptorSet(VkDescriptorS vkUpdateDescriptorSets(m_logicalDevice, 1, &descriptorWrites, 0, nullptr); performanceMonitor.vk.numDescriptorSamplerTextures.increment(); - s_set_cache[hash] = result; + m_backbufferBlitDescriptorSetCache[hash] = result; return result; } @@ -3191,7 +3234,8 @@ VkDescriptorSetInfo::~VkDescriptorSetInfo() performanceMonitor.vk.numDescriptorDynUniformBuffers.decrement(statsNumDynUniformBuffers); performanceMonitor.vk.numDescriptorStorageBuffers.decrement(statsNumStorageBuffers); - VulkanRenderer::GetInstance()->ReleaseDestructibleObject(m_vkObjDescriptorSet); + auto renderer = VulkanRenderer::GetInstance(); + renderer->ReleaseDestructibleObject(m_vkObjDescriptorSet); m_vkObjDescriptorSet = nullptr; } diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index 5ef4558d..f1450487 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -137,8 +137,8 @@ class VulkanRenderer : public Renderer public: // memory management - VKRMemoryManager* memoryManager{}; - VKRMemoryManager* GetMemoryManager() const { return memoryManager; }; + std::unique_ptr<VKRMemoryManager> memoryManager; + VKRMemoryManager* GetMemoryManager() const { return memoryManager.get(); }; VkSupportedFormatInfo_t m_supportedFormatInfo; @@ -583,6 +583,8 @@ private: std::shared_mutex m_pipeline_cache_save_mutex; std::thread m_pipeline_cache_save_thread; VkPipelineCache m_pipeline_cache{ nullptr }; + std::unordered_map<uint64, VkPipeline> m_backbufferBlitPipelineCache; + std::unordered_map<uint64, VkDescriptorSet> m_backbufferBlitDescriptorSetCache; VkPipelineLayout m_pipelineLayout{nullptr}; VkCommandPool m_commandPool{ nullptr }; @@ -860,7 +862,7 @@ private: memBarrier.pNext = nullptr; VkPipelineStageFlags srcStages = VK_PIPELINE_STAGE_TRANSFER_BIT; - VkPipelineStageFlags dstStages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkPipelineStageFlags dstStages = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; memBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; memBarrier.dstAccessMask = 0; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp index 198a32cb..3e23b0aa 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp @@ -900,6 +900,7 @@ VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo* } } } + VKRObjectSampler* samplerObj = VKRObjectSampler::GetOrCreateSampler(&samplerInfo); vkObjDS->addRef(samplerObj); info.sampler = samplerObj->GetSampler(); @@ -1163,28 +1164,17 @@ void VulkanRenderer::draw_prepareDescriptorSets(PipelineInfo* pipeline_info, VkD const auto geometryShader = LatteSHRC_GetActiveGeometryShader(); const auto pixelShader = LatteSHRC_GetActivePixelShader(); - - if (vertexShader) - { - auto descriptorSetInfo = draw_getOrCreateDescriptorSet(pipeline_info, vertexShader); + auto prepareShaderDescriptors = [this, &pipeline_info](LatteDecompilerShader* shader) -> VkDescriptorSetInfo* { + if (!shader) + return nullptr; + auto descriptorSetInfo = draw_getOrCreateDescriptorSet(pipeline_info, shader); descriptorSetInfo->m_vkObjDescriptorSet->flagForCurrentCommandBuffer(); - vertexDS = descriptorSetInfo; - } + return descriptorSetInfo; + }; - if (pixelShader) - { - auto descriptorSetInfo = draw_getOrCreateDescriptorSet(pipeline_info, pixelShader); - descriptorSetInfo->m_vkObjDescriptorSet->flagForCurrentCommandBuffer(); - pixelDS = descriptorSetInfo; - - } - - if (geometryShader) - { - auto descriptorSetInfo = draw_getOrCreateDescriptorSet(pipeline_info, geometryShader); - descriptorSetInfo->m_vkObjDescriptorSet->flagForCurrentCommandBuffer(); - geometryDS = descriptorSetInfo; - } + vertexDS = prepareShaderDescriptors(vertexShader); + pixelDS = prepareShaderDescriptors(pixelShader); + geometryDS = prepareShaderDescriptors(geometryShader); } void VulkanRenderer::draw_updateVkBlendConstants() diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp index bf33ed90..f98eb452 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp @@ -76,6 +76,30 @@ struct CopySurfacePipelineInfo CopySurfacePipelineInfo() = default; CopySurfacePipelineInfo(VkDevice device) : m_device(device) {} CopySurfacePipelineInfo(const CopySurfacePipelineInfo& info) = delete; + ~CopySurfacePipelineInfo() + { + auto renderer = VulkanRenderer::GetInstance(); + renderer->ReleaseDestructibleObject(vkObjRenderPass); + renderer->ReleaseDestructibleObject(vkObjPipeline); + + for(auto& i : map_framebuffers) + { + for(auto& fb : i.second.m_array) + { + renderer->ReleaseDestructibleObject(fb->vkObjFramebuffer); + renderer->ReleaseDestructibleObject(fb->vkObjImageView); + } + } + + for(auto& i : map_descriptors) + { + for(auto& descriptor : i.second.m_array) + { + renderer->ReleaseDestructibleObject(descriptor->vkObjImageView); + renderer->ReleaseDestructibleObject(descriptor->vkObjDescriptorSet); + } + } + } VkDevice m_device = nullptr; @@ -842,5 +866,9 @@ void VulkanRenderer::surfaceCopy_notifyTextureRelease(LatteTextureVk* hostTextur void VulkanRenderer::surfaceCopy_cleanup() { - // todo - release m_copySurfacePipelineCache etc + for(auto& i : m_copySurfacePipelineCache) + { + delete i.second; + } + m_copySurfacePipelineCache = {}; } diff --git a/src/imgui/imgui_impl_vulkan.cpp b/src/imgui/imgui_impl_vulkan.cpp index 723f153c..f9a23166 100644 --- a/src/imgui/imgui_impl_vulkan.cpp +++ b/src/imgui/imgui_impl_vulkan.cpp @@ -465,6 +465,15 @@ void ImGui_ImplVulkan_DestroyFontsTexture() if (g_FontView) { vkDestroyImageView(v->Device, g_FontView, v->Allocator); g_FontView = VK_NULL_HANDLE; } if (g_FontImage) { vkDestroyImage(v->Device, g_FontImage, v->Allocator); g_FontImage = VK_NULL_HANDLE; } if (g_FontMemory) { vkFreeMemory(v->Device, g_FontMemory, v->Allocator); g_FontMemory = VK_NULL_HANDLE; } + + ImGuiIO& io = ImGui::GetIO(); + auto texture = io.Fonts->TexID; + if(texture != (ImTextureID)nullptr) + { + ImGui_ImplVulkan_DeleteTexture(texture); + delete (ImGuiTexture*)texture; + io.Fonts->TexID = nullptr; + } } bool ImGui_ImplVulkan_CreateFontsTexture(VkCommandBuffer command_buffer) From c714e8cb6bbf86b3430cb94159374cf83ed52df4 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Thu, 30 Jan 2025 03:32:24 +0100 Subject: [PATCH 236/299] coreinit: Time to tick conversion is unsigned The result is treated as signed in most cases, but the calculation uses unsigned arithmetic. As a concrete example where this matters, DS VC passes -1 (2^64-1) to OSWaitEventWithTimeout which internally causes an overflow. But only with unsigned arithmetic this will result in a large positive number that behaves like the intended infinite timeout. With signed arithmetic the result is negative and the events will timeout immediately. --- src/Cafe/OS/libs/coreinit/coreinit_Synchronization.cpp | 8 +++----- src/Cafe/OS/libs/coreinit/coreinit_Time.h | 4 ++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Synchronization.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Synchronization.cpp index c144c384..e81cc577 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Synchronization.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Synchronization.cpp @@ -73,8 +73,6 @@ namespace coreinit } } - uint64 coreinit_getOSTime(); - bool OSWaitEventWithTimeout(OSEvent* event, uint64 timeout) { __OSLockScheduler(); @@ -95,14 +93,14 @@ namespace coreinit // workaround for a bad implementation in some Unity games (like Qube Directors Cut, see FEventWiiU::Wait) // where the the return value of OSWaitEventWithTimeout is ignored and instead the game measures the elapsed time to determine if a timeout occurred - timeout = timeout * 98ULL / 100ULL; // 98% (we want the function to return slightly before the actual timeout) + if (timeout < 0x00FFFFFFFFFFFFFFULL) + timeout = timeout * 98ULL / 100ULL; // 98% (we want the function to return slightly before the actual timeout) WaitEventWithTimeoutData data; data.thread = OSGetCurrentThread(); data.threadQueue = &event->threadQueue; data.hasTimeout = false; - - auto hostAlarm = coreinit::OSHostAlarmCreate(coreinit::coreinit_getOSTime() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout), 0, _OSWaitEventWithTimeoutHandler, &data); + auto hostAlarm = coreinit::OSHostAlarmCreate(OSGetTime() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout), 0, _OSWaitEventWithTimeoutHandler, &data); event->threadQueue.queueAndWait(OSGetCurrentThread()); coreinit::OSHostAlarmDestroy(hostAlarm); if (data.hasTimeout) diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Time.h b/src/Cafe/OS/libs/coreinit/coreinit_Time.h index 3aa92b99..37bd5f88 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Time.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_Time.h @@ -40,12 +40,12 @@ namespace coreinit inline TimerTicks ConvertNsToTimerTicks(uint64 ns) { - return ((GetTimerClock() / 31250LL) * ((TimerTicks)ns) / 32000LL); + return static_cast<TimerTicks>((static_cast<uint64>(GetTimerClock()) / 31250ULL) * (ns) / 32000ULL); } inline TimerTicks ConvertMsToTimerTicks(uint64 ms) { - return (TimerTicks)ms * GetTimerClock() / 1000LL; + return static_cast<TimerTicks>(ms * static_cast<uint64>(GetTimerClock()) / 1000ULL); } }; From ec2d7c086a3b2cc4f40897ae9978d4699e273b02 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Thu, 30 Jan 2025 03:49:17 +0100 Subject: [PATCH 237/299] coreinit: Clean up time functions --- .../Espresso/Recompiler/PPCRecompilerX64.cpp | 4 +-- .../HW/Latte/Core/LatteCommandProcessor.cpp | 2 +- src/Cafe/IOSU/legacy/iosu_acp.cpp | 6 ++-- src/Cafe/OS/libs/camera/camera.cpp | 2 +- src/Cafe/OS/libs/coreinit/coreinit_Alarm.cpp | 8 ++--- .../OS/libs/coreinit/coreinit_Spinlock.cpp | 8 ++--- src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp | 2 +- src/Cafe/OS/libs/coreinit/coreinit_Time.cpp | 31 +++++++------------ src/Cafe/OS/libs/coreinit/coreinit_Time.h | 12 +++---- src/Cafe/OS/libs/dmae/dmae.cpp | 2 +- src/Cafe/OS/libs/gx2/GX2.cpp | 2 +- src/Cafe/OS/libs/gx2/GX2_Misc.cpp | 2 +- src/Cafe/OS/libs/nn_acp/nn_acp.cpp | 2 +- src/Cafe/OS/libs/padscore/padscore.cpp | 2 +- src/Cafe/OS/libs/vpad/vpad.cpp | 4 +-- .../dialogs/SaveImport/SaveImportWindow.cpp | 2 +- 16 files changed, 40 insertions(+), 51 deletions(-) diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64.cpp index a30295b5..97b2c14c 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64.cpp @@ -114,13 +114,13 @@ void* ATTR_MS_ABI PPCRecompiler_virtualHLE(PPCInterpreter_t* hCPU, uint32 hleFun void ATTR_MS_ABI PPCRecompiler_getTBL(PPCInterpreter_t* hCPU, uint32 gprIndex) { - uint64 coreTime = coreinit::coreinit_getTimerTick(); + uint64 coreTime = coreinit::OSGetSystemTime(); hCPU->gpr[gprIndex] = (uint32)(coreTime&0xFFFFFFFF); } void ATTR_MS_ABI PPCRecompiler_getTBU(PPCInterpreter_t* hCPU, uint32 gprIndex) { - uint64 coreTime = coreinit::coreinit_getTimerTick(); + uint64 coreTime = coreinit::OSGetSystemTime(); hCPU->gpr[gprIndex] = (uint32)((coreTime>>32)&0xFFFFFFFF); } diff --git a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp index a8f81901..f592cc9e 100644 --- a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp +++ b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp @@ -799,7 +799,7 @@ LatteCMDPtr LatteCP_itHLESampleTimer(LatteCMDPtr cmd, uint32 nWords) { cemu_assert_debug(nWords == 1); MPTR timerMPTR = (MPTR)LatteReadCMD(); - memory_writeU64(timerMPTR, coreinit::coreinit_getTimerTick()); + memory_writeU64(timerMPTR, coreinit::OSGetSystemTime()); return cmd; } diff --git a/src/Cafe/IOSU/legacy/iosu_acp.cpp b/src/Cafe/IOSU/legacy/iosu_acp.cpp index f5144ee6..6a9e6b89 100644 --- a/src/Cafe/IOSU/legacy/iosu_acp.cpp +++ b/src/Cafe/IOSU/legacy/iosu_acp.cpp @@ -469,7 +469,7 @@ namespace iosu entry->ukn0C = 0; entry->sizeA = _swapEndianU64(0); // ukn entry->sizeB = _swapEndianU64(dirSize); - entry->time = _swapEndianU64((coreinit::coreinit_getOSTime() / ESPRESSO_TIMER_CLOCK)); + entry->time = _swapEndianU64((coreinit::OSGetTime() / ESPRESSO_TIMER_CLOCK)); sprintf(entry->path, "%susr/save/%08x/%08x/meta/", devicePath, (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); count++; } @@ -504,7 +504,7 @@ namespace iosu entry->ukn0C = 0; entry->sizeA = _swapEndianU64(0); entry->sizeB = _swapEndianU64(0); - entry->time = _swapEndianU64((coreinit::coreinit_getOSTime() / ESPRESSO_TIMER_CLOCK)); + entry->time = _swapEndianU64((coreinit::OSGetTime() / ESPRESSO_TIMER_CLOCK)); sprintf(entry->path, "%susr/save/%08x/%08x/meta/", devicePath, (uint32)(titleId >> 32), (uint32)(titleId & 0xFFFFFFFF)); count++; } @@ -584,7 +584,7 @@ namespace iosu uint64 _ACPGetTimestamp() { - return coreinit::coreinit_getOSTime() / ESPRESSO_TIMER_CLOCK; + return coreinit::OSGetTime() / ESPRESSO_TIMER_CLOCK; } nnResult ACPUpdateSaveTimeStamp(uint32 persistentId, uint64 titleId, ACPDeviceType deviceType) diff --git a/src/Cafe/OS/libs/camera/camera.cpp b/src/Cafe/OS/libs/camera/camera.cpp index 03e01bfc..efb8013d 100644 --- a/src/Cafe/OS/libs/camera/camera.cpp +++ b/src/Cafe/OS/libs/camera/camera.cpp @@ -186,7 +186,7 @@ namespace camera if (g_cameraCounter == 0) { coreinit::OSCreateAlarm(g_alarm_camera.GetPtr()); - coreinit::OSSetPeriodicAlarm(g_alarm_camera.GetPtr(), coreinit::coreinit_getOSTime(), (uint64)ESPRESSO_TIMER_CLOCK / 60ull, RPLLoader_MakePPCCallable(ppcCAMUpdate60)); + coreinit::OSSetPeriodicAlarm(g_alarm_camera.GetPtr(), coreinit::OSGetTime(), (uint64)ESPRESSO_TIMER_CLOCK / 60ull, RPLLoader_MakePPCCallable(ppcCAMUpdate60)); } g_cameraCounter++; diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Alarm.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Alarm.cpp index f7e58115..ae2d1e63 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Alarm.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Alarm.cpp @@ -166,7 +166,7 @@ namespace coreinit void alarm_update() { cemu_assert_debug(!__OSHasSchedulerLock()); - uint64 currentTick = coreinit::coreinit_getOSTime(); + uint64 currentTick = coreinit::OSGetTime(); if (!OSHostAlarm::quickCheckForAlarm(currentTick)) return; __OSLockScheduler(); @@ -233,7 +233,7 @@ namespace coreinit if (period == 0) return; - uint64 currentTime = coreinit_getOSTime(); + uint64 currentTime = OSGetTime(); uint64 ticksSinceStart = currentTime - startTime; uint64 numPeriods = ticksSinceStart / period; @@ -267,7 +267,7 @@ namespace coreinit void OSSetAlarm(OSAlarm_t* alarm, uint64 delayInTicks, MPTR handlerFunc) { __OSLockScheduler(); - __OSInitiateAlarm(alarm, coreinit_getOSTime() + delayInTicks, 0, handlerFunc, false); + __OSInitiateAlarm(alarm, OSGetTime() + delayInTicks, 0, handlerFunc, false); __OSUnlockScheduler(); } @@ -310,7 +310,7 @@ namespace coreinit while( true ) { OSWaitEvent(g_alarmEvent.GetPtr()); - uint64 currentTick = coreinit_getOSTime(); + uint64 currentTick = OSGetTime(); while (true) { // get alarm to fire diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Spinlock.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Spinlock.cpp index 5201d441..3d235107 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Spinlock.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Spinlock.cpp @@ -86,11 +86,11 @@ namespace coreinit else { // loop until lock acquired or timeout occurred - uint64 timeoutValue = coreinit_getTimerTick() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout); + uint64 timeoutValue = OSGetSystemTime() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout); while (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread)) { OSYieldThread(); - if (coreinit_getTimerTick() >= timeoutValue) + if (OSGetSystemTime() >= timeoutValue) { return false; } @@ -182,11 +182,11 @@ namespace coreinit else { // loop until lock acquired or timeout occurred - uint64 timeoutValue = coreinit_getTimerTick() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout); + uint64 timeoutValue = OSGetSystemTime() + coreinit::EspressoTime::ConvertNsToTimerTicks(timeout); while (!spinlock->ownerThread.atomic_compare_exchange(nullptr, currentThread)) { OSYieldThread(); - if (coreinit_getTimerTick() >= timeoutValue) + if (OSGetSystemTime() >= timeoutValue) { return false; } diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp index db457047..870d1850 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp @@ -655,7 +655,7 @@ namespace coreinit StackAllocator<OSThreadQueue> _threadQueue; OSInitThreadQueue(_threadQueue.GetPointer()); __OSLockScheduler(); - OSHostAlarm* hostAlarm = OSHostAlarmCreate(coreinit_getOSTime() + ticks, 0, _OSSleepTicks_alarmHandler, _threadQueue.GetPointer()); + OSHostAlarm* hostAlarm = OSHostAlarmCreate(OSGetTime() + ticks, 0, _OSSleepTicks_alarmHandler, _threadQueue.GetPointer()); _threadQueue.GetPointer()->queueAndWait(OSGetCurrentThread()); OSHostAlarmDestroy(hostAlarm); __OSUnlockScheduler(); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Time.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Time.cpp index d6fc27b2..50a404f4 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Time.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Time.cpp @@ -3,38 +3,32 @@ namespace coreinit { - - uint64 coreinit_getTimerTick() + uint64 coreinit_GetMFTB() { // bus clock is 1/5th of core clock // timer clock is 1/4th of bus clock return PPCInterpreter_getMainCoreCycleCounter() / 20ULL; } - uint64 coreinit_getOSTime() + uint64 OSGetSystemTime() { - return coreinit_getTimerTick() + ppcCyclesSince2000TimerClock; - } - - void export_OSGetTick(PPCInterpreter_t* hCPU) - { - uint64 osTime = coreinit_getOSTime(); - osLib_returnFromFunction(hCPU, (uint32)osTime); + return coreinit_GetMFTB(); } uint64 OSGetTime() { - return coreinit_getOSTime(); + return OSGetSystemTime() + ppcCyclesSince2000TimerClock; } - void export_OSGetSystemTime(PPCInterpreter_t* hCPU) + uint32 OSGetSystemTick() { - osLib_returnFromFunction64(hCPU, coreinit_getTimerTick()); + return static_cast<uint32>(coreinit_GetMFTB()); } - void export_OSGetSystemTick(PPCInterpreter_t* hCPU) + uint32 OSGetTick() { - osLib_returnFromFunction(hCPU, (uint32)coreinit_getTimerTick()); + uint64 osTime = OSGetTime(); + return static_cast<uint32>(osTime); } uint32 getLeapDaysUntilYear(uint32 year) @@ -360,14 +354,13 @@ namespace coreinit void InitializeTimeAndCalendar() { cafeExportRegister("coreinit", OSGetTime, LogType::Placeholder); - osLib_addFunction("coreinit", "OSGetSystemTime", export_OSGetSystemTime); - osLib_addFunction("coreinit", "OSGetTick", export_OSGetTick); - osLib_addFunction("coreinit", "OSGetSystemTick", export_OSGetSystemTick); + cafeExportRegister("coreinit", OSGetSystemTime, LogType::Placeholder); + cafeExportRegister("coreinit", OSGetTick, LogType::Placeholder); + cafeExportRegister("coreinit", OSGetSystemTick, LogType::Placeholder); cafeExportRegister("coreinit", OSTicksToCalendarTime, LogType::Placeholder); cafeExportRegister("coreinit", OSCalendarTimeToTicks, LogType::Placeholder); - //timeTest(); } }; \ No newline at end of file diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Time.h b/src/Cafe/OS/libs/coreinit/coreinit_Time.h index 37bd5f88..380ccf1d 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Time.h +++ b/src/Cafe/OS/libs/coreinit/coreinit_Time.h @@ -50,15 +50,11 @@ namespace coreinit }; void OSTicksToCalendarTime(uint64 ticks, OSCalendarTime_t* calenderStruct); + + uint64 OSGetSystemTime(); uint64 OSGetTime(); - - uint64 coreinit_getOSTime(); - uint64 coreinit_getTimerTick(); - - static uint64 OSGetSystemTime() - { - return coreinit_getTimerTick(); - } + uint32 OSGetSystemTick(); + uint32 OSGetTick(); void InitializeTimeAndCalendar(); }; diff --git a/src/Cafe/OS/libs/dmae/dmae.cpp b/src/Cafe/OS/libs/dmae/dmae.cpp index 6b3e8d0d..7c513784 100644 --- a/src/Cafe/OS/libs/dmae/dmae.cpp +++ b/src/Cafe/OS/libs/dmae/dmae.cpp @@ -11,7 +11,7 @@ uint64 dmaeRetiredTimestamp = 0; uint64 dmae_getTimestamp() { - return coreinit::coreinit_getTimerTick(); + return coreinit::OSGetSystemTime(); } void dmae_setRetiredTimestamp(uint64 timestamp) diff --git a/src/Cafe/OS/libs/gx2/GX2.cpp b/src/Cafe/OS/libs/gx2/GX2.cpp index c2ea34a4..593d31fb 100644 --- a/src/Cafe/OS/libs/gx2/GX2.cpp +++ b/src/Cafe/OS/libs/gx2/GX2.cpp @@ -322,7 +322,7 @@ uint64 _prevReturnedGPUTime = 0; uint64 Latte_GetTime() { - uint64 gpuTime = coreinit::coreinit_getTimerTick(); + uint64 gpuTime = coreinit::OSGetSystemTime(); gpuTime *= 20000ULL; if (gpuTime <= _prevReturnedGPUTime) gpuTime = _prevReturnedGPUTime + 1; // avoid ever returning identical timestamps diff --git a/src/Cafe/OS/libs/gx2/GX2_Misc.cpp b/src/Cafe/OS/libs/gx2/GX2_Misc.cpp index 2111238a..3c7ea3f9 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Misc.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_Misc.cpp @@ -54,7 +54,7 @@ void gx2Export_GX2GetGPUTimeout(PPCInterpreter_t* hCPU) void gx2Export_GX2SampleTopGPUCycle(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2SampleTopGPUCycle(0x{:08x})", hCPU->gpr[3]); - memory_writeU64(hCPU->gpr[3], coreinit::coreinit_getTimerTick()); + memory_writeU64(hCPU->gpr[3], coreinit::OSGetSystemTime()); osLib_returnFromFunction(hCPU, 0); } diff --git a/src/Cafe/OS/libs/nn_acp/nn_acp.cpp b/src/Cafe/OS/libs/nn_acp/nn_acp.cpp index 37ea471f..9cde0213 100644 --- a/src/Cafe/OS/libs/nn_acp/nn_acp.cpp +++ b/src/Cafe/OS/libs/nn_acp/nn_acp.cpp @@ -315,7 +315,7 @@ namespace acp ppcDefineParamU32BEPtr(timestamp64, 0); ppcDefineParamU32BEPtr(ukn, 1); // probably timezone or offset? Could also be a bool for success/failed - uint64 t = coreinit::coreinit_getOSTime() + (uint64)((sint64)(ppcCyclesSince2000_UTC - ppcCyclesSince2000) / 20LL); + uint64 t = coreinit::OSGetTime() + (uint64)((sint64)(ppcCyclesSince2000_UTC - ppcCyclesSince2000) / 20LL); timestamp64[0] = (uint32)(t >> 32); timestamp64[1] = (uint32)(t & 0xFFFFFFFF); diff --git a/src/Cafe/OS/libs/padscore/padscore.cpp b/src/Cafe/OS/libs/padscore/padscore.cpp index 0a577b97..2f359748 100644 --- a/src/Cafe/OS/libs/padscore/padscore.cpp +++ b/src/Cafe/OS/libs/padscore/padscore.cpp @@ -760,7 +760,7 @@ namespace padscore void start() { OSCreateAlarm(&g_padscore.alarm); - const uint64 start_tick = coreinit::coreinit_getOSTime(); + const uint64 start_tick = coreinit::OSGetTime(); const uint64 period_tick = coreinit::EspressoTime::GetTimerClock() / 200; // every 5ms MPTR handler = PPCInterpreter_makeCallableExportDepr(TickFunction); OSSetPeriodicAlarm(&g_padscore.alarm, start_tick, period_tick, handler); diff --git a/src/Cafe/OS/libs/vpad/vpad.cpp b/src/Cafe/OS/libs/vpad/vpad.cpp index ded4304d..21c1c9e5 100644 --- a/src/Cafe/OS/libs/vpad/vpad.cpp +++ b/src/Cafe/OS/libs/vpad/vpad.cpp @@ -267,7 +267,7 @@ namespace vpad { if (channel <= 1 && vpadDelayEnabled) { - uint64 currentTime = coreinit::coreinit_getOSTime(); + uint64 currentTime = coreinit::OSGetTime(); const auto dif = currentTime - vpad::g_vpad.controller_data[channel].drcLastCallTime; if (dif <= (ESPRESSO_TIMER_CLOCK / 60ull)) { @@ -1149,7 +1149,7 @@ namespace vpad void start() { coreinit::OSCreateAlarm(&g_vpad.alarm); - const uint64 start_tick = coreinit::coreinit_getOSTime(); + const uint64 start_tick = coreinit::OSGetTime(); const uint64 period_tick = coreinit::EspressoTime::GetTimerClock() * 5 / 1000; const MPTR handler = PPCInterpreter_makeCallableExportDepr(TickFunction); coreinit::OSSetPeriodicAlarm(&g_vpad.alarm, start_tick, period_tick, handler); diff --git a/src/gui/dialogs/SaveImport/SaveImportWindow.cpp b/src/gui/dialogs/SaveImport/SaveImportWindow.cpp index b31f24b2..1b1fecbf 100644 --- a/src/gui/dialogs/SaveImport/SaveImportWindow.cpp +++ b/src/gui/dialogs/SaveImport/SaveImportWindow.cpp @@ -304,7 +304,7 @@ void SaveImportWindow::OnImport(wxCommandEvent& event) 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? + timestamp.text().set(fmt::format("{:016x}", coreinit::OSGetTime() / ESPRESSO_TIMER_CLOCK).c_str()); // TODO time not initialized yet? if(!doc.save_file(saveinfo.c_str())) cemuLog_log(LogType::Force, "couldn't insert save entry in saveinfo.xml: {}", _pathToUtf8(saveinfo)); From a6fb0a48eb437a8a41c13b782ac8ae0433bf8f98 Mon Sep 17 00:00:00 2001 From: capitalistspz <keipitalists@proton.me> Date: Tue, 4 Feb 2025 09:56:33 +0000 Subject: [PATCH 238/299] BUILD.md: Provide more info about build configuration flags (#1486) --- BUILD.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/BUILD.md b/BUILD.md index 662be96d..31c26531 100644 --- a/BUILD.md +++ b/BUILD.md @@ -192,3 +192,41 @@ Then install the dependencies: If CMake complains about Cemu already being compiled or another similar error, try deleting the `CMakeCache.txt` file inside the `build` folder and retry building. +## CMake configure flags +Some flags can be passed during CMake configure to customise which features are enabled on build. + +Example usage: `cmake -S . -B build -DCMAKE_BUILD_TYPE=release -DENABLE_SDL=ON -DENABLE_VULKAN=OFF` + +### All platforms +| Flag | | Description | Default | Note | +|--------------------|:--|-----------------------------------------------------------------------------|---------|--------------------| +| ALLOW_PORTABLE | | Allow Cemu to use the `portable` directory to store configs and data | ON | | +| CEMU_CXX_FLAGS | | Flags passed straight to the compiler, e.g. `-march=native`, `-Wall`, `/W3` | "" | | +| ENABLE_CUBEB | | Enable cubeb audio backend | ON | | +| ENABLE_DISCORD_RPC | | Enable Discord Rich presence support | ON | | +| ENABLE_OPENGL | | Enable OpenGL graphics backend | ON | Currently required | +| ENABLE_HIDAPI | | Enable HIDAPI (used for Wiimote controller API) | ON | | +| ENABLE_SDL | | Enable SDLController controller API | ON | Currently required | +| ENABLE_VCPKG | | Use VCPKG package manager to obtain dependencies | ON | | +| ENABLE_VULKAN | | Enable the Vulkan graphics backend | ON | | +| ENABLE_WXWIDGETS | | Enable wxWidgets UI | ON | Currently required | + +### Windows +| Flag | Description | Default | Note | +|--------------------|-----------------------------------|---------|--------------------| +| ENABLE_DIRECTAUDIO | Enable DirectAudio audio backend | ON | Currently required | +| ENABLE_DIRECTINPUT | Enable DirectInput controller API | ON | Currently required | +| ENABLE_XAUDIO | Enable XAudio audio backend | ON | | +| ENABLE_XINPUT | Enable XInput controller API | ON | | + +### Linux +| Flag | Description | Default | +|-----------------------|----------------------------------------------------|---------| +| ENABLE_BLUEZ | Build with Bluez (used for Wiimote controller API) | ON | +| ENABLE_FERAL_GAMEMODE | Enable Feral Interactive GameMode support | ON | +| ENABLE_WAYLAND | Enable Wayland support | ON | + +### macOS +| Flag | Description | Default | +|--------------|------------------------------------------------|---------| +| MACOS_BUNDLE | MacOS executable will be an application bundle | OFF | From ebb5ab53e23b94961d2340d359fb04d8416b1d1d Mon Sep 17 00:00:00 2001 From: capitalistspz <keipitalists@proton.me> Date: Fri, 14 Feb 2025 19:56:51 +0000 Subject: [PATCH 239/299] Add menu item for opening shader cache directory (#1494) --- src/gui/MainWindow.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 4801706a..48bdd7d7 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -77,6 +77,7 @@ enum MAINFRAME_MENU_ID_FILE_INSTALL_UPDATE, MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER, MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER, + MAINFRAME_MENU_ID_FILE_OPEN_SHADERCACHE_FOLDER, MAINFRAME_MENU_ID_FILE_EXIT, MAINFRAME_MENU_ID_FILE_END_EMULATION, MAINFRAME_MENU_ID_FILE_RECENT_0, @@ -169,6 +170,7 @@ EVT_MENU(MAINFRAME_MENU_ID_FILE_LOAD, MainWindow::OnFileMenu) EVT_MENU(MAINFRAME_MENU_ID_FILE_INSTALL_UPDATE, MainWindow::OnInstallUpdate) EVT_MENU(MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER, MainWindow::OnOpenFolder) EVT_MENU(MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER, MainWindow::OnOpenFolder) +EVT_MENU(MAINFRAME_MENU_ID_FILE_OPEN_SHADERCACHE_FOLDER, MainWindow::OnOpenFolder) EVT_MENU(MAINFRAME_MENU_ID_FILE_EXIT, MainWindow::OnFileExit) EVT_MENU(MAINFRAME_MENU_ID_FILE_END_EMULATION, MainWindow::OnFileMenu) EVT_MENU_RANGE(MAINFRAME_MENU_ID_FILE_RECENT_0 + 0, MAINFRAME_MENU_ID_FILE_RECENT_LAST, MainWindow::OnFileMenu) @@ -673,10 +675,15 @@ void MainWindow::OnFileMenu(wxCommandEvent& event) void MainWindow::OnOpenFolder(wxCommandEvent& event) { - if(event.GetId() == MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER) + const auto id = event.GetId(); + if(id == MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER) wxLaunchDefaultApplication(wxHelper::FromPath(ActiveSettings::GetUserDataPath())); - else if(event.GetId() == MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER) + else if(id == MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER) wxLaunchDefaultApplication(wxHelper::FromPath(ActiveSettings::GetMlcPath())); + else if (id == MAINFRAME_MENU_ID_FILE_OPEN_SHADERCACHE_FOLDER) + wxLaunchDefaultApplication(wxHelper::FromPath(ActiveSettings::GetCachePath("shaderCache"))); + + } void MainWindow::OnInstallUpdate(wxCommandEvent& event) @@ -2099,6 +2106,7 @@ void MainWindow::RecreateMenu() m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_OPEN_CEMU_FOLDER, _("&Open Cemu folder")); m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_OPEN_MLC_FOLDER, _("&Open MLC folder")); + m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_OPEN_SHADERCACHE_FOLDER, _("Open &shader cache folder")); m_fileMenu->AppendSeparator(); m_exitMenuItem = m_fileMenu->Append(MAINFRAME_MENU_ID_FILE_EXIT, _("&Exit")); From 31d2db6f78a72feda9e3804060eb4364188c8553 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Tue, 4 Mar 2025 17:58:18 +0100 Subject: [PATCH 240/299] OpenGL: Add explicit/matching qualifiers in output shader interface fixes issues with old intel drivers --- src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp b/src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp index 3a00c36a..afe53a16 100644 --- a/src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp +++ b/src/Cafe/HW/Latte/Renderer/RendererOuputShader.cpp @@ -187,8 +187,8 @@ std::string RendererOutputShader::GetOpenGlVertexSource(bool render_upside_down) // vertex shader std::ostringstream vertex_source; vertex_source << - R"(#version 400 -out vec2 passUV; + R"(#version 420 +layout(location = 0) smooth out vec2 passUV; out gl_PerVertex { @@ -297,7 +297,7 @@ uniform vec2 nativeResolution; uniform vec2 outputResolution; #endif -layout(location = 0) in vec2 passUV; +layout(location = 0) smooth in vec2 passUV; layout(binding = 0) uniform sampler2D textureSrc; layout(location = 0) out vec4 colorOut0; )" + shaderSrc; From 186e92221a387ccad76cdfda661cd1bd94d82429 Mon Sep 17 00:00:00 2001 From: Crementif <26669564+Crementif@users.noreply.github.com> Date: Fri, 7 Mar 2025 23:40:17 +0100 Subject: [PATCH 241/299] debugger: allow printing registers using logging breakpoint placeholders (#1510) This allows a savy user, developer or modder to change the comment field of a logging breakpoint to include placeholders such as {r3} or {f3} to log the register values whenever that code is hit. --- src/Cafe/HW/Espresso/Debugger/Debugger.cpp | 68 +++++++++++++++++++--- src/Cafe/HW/Espresso/Debugger/Debugger.h | 2 +- src/gui/debugger/BreakpointWindow.cpp | 12 ++-- src/gui/debugger/DisasmCtrl.cpp | 12 +++- src/gui/debugger/DisasmCtrl.h | 1 + 5 files changed, 78 insertions(+), 17 deletions(-) diff --git a/src/Cafe/HW/Espresso/Debugger/Debugger.cpp b/src/Cafe/HW/Espresso/Debugger/Debugger.cpp index 37e374d6..e84c9fda 100644 --- a/src/Cafe/HW/Espresso/Debugger/Debugger.cpp +++ b/src/Cafe/HW/Espresso/Debugger/Debugger.cpp @@ -8,6 +8,7 @@ #include "gui/debugger/DebuggerWindow2.h" #include "Cafe/OS/libs/coreinit/coreinit.h" +#include "util/helpers/helpers.h" #if BOOST_OS_WINDOWS #include <Windows.h> @@ -136,11 +137,6 @@ void debugger_createCodeBreakpoint(uint32 address, uint8 bpType) debugger_updateExecutionBreakpoint(address); } -void debugger_createExecuteBreakpoint(uint32 address) -{ - debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_NORMAL); -} - namespace coreinit { std::vector<std::thread::native_handle_type>& OSGetSchedulerThreads(); @@ -294,8 +290,23 @@ void debugger_toggleExecuteBreakpoint(uint32 address) } else { - // create new breakpoint - debugger_createExecuteBreakpoint(address); + // create new execution breakpoint + debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_NORMAL); + } +} + +void debugger_toggleLoggingBreakpoint(uint32 address) +{ + auto existingBP = debugger_getFirstBP(address, DEBUGGER_BP_T_LOGGING); + if (existingBP) + { + // delete existing breakpoint + debugger_deleteBreakpoint(existingBP); + } + else + { + // create new logging breakpoint + debugger_createCodeBreakpoint(address, DEBUGGER_BP_T_LOGGING); } } @@ -538,7 +549,48 @@ void debugger_enterTW(PPCInterpreter_t* hCPU) { if (bp->bpType == DEBUGGER_BP_T_LOGGING && bp->enabled) { - std::string logName = !bp->comment.empty() ? "Breakpoint '"+boost::nowide::narrow(bp->comment)+"'" : fmt::format("Breakpoint at 0x{:08X} (no comment)", bp->address); + std::string comment = !bp->comment.empty() ? boost::nowide::narrow(bp->comment) : fmt::format("Breakpoint at 0x{:08X} (no comment)", bp->address); + + auto replacePlaceholders = [&](const std::string& prefix, const auto& formatFunc) + { + size_t pos = 0; + while ((pos = comment.find(prefix, pos)) != std::string::npos) + { + size_t endPos = comment.find('}', pos); + if (endPos == std::string::npos) + break; + + try + { + if (int regNum = ConvertString<int>(comment.substr(pos + prefix.length(), endPos - pos - prefix.length())); regNum >= 0 && regNum < 32) + { + std::string replacement = formatFunc(regNum); + comment.replace(pos, endPos - pos + 1, replacement); + pos += replacement.length(); + } + else + { + pos = endPos + 1; + } + } + catch (...) + { + pos = endPos + 1; + } + } + }; + + // Replace integer register placeholders {rX} + replacePlaceholders("{r", [&](int regNum) { + return fmt::format("0x{:08X}", hCPU->gpr[regNum]); + }); + + // Replace floating point register placeholders {fX} + replacePlaceholders("{f", [&](int regNum) { + return fmt::format("{}", hCPU->fpr[regNum].fpr); + }); + + std::string logName = "Breakpoint '" + comment + "'"; std::string logContext = fmt::format("Thread: {:08x} LR: 0x{:08x}", MEMPTR<OSThread_t>(coreinit::OSGetCurrentThread()).GetMPTR(), hCPU->spr.LR, cemuLog_advancedPPCLoggingEnabled() ? " Stack Trace:" : ""); cemuLog_log(LogType::Force, "[Debugger] {} was executed! {}", logName, logContext); if (cemuLog_advancedPPCLoggingEnabled()) diff --git a/src/Cafe/HW/Espresso/Debugger/Debugger.h b/src/Cafe/HW/Espresso/Debugger/Debugger.h index 249c47b8..c220eb8a 100644 --- a/src/Cafe/HW/Espresso/Debugger/Debugger.h +++ b/src/Cafe/HW/Espresso/Debugger/Debugger.h @@ -100,8 +100,8 @@ extern debuggerState_t debuggerState; // new API DebuggerBreakpoint* debugger_getFirstBP(uint32 address); void debugger_createCodeBreakpoint(uint32 address, uint8 bpType); -void debugger_createExecuteBreakpoint(uint32 address); void debugger_toggleExecuteBreakpoint(uint32 address); // create/remove execute breakpoint +void debugger_toggleLoggingBreakpoint(uint32 address); // create/remove logging breakpoint void debugger_toggleBreakpoint(uint32 address, bool state, DebuggerBreakpoint* bp); void debugger_createMemoryBreakpoint(uint32 address, bool onRead, bool onWrite); diff --git a/src/gui/debugger/BreakpointWindow.cpp b/src/gui/debugger/BreakpointWindow.cpp index 63b92626..658a51ad 100644 --- a/src/gui/debugger/BreakpointWindow.cpp +++ b/src/gui/debugger/BreakpointWindow.cpp @@ -202,14 +202,14 @@ void BreakpointWindow::OnLeftDClick(wxMouseEvent& event) 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) + const wxString dialogTitle = (*it)->bpType == DEBUGGER_BP_T_LOGGING ? _("Enter a new logging message") : _("Enter a new comment"); + const wxString dialogMessage = (*it)->bpType == DEBUGGER_BP_T_LOGGING ? _("Set logging message when code at address %08x is ran.\nUse placeholders like {r3} or {f3} to log register values") : _("Set comment for breakpoint at address %08x"); + wxTextEntryDialog set_comment_dialog(this, dialogMessage, dialogTitle, (*it)->comment); + if (set_comment_dialog.ShowModal() == wxID_OK) { - (*it)->comment = set_value_dialog.GetValue().ToStdWstring(); - m_breakpoints->SetItem(index, ColumnComment, set_value_dialog.GetValue()); + (*it)->comment = set_comment_dialog.GetValue().ToStdWstring(); + m_breakpoints->SetItem(index, ColumnComment, set_comment_dialog.GetValue()); } - - return; } } diff --git a/src/gui/debugger/DisasmCtrl.cpp b/src/gui/debugger/DisasmCtrl.cpp index 2f38d55e..e74d64b9 100644 --- a/src/gui/debugger/DisasmCtrl.cpp +++ b/src/gui/debugger/DisasmCtrl.cpp @@ -538,7 +538,7 @@ void DisasmCtrl::OnKeyPressed(sint32 key_code, const wxPoint& position) auto optVirtualAddress = LinePixelPosToAddress(position.y); switch (key_code) { - case WXK_F9: + case WXK_F9: { if (optVirtualAddress) { @@ -549,7 +549,7 @@ void DisasmCtrl::OnKeyPressed(sint32 key_code, const wxPoint& position) } return; } - case 'G': + case 'G': { if(IsKeyDown(WXK_CONTROL)) { @@ -686,6 +686,7 @@ void DisasmCtrl::OnContextMenu(const wxPoint& position, uint32 line) // show dialog wxMenu menu; menu.Append(IDContextMenu_ToggleBreakpoint, _("Toggle breakpoint")); + menu.Append(IDContextMenu_ToggleLoggingBreakpoint, _("Toggle logging point")); if(debugger_hasPatch(virtualAddress)) menu.Append(IDContextMenu_RestoreOriginalInstructions, _("Restore original instructions")); menu.AppendSeparator(); @@ -707,6 +708,13 @@ void DisasmCtrl::OnContextMenuEntryClicked(wxCommandEvent& event) wxPostEvent(this->m_parent, evt); break; } + case IDContextMenu_ToggleLoggingBreakpoint: + { + debugger_toggleLoggingBreakpoint(m_contextMenuAddress); + wxCommandEvent evt(wxEVT_BREAKPOINT_CHANGE); + wxPostEvent(this->m_parent, evt); + break; + } case IDContextMenu_RestoreOriginalInstructions: { debugger_removePatch(m_contextMenuAddress); diff --git a/src/gui/debugger/DisasmCtrl.h b/src/gui/debugger/DisasmCtrl.h index 5a67e49a..b526e8f9 100644 --- a/src/gui/debugger/DisasmCtrl.h +++ b/src/gui/debugger/DisasmCtrl.h @@ -8,6 +8,7 @@ class DisasmCtrl : public TextList enum { IDContextMenu_ToggleBreakpoint = wxID_HIGHEST + 1, + IDContextMenu_ToggleLoggingBreakpoint, IDContextMenu_RestoreOriginalInstructions, IDContextMenu_CopyAddress, IDContextMenu_CopyUnrelocatedAddress, From 8b5cafa98e7149399b3fcffa8746f40bbec18d86 Mon Sep 17 00:00:00 2001 From: capitalistspz <keipitalists@proton.me> Date: Thu, 13 Mar 2025 00:09:45 +0000 Subject: [PATCH 242/299] Wiimote/L2CAP: More accurate descriptions for descriptors (#1512) --- src/input/api/Wiimote/l2cap/L2CapWiimote.cpp | 62 ++++++++++---------- src/input/api/Wiimote/l2cap/L2CapWiimote.h | 6 +- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp b/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp index 28a123f3..a6bdf574 100644 --- a/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp +++ b/src/input/api/Wiimote/l2cap/L2CapWiimote.cpp @@ -23,15 +23,15 @@ static bool AttemptSetNonBlock(int sockFd) return fcntl(sockFd, F_SETFL, fcntl(sockFd, F_GETFL) | O_NONBLOCK) == 0; } -L2CapWiimote::L2CapWiimote(int recvFd, int sendFd, bdaddr_t addr) - : m_recvFd(recvFd), m_sendFd(sendFd), m_addr(addr) +L2CapWiimote::L2CapWiimote(int controlFd, int dataFd, bdaddr_t addr) + : m_controlFd(controlFd), m_dataFd(dataFd), m_addr(addr) { } L2CapWiimote::~L2CapWiimote() { - close(m_recvFd); - close(m_sendFd); + close(m_dataFd); + close(m_controlFd); const auto& b = m_addr.b; cemuLog_logDebug(LogType::Force, "Wiimote at {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x} disconnected", b[5], b[4], b[3], b[2], b[1], b[0]); @@ -61,51 +61,51 @@ std::vector<WiimoteDevicePtr> L2CapWiimote::get_devices() std::vector<WiimoteDevicePtr> outDevices; for (const auto& addr : unconnected) { - // Socket for sending data to controller, PSM 0x11 - auto sendFd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); - if (sendFd < 0) + // Control socket, PSM 0x11, needs to be open for the data socket to be opened + auto controlFd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + if (controlFd < 0) { - cemuLog_logDebug(LogType::Force, "Failed to open send socket: {}", strerror(errno)); + cemuLog_logDebug(LogType::Force, "Failed to open control socket: {}", strerror(errno)); continue; } - sockaddr_l2 sendAddr{}; - sendAddr.l2_family = AF_BLUETOOTH; - sendAddr.l2_psm = htobs(0x11); - sendAddr.l2_bdaddr = addr; + sockaddr_l2 controlAddr{}; + controlAddr.l2_family = AF_BLUETOOTH; + controlAddr.l2_psm = htobs(0x11); + controlAddr.l2_bdaddr = addr; - if (!AttemptConnect(sendFd, sendAddr) || !AttemptSetNonBlock(sendFd)) + if (!AttemptConnect(controlFd, controlAddr) || !AttemptSetNonBlock(controlFd)) { const auto& b = addr.b; - cemuLog_logDebug(LogType::Force, "Failed to connect send socket to '{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}': {}", + cemuLog_logDebug(LogType::Force, "Failed to connect control socket to '{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}': {}", b[5], b[4], b[3], b[2], b[1], b[0], strerror(errno)); - close(sendFd); + close(controlFd); continue; } - // Socket for receiving data from controller, PSM 0x13 - auto recvFd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); - if (recvFd < 0) + // Socket for sending and receiving data from controller, PSM 0x13 + auto dataFd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + if (dataFd < 0) { - cemuLog_logDebug(LogType::Force, "Failed to open recv socket: {}", strerror(errno)); - close(sendFd); + cemuLog_logDebug(LogType::Force, "Failed to open data socket: {}", strerror(errno)); + close(controlFd); continue; } - sockaddr_l2 recvAddr{}; - recvAddr.l2_family = AF_BLUETOOTH; - recvAddr.l2_psm = htobs(0x13); - recvAddr.l2_bdaddr = addr; + sockaddr_l2 dataAddr{}; + dataAddr.l2_family = AF_BLUETOOTH; + dataAddr.l2_psm = htobs(0x13); + dataAddr.l2_bdaddr = addr; - if (!AttemptConnect(recvFd, recvAddr) || !AttemptSetNonBlock(recvFd)) + if (!AttemptConnect(dataFd, dataAddr) || !AttemptSetNonBlock(dataFd)) { const auto& b = addr.b; - cemuLog_logDebug(LogType::Force, "Failed to connect recv socket to '{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}': {}", + cemuLog_logDebug(LogType::Force, "Failed to connect data socket to '{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}': {}", b[5], b[4], b[3], b[2], b[1], b[0], strerror(errno)); - close(sendFd); - close(recvFd); + close(dataFd); + close(controlFd); continue; } - outDevices.emplace_back(std::make_shared<L2CapWiimote>(sendFd, recvFd, addr)); + outDevices.emplace_back(std::make_shared<L2CapWiimote>(controlFd, dataFd, addr)); s_addressMutex.lock(); s_addresses[addr] = true; @@ -123,13 +123,13 @@ bool L2CapWiimote::write_data(const std::vector<uint8>& data) buffer[0] = 0xA2; std::memcpy(buffer + 1, data.data(), size); const auto outSize = size + 1; - return send(m_sendFd, buffer, outSize, 0) == outSize; + return send(m_dataFd, buffer, outSize, 0) == outSize; } std::optional<std::vector<uint8>> L2CapWiimote::read_data() { uint8 buffer[23]; - const auto nBytes = recv(m_sendFd, buffer, 23, 0); + const auto nBytes = recv(m_dataFd, buffer, 23, 0); if (nBytes < 0 && errno == EWOULDBLOCK) return std::vector<uint8>{}; diff --git a/src/input/api/Wiimote/l2cap/L2CapWiimote.h b/src/input/api/Wiimote/l2cap/L2CapWiimote.h index cc8d071b..0b6c5c19 100644 --- a/src/input/api/Wiimote/l2cap/L2CapWiimote.h +++ b/src/input/api/Wiimote/l2cap/L2CapWiimote.h @@ -5,7 +5,7 @@ class L2CapWiimote : public WiimoteDevice { public: - L2CapWiimote(int recvFd, int sendFd, bdaddr_t addr); + L2CapWiimote(int controlFd, int dataFd, bdaddr_t addr); ~L2CapWiimote() override; bool write_data(const std::vector<uint8>& data) override; @@ -15,8 +15,8 @@ class L2CapWiimote : public WiimoteDevice static void AddCandidateAddress(bdaddr_t addr); static std::vector<WiimoteDevicePtr> get_devices(); private: - int m_recvFd; - int m_sendFd; + int m_controlFd; + int m_dataFd; bdaddr_t m_addr; }; From 57ff99ce536149d177cb9958c5919e6aa4914180 Mon Sep 17 00:00:00 2001 From: mitoposter <weebcel@japanese.wives.forsale> Date: Wed, 19 Mar 2025 16:06:55 +0000 Subject: [PATCH 243/299] cubeb: Show default device option even if enumerating devices fails (#1515) --- src/audio/CubebAPI.cpp | 12 ++++++------ src/audio/CubebInputAPI.cpp | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/audio/CubebAPI.cpp b/src/audio/CubebAPI.cpp index f98fa601..f6d5d516 100644 --- a/src/audio/CubebAPI.cpp +++ b/src/audio/CubebAPI.cpp @@ -183,17 +183,17 @@ void CubebAPI::Destroy() std::vector<IAudioAPI::DeviceDescriptionPtr> CubebAPI::GetDevices() { - cubeb_device_collection devices; - if (cubeb_enumerate_devices(s_context, CUBEB_DEVICE_TYPE_OUTPUT, &devices) != CUBEB_OK) - return {}; - std::vector<DeviceDescriptionPtr> result; - result.reserve(devices.count + 1); // Reserve space for the default device - // Add the default device to the list auto defaultDevice = std::make_shared<CubebDeviceDescription>(nullptr, "default", L"Default Device"); result.emplace_back(defaultDevice); + cubeb_device_collection devices; + if (cubeb_enumerate_devices(s_context, CUBEB_DEVICE_TYPE_OUTPUT, &devices) != CUBEB_OK) + return result; + + result.reserve(devices.count + 1); // The default device already occupies one element + for (size_t i = 0; i < devices.count; ++i) { // const auto& device = devices.device[i]; diff --git a/src/audio/CubebInputAPI.cpp b/src/audio/CubebInputAPI.cpp index c0fa73f4..a9faa9c8 100644 --- a/src/audio/CubebInputAPI.cpp +++ b/src/audio/CubebInputAPI.cpp @@ -175,17 +175,17 @@ void CubebInputAPI::Destroy() std::vector<IAudioInputAPI::DeviceDescriptionPtr> CubebInputAPI::GetDevices() { - cubeb_device_collection devices; - if (cubeb_enumerate_devices(s_context, CUBEB_DEVICE_TYPE_INPUT, &devices) != CUBEB_OK) - return {}; - std::vector<DeviceDescriptionPtr> result; - result.reserve(devices.count + 1); // Reserve space for the default device - // Add the default device to the list auto defaultDevice = std::make_shared<CubebDeviceDescription>(nullptr, "default", L"Default Device"); result.emplace_back(defaultDevice); + cubeb_device_collection devices; + if (cubeb_enumerate_devices(s_context, CUBEB_DEVICE_TYPE_INPUT, &devices) != CUBEB_OK) + return result; + + result.reserve(devices.count + 1); // The default device already occupies one element + for (size_t i = 0; i < devices.count; ++i) { // const auto& device = devices.device[i]; From c4eab08f308e33d6501550976943b701997a0036 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Thu, 3 Apr 2025 17:35:09 +0200 Subject: [PATCH 244/299] Update vcpkg --- dependencies/vcpkg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies/vcpkg b/dependencies/vcpkg index a4275b7e..533a5fda 160000 --- a/dependencies/vcpkg +++ b/dependencies/vcpkg @@ -1 +1 @@ -Subproject commit a4275b7eee79fb24ec2e135481ef5fce8b41c339 +Subproject commit 533a5fda5c0646d1771345fb572e759283444d5f From cd6eb1097b1d6c986dd2124106580af400426da1 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Tue, 15 Apr 2025 21:10:11 +0200 Subject: [PATCH 245/299] Vulkan: Fix a validation error + minor code refactor We were using VK_EXT_DEPTH_CLIP_ENABLE but didn't actually request it. Also fixed an assert when closing Cemu caused by incorrectly tracking the number of allocated pipelines --- src/Cafe/HW/Latte/Renderer/Vulkan/VKRBase.h | 11 +++-- .../Latte/Renderer/Vulkan/VKRPipelineInfo.cpp | 1 - .../Vulkan/VulkanPipelineCompiler.cpp | 16 +++--- .../Renderer/Vulkan/VulkanPipelineCompiler.h | 2 +- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 49 +++++++++++-------- .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 1 + .../Renderer/Vulkan/VulkanRendererCore.cpp | 23 +++++---- .../Renderer/Vulkan/VulkanSurfaceCopy.cpp | 33 ++++++------- 8 files changed, 73 insertions(+), 63 deletions(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VKRBase.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRBase.h index 9c7e03f3..7dcd3ebc 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VKRBase.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRBase.h @@ -221,11 +221,14 @@ public: VKRObjectPipeline(); ~VKRObjectPipeline() override; - void setPipeline(VkPipeline newPipeline); + void SetPipeline(VkPipeline newPipeline); + VkPipeline GetPipeline() const { return m_pipeline; } - VkPipeline pipeline = VK_NULL_HANDLE; - VkDescriptorSetLayout vertexDSL = VK_NULL_HANDLE, pixelDSL = VK_NULL_HANDLE, geometryDSL = VK_NULL_HANDLE; - VkPipelineLayout pipeline_layout = VK_NULL_HANDLE; + VkDescriptorSetLayout m_vertexDSL = VK_NULL_HANDLE, m_pixelDSL = VK_NULL_HANDLE, m_geometryDSL = VK_NULL_HANDLE; + VkPipelineLayout m_pipelineLayout = VK_NULL_HANDLE; + +private: + VkPipeline m_pipeline = VK_NULL_HANDLE; }; class VKRObjectDescriptorSet : public VKRDestructibleObject diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp index fd5a5b78..b316b9c5 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VKRPipelineInfo.cpp @@ -26,7 +26,6 @@ PipelineInfo::PipelineInfo(uint64 minimalStateHash, uint64 pipelineHash, LatteFe // init VKRObjPipeline m_vkrObjPipeline = new VKRObjectPipeline(); - m_vkrObjPipeline->pipeline = VK_NULL_HANDLE; // track dependency with shaders if (vertexShaderVk) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp index ba094a84..1ea522dc 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp @@ -558,8 +558,8 @@ void PipelineCompiler::InitRasterizerState(const LatteContextRegister& latteRegi rasterizerExt.flags = 0; rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; - rasterizer.pNext = &rasterizerExt; rasterizer.rasterizerDiscardEnable = LatteGPUState.contextNew.PA_CL_CLIP_CNTL.get_DX_RASTERIZATION_KILL(); + rasterizer.pNext = VulkanRenderer::GetInstance()->m_featureControl.deviceExtensions.depth_clip_enable ? &rasterizerExt : nullptr; // GX2SetSpecialState(0, true) workaround if (!LatteGPUState.contextNew.PA_CL_VTE_CNTL.get_VPORT_X_OFFSET_ENA()) rasterizer.rasterizerDiscardEnable = false; @@ -730,7 +730,7 @@ void PipelineCompiler::InitDescriptorSetLayouts(VulkanRenderer* vkRenderer, Pipe { cemu_assert_debug(descriptorSetLayoutCount == 0); CreateDescriptorSetLayout(vkRenderer, vertexShader, descriptorSetLayout[descriptorSetLayoutCount], vkrPipelineInfo); - vkObjPipeline->vertexDSL = descriptorSetLayout[descriptorSetLayoutCount]; + vkObjPipeline->m_vertexDSL = descriptorSetLayout[descriptorSetLayoutCount]; descriptorSetLayoutCount++; } @@ -738,7 +738,7 @@ void PipelineCompiler::InitDescriptorSetLayouts(VulkanRenderer* vkRenderer, Pipe { cemu_assert_debug(descriptorSetLayoutCount == 1); CreateDescriptorSetLayout(vkRenderer, pixelShader, descriptorSetLayout[descriptorSetLayoutCount], vkrPipelineInfo); - vkObjPipeline->pixelDSL = descriptorSetLayout[descriptorSetLayoutCount]; + vkObjPipeline->m_pixelDSL = descriptorSetLayout[descriptorSetLayoutCount]; descriptorSetLayoutCount++; } else if (geometryShader) @@ -757,7 +757,7 @@ void PipelineCompiler::InitDescriptorSetLayouts(VulkanRenderer* vkRenderer, Pipe { cemu_assert_debug(descriptorSetLayoutCount == 2); CreateDescriptorSetLayout(vkRenderer, geometryShader, descriptorSetLayout[descriptorSetLayoutCount], vkrPipelineInfo); - vkObjPipeline->geometryDSL = descriptorSetLayout[descriptorSetLayoutCount]; + vkObjPipeline->m_geometryDSL = descriptorSetLayout[descriptorSetLayoutCount]; descriptorSetLayoutCount++; } } @@ -918,7 +918,7 @@ bool PipelineCompiler::InitFromCurrentGPUState(PipelineInfo* pipelineInfo, const pipelineLayoutInfo.pPushConstantRanges = nullptr; pipelineLayoutInfo.pushConstantRangeCount = 0; - VkResult result = vkCreatePipelineLayout(vkRenderer->m_logicalDevice, &pipelineLayoutInfo, nullptr, &m_pipeline_layout); + VkResult result = vkCreatePipelineLayout(vkRenderer->m_logicalDevice, &pipelineLayoutInfo, nullptr, &m_pipelineLayout); if (result != VK_SUCCESS) { cemuLog_log(LogType::Force, "Failed to create pipeline layout: {}", result); @@ -936,7 +936,7 @@ bool PipelineCompiler::InitFromCurrentGPUState(PipelineInfo* pipelineInfo, const // ########################################################################################################################################## - pipelineInfo->m_vkrObjPipeline->pipeline_layout = m_pipeline_layout; + pipelineInfo->m_vkrObjPipeline->m_pipelineLayout = m_pipelineLayout; // increment ref counter for vkrObjPipeline and renderpass object to make sure they dont get released while we are using them m_vkrObjPipeline->incRef(); @@ -989,7 +989,7 @@ bool PipelineCompiler::Compile(bool forceCompile, bool isRenderThread, bool show pipelineInfo.pRasterizationState = &rasterizer; pipelineInfo.pMultisampleState = &multisampling; pipelineInfo.pColorBlendState = &colorBlending; - pipelineInfo.layout = m_pipeline_layout; + pipelineInfo.layout = m_pipelineLayout; pipelineInfo.renderPass = m_renderPassObj->m_renderPass; pipelineInfo.pDepthStencilState = &depthStencilState; pipelineInfo.subpass = 0; @@ -1037,7 +1037,7 @@ bool PipelineCompiler::Compile(bool forceCompile, bool isRenderThread, bool show } else if (result == VK_SUCCESS) { - m_vkrObjPipeline->setPipeline(pipeline); + m_vkrObjPipeline->SetPipeline(pipeline); } else { diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.h index 304a7b31..7879b932 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.h @@ -41,7 +41,7 @@ public: bool InitFromCurrentGPUState(PipelineInfo* pipelineInfo, const LatteContextRegister& latteRegister, VKRObjectRenderPass* renderPassObj); void TrackAsCached(uint64 baseHash, uint64 pipelineStateHash); // stores pipeline to permanent cache if not yet cached. Must be called synchronously from render thread due to dependency on GPU state - VkPipelineLayout m_pipeline_layout; + VkPipelineLayout m_pipelineLayout; VKRObjectRenderPass* m_renderPassObj{}; /* shader stages */ diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index 4ff2faac..f47d6a87 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -49,7 +49,8 @@ const std::vector<const char*> kOptionalDeviceExtensions = VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME, VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME, VK_KHR_PRESENT_WAIT_EXTENSION_NAME, - VK_KHR_PRESENT_ID_EXTENSION_NAME + VK_KHR_PRESENT_ID_EXTENSION_NAME, + VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME }; const std::vector<const char*> kRequiredDeviceExtensions = @@ -82,8 +83,6 @@ VKAPI_ATTR VkBool32 VKAPI_CALL DebugUtilsCallback(VkDebugUtilsMessageSeverityFla if (strstr(pCallbackData->pMessage, "Number of currently valid sampler objects is not less than the maximum allowed")) return VK_FALSE; - assert_dbg(); - #endif cemuLog_log(LogType::Force, (char*)pCallbackData->pMessage); @@ -314,7 +313,10 @@ void VulkanRenderer::GetDeviceFeatures() cemuLog_log(LogType::Force, "VK_EXT_custom_border_color not supported. Cannot emulate arbitrary border color"); } } - + if (!m_featureControl.deviceExtensions.depth_clip_enable) + { + cemuLog_log(LogType::Force, "VK_EXT_depth_clip_enable not supported"); + } // get limits m_featureControl.limits.minUniformBufferOffsetAlignment = std::max(prop2.properties.limits.minUniformBufferOffsetAlignment, (VkDeviceSize)4); m_featureControl.limits.nonCoherentAtomSize = std::max(prop2.properties.limits.nonCoherentAtomSize, (VkDeviceSize)4); @@ -1118,10 +1120,13 @@ VkDeviceCreateInfo VulkanRenderer::CreateDeviceCreateInfo(const std::vector<VkDe used_extensions.emplace_back(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); if (m_featureControl.deviceExtensions.shader_float_controls) used_extensions.emplace_back(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME); + if (m_featureControl.deviceExtensions.depth_clip_enable) + used_extensions.emplace_back(VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME); if (m_featureControl.deviceExtensions.present_wait) + { used_extensions.emplace_back(VK_KHR_PRESENT_ID_EXTENSION_NAME); - if (m_featureControl.deviceExtensions.present_wait) used_extensions.emplace_back(VK_KHR_PRESENT_WAIT_EXTENSION_NAME); + } VkDeviceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; @@ -1218,6 +1223,7 @@ bool VulkanRenderer::CheckDeviceExtensionSupport(const VkPhysicalDevice device, info.deviceExtensions.synchronization2 = isExtensionAvailable(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); info.deviceExtensions.shader_float_controls = isExtensionAvailable(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME); info.deviceExtensions.dynamic_rendering = false; // isExtensionAvailable(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); + info.deviceExtensions.depth_clip_enable = isExtensionAvailable(VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME); // dynamic rendering doesn't provide any benefits for us right now. Driver implementations are very unoptimized as of Feb 2022 info.deviceExtensions.present_wait = isExtensionAvailable(VK_KHR_PRESENT_WAIT_EXTENSION_NAME) && isExtensionAvailable(VK_KHR_PRESENT_ID_EXTENSION_NAME); @@ -4112,33 +4118,36 @@ VKRObjectFramebuffer::~VKRObjectFramebuffer() VKRObjectPipeline::VKRObjectPipeline() { - // todo } -void VKRObjectPipeline::setPipeline(VkPipeline newPipeline) +void VKRObjectPipeline::SetPipeline(VkPipeline newPipeline) { - cemu_assert_debug(pipeline == VK_NULL_HANDLE); - pipeline = newPipeline; - if(newPipeline != VK_NULL_HANDLE) + if (m_pipeline == newPipeline) + return; + cemu_assert_debug(m_pipeline == VK_NULL_HANDLE); // replacing an already assigned pipeline is not intended + if(m_pipeline == VK_NULL_HANDLE && newPipeline != VK_NULL_HANDLE) performanceMonitor.vk.numGraphicPipelines.increment(); + else if(m_pipeline != VK_NULL_HANDLE && newPipeline == VK_NULL_HANDLE) + performanceMonitor.vk.numGraphicPipelines.decrement(); + m_pipeline = newPipeline; } VKRObjectPipeline::~VKRObjectPipeline() { auto vkr = VulkanRenderer::GetInstance(); - if (pipeline != VK_NULL_HANDLE) + if (m_pipeline != VK_NULL_HANDLE) { - vkDestroyPipeline(vkr->GetLogicalDevice(), pipeline, nullptr); + vkDestroyPipeline(vkr->GetLogicalDevice(), m_pipeline, nullptr); performanceMonitor.vk.numGraphicPipelines.decrement(); } - if (vertexDSL != VK_NULL_HANDLE) - vkDestroyDescriptorSetLayout(vkr->GetLogicalDevice(), vertexDSL, nullptr); - if (pixelDSL != VK_NULL_HANDLE) - vkDestroyDescriptorSetLayout(vkr->GetLogicalDevice(), pixelDSL, nullptr); - if (geometryDSL != VK_NULL_HANDLE) - vkDestroyDescriptorSetLayout(vkr->GetLogicalDevice(), geometryDSL, nullptr); - if (pipeline_layout != VK_NULL_HANDLE) - vkDestroyPipelineLayout(vkr->GetLogicalDevice(), pipeline_layout, nullptr); + if (m_vertexDSL != VK_NULL_HANDLE) + vkDestroyDescriptorSetLayout(vkr->GetLogicalDevice(), m_vertexDSL, nullptr); + if (m_pixelDSL != VK_NULL_HANDLE) + vkDestroyDescriptorSetLayout(vkr->GetLogicalDevice(), m_pixelDSL, nullptr); + if (m_geometryDSL != VK_NULL_HANDLE) + vkDestroyDescriptorSetLayout(vkr->GetLogicalDevice(), m_geometryDSL, nullptr); + if (m_pipelineLayout != VK_NULL_HANDLE) + vkDestroyPipelineLayout(vkr->GetLogicalDevice(), m_pipelineLayout, nullptr); } VKRObjectDescriptorSet::VKRObjectDescriptorSet() diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index f1450487..933043d3 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -452,6 +452,7 @@ private: bool dynamic_rendering = false; // VK_KHR_dynamic_rendering bool shader_float_controls = false; // VK_KHR_shader_float_controls bool present_wait = false; // VK_KHR_present_wait + bool depth_clip_enable = false; // VK_EXT_depth_clip_enable }deviceExtensions; struct diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp index 3e23b0aa..bd49a69e 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp @@ -603,7 +603,7 @@ VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo* const auto it = pipeline_info->vertex_ds_cache.find(stateHash); if (it != pipeline_info->vertex_ds_cache.cend()) return it->second; - descriptor_set_layout = pipeline_info->m_vkrObjPipeline->vertexDSL; + descriptor_set_layout = pipeline_info->m_vkrObjPipeline->m_vertexDSL; break; } case LatteConst::ShaderType::Pixel: @@ -611,7 +611,7 @@ VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo* const auto it = pipeline_info->pixel_ds_cache.find(stateHash); if (it != pipeline_info->pixel_ds_cache.cend()) return it->second; - descriptor_set_layout = pipeline_info->m_vkrObjPipeline->pixelDSL; + descriptor_set_layout = pipeline_info->m_vkrObjPipeline->m_pixelDSL; break; } case LatteConst::ShaderType::Geometry: @@ -619,7 +619,7 @@ VkDescriptorSetInfo* VulkanRenderer::draw_getOrCreateDescriptorSet(PipelineInfo* const auto it = pipeline_info->geometry_ds_cache.find(stateHash); if (it != pipeline_info->geometry_ds_cache.cend()) return it->second; - descriptor_set_layout = pipeline_info->m_vkrObjPipeline->geometryDSL; + descriptor_set_layout = pipeline_info->m_vkrObjPipeline->m_geometryDSL; break; } default: @@ -1481,8 +1481,7 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 } auto vkObjPipeline = pipeline_info->m_vkrObjPipeline; - - if (vkObjPipeline->pipeline == VK_NULL_HANDLE) + if (vkObjPipeline->GetPipeline() == VK_NULL_HANDLE) { // invalid/uninitialized pipeline m_state.activeVertexDS = nullptr; @@ -1509,11 +1508,11 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 draw_setRenderPass(); - if (m_state.currentPipeline != vkObjPipeline->pipeline) + if (m_state.currentPipeline != vkObjPipeline->GetPipeline()) { - vkCmdBindPipeline(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vkObjPipeline->pipeline); + vkCmdBindPipeline(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vkObjPipeline->GetPipeline()); vkObjPipeline->flagForCurrentCommandBuffer(); - m_state.currentPipeline = vkObjPipeline->pipeline; + m_state.currentPipeline = vkObjPipeline->GetPipeline(); // depth bias if (pipeline_info->usesDepthBias) draw_updateDepthBias(true); @@ -1545,7 +1544,7 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 dsArray[1] = pixelDS->m_vkObjDescriptorSet->descriptorSet; vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, - vkObjPipeline->pipeline_layout, 0, 2, dsArray, numDynOffsetsVS + numDynOffsetsPS, + vkObjPipeline->m_pipelineLayout, 0, 2, dsArray, numDynOffsetsVS + numDynOffsetsPS, dynamicOffsets); } else if (vertexDS) @@ -1554,7 +1553,7 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_VERTEX, dynamicOffsets, numDynOffsets, pipeline_info); vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, - vkObjPipeline->pipeline_layout, 0, 1, &vertexDS->m_vkObjDescriptorSet->descriptorSet, numDynOffsets, + vkObjPipeline->m_pipelineLayout, 0, 1, &vertexDS->m_vkObjDescriptorSet->descriptorSet, numDynOffsets, dynamicOffsets); } else if (pixelDS) @@ -1563,7 +1562,7 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_FRAGMENT, dynamicOffsets, numDynOffsets, pipeline_info); vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, - vkObjPipeline->pipeline_layout, 1, 1, &pixelDS->m_vkObjDescriptorSet->descriptorSet, numDynOffsets, + vkObjPipeline->m_pipelineLayout, 1, 1, &pixelDS->m_vkObjDescriptorSet->descriptorSet, numDynOffsets, dynamicOffsets); } if (geometryDS) @@ -1572,7 +1571,7 @@ void VulkanRenderer::draw_execute(uint32 baseVertex, uint32 baseInstance, uint32 draw_prepareDynamicOffsetsForDescriptorSet(VulkanRendererConst::SHADER_STAGE_INDEX_GEOMETRY, dynamicOffsets, numDynOffsets, pipeline_info); vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, - vkObjPipeline->pipeline_layout, 2, 1, &geometryDS->m_vkObjDescriptorSet->descriptorSet, numDynOffsets, + vkObjPipeline->m_pipelineLayout, 2, 1, &geometryDS->m_vkObjDescriptorSet->descriptorSet, numDynOffsets, dynamicOffsets); } diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp index f98eb452..e3e42012 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanSurfaceCopy.cpp @@ -357,7 +357,7 @@ CopySurfacePipelineInfo* VulkanRenderer::copySurface_getOrCreateGraphicsPipeline layoutInfo.bindingCount = (uint32_t)descriptorSetLayoutBindings.size(); layoutInfo.pBindings = descriptorSetLayoutBindings.data(); - if (vkCreateDescriptorSetLayout(m_logicalDevice, &layoutInfo, nullptr, &vkObjPipeline->pixelDSL) != VK_SUCCESS) + if (vkCreateDescriptorSetLayout(m_logicalDevice, &layoutInfo, nullptr, &vkObjPipeline->m_pixelDSL) != VK_SUCCESS) UnrecoverableError(fmt::format("Failed to create descriptor set layout for surface copy shader").c_str()); // ########################################################################################################################################## @@ -370,15 +370,15 @@ CopySurfacePipelineInfo* VulkanRenderer::copySurface_getOrCreateGraphicsPipeline VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutInfo.setLayoutCount = 1; - pipelineLayoutInfo.pSetLayouts = &vkObjPipeline->pixelDSL; + pipelineLayoutInfo.pSetLayouts = &vkObjPipeline->m_pixelDSL; pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange; pipelineLayoutInfo.pushConstantRangeCount = 1; - VkResult result = vkCreatePipelineLayout(m_logicalDevice, &pipelineLayoutInfo, nullptr, &vkObjPipeline->pipeline_layout); + VkResult result = vkCreatePipelineLayout(m_logicalDevice, &pipelineLayoutInfo, nullptr, &vkObjPipeline->m_pipelineLayout); if (result != VK_SUCCESS) { cemuLog_log(LogType::Force, "Failed to create pipeline layout: {}", result); - vkObjPipeline->pipeline = VK_NULL_HANDLE; + vkObjPipeline->SetPipeline(VK_NULL_HANDLE); return copyPipeline; } @@ -425,7 +425,7 @@ CopySurfacePipelineInfo* VulkanRenderer::copySurface_getOrCreateGraphicsPipeline pipelineInfo.pRasterizationState = &rasterizer; pipelineInfo.pMultisampleState = &multisampling; pipelineInfo.pColorBlendState = state.destinationTexture->isDepth?nullptr:&colorBlending; - pipelineInfo.layout = vkObjPipeline->pipeline_layout; + pipelineInfo.layout = vkObjPipeline->m_pipelineLayout; pipelineInfo.renderPass = copyPipeline->vkObjRenderPass->m_renderPass; pipelineInfo.pDepthStencilState = &depthStencilState; pipelineInfo.subpass = 0; @@ -434,17 +434,16 @@ CopySurfacePipelineInfo* VulkanRenderer::copySurface_getOrCreateGraphicsPipeline copyPipeline->vkObjPipeline = vkObjPipeline; - result = vkCreateGraphicsPipelines(m_logicalDevice, m_pipeline_cache, 1, &pipelineInfo, nullptr, ©Pipeline->vkObjPipeline->pipeline); + VkPipeline pipeline = VK_NULL_HANDLE; + result = vkCreateGraphicsPipelines(m_logicalDevice, m_pipeline_cache, 1, &pipelineInfo, nullptr, &pipeline); if (result != VK_SUCCESS) { + copyPipeline->vkObjPipeline->SetPipeline(nullptr); cemuLog_log(LogType::Force, "Failed to create graphics pipeline for surface copy. Error {} Info:", (sint32)result); - cemu_assert_debug(false); - copyPipeline->vkObjPipeline->pipeline = VK_NULL_HANDLE; + cemu_assert_suspicious(); } - //performanceMonitor.vk.numGraphicPipelines.increment(); - - //m_pipeline_cache_semaphore.notify(); - + else + copyPipeline->vkObjPipeline->SetPipeline(pipeline); return copyPipeline; } @@ -522,7 +521,7 @@ VKRObjectDescriptorSet* VulkanRenderer::surfaceCopy_getOrCreateDescriptorSet(VkC allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; allocInfo.descriptorPool = m_descriptorPool; allocInfo.descriptorSetCount = 1; - allocInfo.pSetLayouts = &(pipelineInfo->vkObjPipeline->pixelDSL); + allocInfo.pSetLayouts = &pipelineInfo->vkObjPipeline->m_pixelDSL; if (vkAllocateDescriptorSets(m_logicalDevice, &allocInfo, &vkObjDescriptorSet->descriptorSet) != VK_SUCCESS) { @@ -644,7 +643,7 @@ void VulkanRenderer::surfaceCopy_viaDrawcall(LatteTextureVk* srcTextureVk, sint3 pushConstantData.srcTexelOffset[0] = 0; pushConstantData.srcTexelOffset[1] = 0; - vkCmdPushConstants(m_state.currentCommandBuffer, copySurfacePipelineInfo->vkObjPipeline->pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(pushConstantData), &pushConstantData); + vkCmdPushConstants(m_state.currentCommandBuffer, copySurfacePipelineInfo->vkObjPipeline->m_pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(pushConstantData), &pushConstantData); // draw VkRenderPassBeginInfo renderPassInfo{}; @@ -680,13 +679,13 @@ void VulkanRenderer::surfaceCopy_viaDrawcall(LatteTextureVk* srcTextureVk, sint3 vkCmdBeginRenderPass(m_state.currentCommandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); - vkCmdBindPipeline(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, copySurfacePipelineInfo->vkObjPipeline->pipeline); + vkCmdBindPipeline(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, copySurfacePipelineInfo->vkObjPipeline->GetPipeline()); copySurfacePipelineInfo->vkObjPipeline->flagForCurrentCommandBuffer(); - m_state.currentPipeline = copySurfacePipelineInfo->vkObjPipeline->pipeline; + m_state.currentPipeline = copySurfacePipelineInfo->vkObjPipeline->GetPipeline(); vkCmdBindDescriptorSets(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, - copySurfacePipelineInfo->vkObjPipeline->pipeline_layout, 0, 1, &vkObjDescriptorSet->descriptorSet, 0, nullptr); + copySurfacePipelineInfo->vkObjPipeline->m_pipelineLayout, 0, 1, &vkObjDescriptorSet->descriptorSet, 0, nullptr); vkObjDescriptorSet->flagForCurrentCommandBuffer(); vkCmdDraw(m_state.currentCommandBuffer, 6, 1, 0, 0); From 4972381edce17bb2412b44c798e76f2b2e079249 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Tue, 15 Apr 2025 22:46:19 +0200 Subject: [PATCH 246/299] Vulkan: Fix imgui validation error when sRGB framebuffer is used --- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 59 ++++++++++--------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index f47d6a87..bfe21bcc 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -1595,37 +1595,35 @@ void VulkanRenderer::DeleteNullObjects() void VulkanRenderer::ImguiInit() { - if (m_imguiRenderPass == VK_NULL_HANDLE) - { - // TODO: renderpass swapchain format may change between srgb and rgb -> need reinit - VkAttachmentDescription colorAttachment = {}; - colorAttachment.format = m_mainSwapchainInfo->m_surfaceFormat.format; - colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; - colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; - colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; - colorAttachment.initialLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + VkRenderPass prevRenderPass = m_imguiRenderPass; - VkAttachmentReference colorAttachmentRef = {}; - colorAttachmentRef.attachment = 0; - colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - VkSubpassDescription subpass = {}; - subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - subpass.colorAttachmentCount = 1; - subpass.pColorAttachments = &colorAttachmentRef; + VkAttachmentDescription colorAttachment = {}; + colorAttachment.format = m_mainSwapchainInfo->m_surfaceFormat.format; + colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; + colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + colorAttachment.initialLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - VkRenderPassCreateInfo renderPassInfo = {}; - renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - renderPassInfo.attachmentCount = 1; - renderPassInfo.pAttachments = &colorAttachment; - renderPassInfo.subpassCount = 1; - renderPassInfo.pSubpasses = &subpass; - const auto result = vkCreateRenderPass(m_logicalDevice, &renderPassInfo, nullptr, &m_imguiRenderPass); - if (result != VK_SUCCESS) - throw VkException(result, "can't create imgui renderpass"); - } + VkAttachmentReference colorAttachmentRef = {}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + VkSubpassDescription subpass = {}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + VkRenderPassCreateInfo renderPassInfo = {}; + renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + const auto result = vkCreateRenderPass(m_logicalDevice, &renderPassInfo, nullptr, &m_imguiRenderPass); + if (result != VK_SUCCESS) + throw VkException(result, "can't create imgui renderpass"); ImGui_ImplVulkan_InitInfo info{}; info.Instance = m_instance; @@ -1639,6 +1637,9 @@ void VulkanRenderer::ImguiInit() info.ImageCount = info.MinImageCount; ImGui_ImplVulkan_Init(&info, m_imguiRenderPass); + + if (prevRenderPass != VK_NULL_HANDLE) + vkDestroyRenderPass(GetLogicalDevice(), prevRenderPass, nullptr); } void VulkanRenderer::Initialize() From 06233e34629e53682aad55bb8be5a98caec91de3 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 16 Apr 2025 14:36:11 +0200 Subject: [PATCH 247/299] UI: Fix wxWidgets debug assert Adding the same component multiple times is not allowed. Use sizers instead --- src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp index c77ae081..d40e5e5e 100644 --- a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp +++ b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp @@ -146,17 +146,15 @@ wxPanel* EmulatedUSBDeviceFrame::AddDimensionsPage(wxNotebook* notebook) auto* top_row = new wxBoxSizer(wxHORIZONTAL); auto* bottom_row = new wxBoxSizer(wxHORIZONTAL); - auto* dummy = new wxStaticText(box, wxID_ANY, ""); - top_row->Add(AddDimensionPanel(2, 0, box), 1, wxEXPAND | wxALL, 2); - top_row->Add(dummy, 1, wxEXPAND | wxLEFT | wxRIGHT, 2); + top_row->Add(0, 0, 1, wxEXPAND | wxLEFT | wxRIGHT, 2); top_row->Add(AddDimensionPanel(1, 1, box), 1, wxEXPAND | wxALL, 2); - top_row->Add(dummy, 1, wxEXPAND | wxLEFT | wxRIGHT, 2); + top_row->Add(0, 0, 1, wxEXPAND | wxLEFT | wxRIGHT, 2); top_row->Add(AddDimensionPanel(3, 2, box), 1, wxEXPAND | wxALL, 2); bottom_row->Add(AddDimensionPanel(2, 3, box), 1, wxEXPAND | wxALL, 2); bottom_row->Add(AddDimensionPanel(2, 4, box), 1, wxEXPAND | wxALL, 2); - bottom_row->Add(dummy, 1, wxEXPAND | wxLEFT | wxRIGHT, 0); + bottom_row->Add(0, 0, 1, wxEXPAND | wxLEFT | wxRIGHT, 0); bottom_row->Add(AddDimensionPanel(3, 5, box), 1, wxEXPAND | wxALL, 2); bottom_row->Add(AddDimensionPanel(3, 6, box), 1, wxEXPAND | wxALL, 2); From b089ae5b32f5f2bc901b9ff2db2424cbf3c1186b Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 26 Apr 2025 17:59:32 +0200 Subject: [PATCH 248/299] PowerPC recompiler rework (#641) --- boost.natvis | 26 + src/Cafe/CMakeLists.txt | 35 +- src/Cafe/HW/Espresso/EspressoISA.h | 20 +- .../Interpreter/PPCInterpreterALU.hpp | 20 +- .../Interpreter/PPCInterpreterFPU.cpp | 4 +- .../Interpreter/PPCInterpreterInternal.h | 11 +- .../Interpreter/PPCInterpreterLoadStore.hpp | 3 +- .../Interpreter/PPCInterpreterMain.cpp | 16 +- .../Interpreter/PPCInterpreterOPC.cpp | 1 - src/Cafe/HW/Espresso/PPCState.h | 5 +- .../Recompiler/BackendX64/BackendX64.cpp | 1668 ++++++ .../BackendX64.h} | 175 +- .../BackendX64AVX.cpp} | 10 +- .../BackendX64BMI.cpp} | 40 +- .../BackendX64FPU.cpp} | 733 +-- .../BackendX64Gen.cpp} | 184 +- .../BackendX64GenFPU.cpp} | 50 +- .../{x64Emit.hpp => BackendX64/X64Emit.hpp} | 1 - .../Recompiler/BackendX64/x86Emitter.h | 4335 ++++++++++++++ src/Cafe/HW/Espresso/Recompiler/IML/IML.h | 16 + .../Espresso/Recompiler/IML/IMLAnalyzer.cpp | 5 + .../HW/Espresso/Recompiler/IML/IMLDebug.cpp | 528 ++ .../Recompiler/IML/IMLInstruction.cpp | 669 +++ .../Espresso/Recompiler/IML/IMLInstruction.h | 785 +++ .../Espresso/Recompiler/IML/IMLOptimizer.cpp | 794 +++ .../Recompiler/IML/IMLRegisterAllocator.cpp | 2199 +++++++ .../Recompiler/IML/IMLRegisterAllocator.h | 125 + .../IML/IMLRegisterAllocatorRanges.cpp | 635 ++ .../IML/IMLRegisterAllocatorRanges.h | 364 ++ .../HW/Espresso/Recompiler/IML/IMLSegment.cpp | 133 + .../HW/Espresso/Recompiler/IML/IMLSegment.h | 193 + .../Recompiler/PPCFunctionBoundaryTracker.h | 45 +- .../HW/Espresso/Recompiler/PPCRecompiler.cpp | 240 +- .../HW/Espresso/Recompiler/PPCRecompiler.h | 375 +- .../HW/Espresso/Recompiler/PPCRecompilerIml.h | 346 +- .../Recompiler/PPCRecompilerImlAnalyzer.cpp | 137 - .../Recompiler/PPCRecompilerImlGen.cpp | 5289 ++++++----------- .../Recompiler/PPCRecompilerImlGenFPU.cpp | 633 +- .../Recompiler/PPCRecompilerImlOptimizer.cpp | 2175 ------- .../Recompiler/PPCRecompilerImlRanges.cpp | 399 -- .../Recompiler/PPCRecompilerImlRanges.h | 27 - .../PPCRecompilerImlRegisterAllocator.cpp | 1012 ---- .../PPCRecompilerImlRegisterAllocator2.cpp | 414 -- .../Recompiler/PPCRecompilerIntermediate.cpp | 163 +- .../Espresso/Recompiler/PPCRecompilerX64.cpp | 2687 --------- src/Cafe/HW/Latte/Core/LatteThread.cpp | 2 + src/Cemu/Logging/CemuLogging.h | 2 +- src/config/ActiveSettings.cpp | 10 + src/config/ActiveSettings.h | 3 + src/config/LaunchSettings.cpp | 19 +- src/config/LaunchSettings.h | 7 + src/gui/MainWindow.cpp | 47 +- src/gui/MainWindow.h | 3 +- src/util/helpers/StringBuf.h | 12 +- 54 files changed, 15433 insertions(+), 12397 deletions(-) create mode 100644 boost.natvis create mode 100644 src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64.cpp rename src/Cafe/HW/Espresso/Recompiler/{PPCRecompilerX64.h => BackendX64/BackendX64.h} (81%) rename src/Cafe/HW/Espresso/Recompiler/{PPCRecompilerX64AVX.cpp => BackendX64/BackendX64AVX.cpp} (92%) rename src/Cafe/HW/Espresso/Recompiler/{PPCRecompilerX64BMI.cpp => BackendX64/BackendX64BMI.cpp} (67%) rename src/Cafe/HW/Espresso/Recompiler/{PPCRecompilerX64FPU.cpp => BackendX64/BackendX64FPU.cpp} (51%) rename src/Cafe/HW/Espresso/Recompiler/{PPCRecompilerX64Gen.cpp => BackendX64/BackendX64Gen.cpp} (90%) rename src/Cafe/HW/Espresso/Recompiler/{PPCRecompilerX64GenFPU.cpp => BackendX64/BackendX64GenFPU.cpp} (97%) rename src/Cafe/HW/Espresso/Recompiler/{x64Emit.hpp => BackendX64/X64Emit.hpp} (99%) create mode 100644 src/Cafe/HW/Espresso/Recompiler/BackendX64/x86Emitter.h create mode 100644 src/Cafe/HW/Espresso/Recompiler/IML/IML.h create mode 100644 src/Cafe/HW/Espresso/Recompiler/IML/IMLAnalyzer.cpp create mode 100644 src/Cafe/HW/Espresso/Recompiler/IML/IMLDebug.cpp create mode 100644 src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.cpp create mode 100644 src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.h create mode 100644 src/Cafe/HW/Espresso/Recompiler/IML/IMLOptimizer.cpp create mode 100644 src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocator.cpp create mode 100644 src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocator.h create mode 100644 src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocatorRanges.cpp create mode 100644 src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocatorRanges.h create mode 100644 src/Cafe/HW/Espresso/Recompiler/IML/IMLSegment.cpp create mode 100644 src/Cafe/HW/Espresso/Recompiler/IML/IMLSegment.h delete mode 100644 src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlAnalyzer.cpp delete mode 100644 src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlOptimizer.cpp delete mode 100644 src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlRanges.cpp delete mode 100644 src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlRanges.h delete mode 100644 src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlRegisterAllocator.cpp delete mode 100644 src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlRegisterAllocator2.cpp delete mode 100644 src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64.cpp diff --git a/boost.natvis b/boost.natvis new file mode 100644 index 00000000..2781a585 --- /dev/null +++ b/boost.natvis @@ -0,0 +1,26 @@ +<?xml version='1.0' encoding='utf-8'?> +<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> + + <Type Name="boost::container::small_vector<*>"> + <Expand> + <Item Name="[size]">m_holder.m_size</Item> + <ArrayItems> + <Size>m_holder.m_size</Size> + <ValuePointer>m_holder.m_start</ValuePointer> + </ArrayItems> + </Expand> + </Type> + + <Type Name="boost::container::static_vector<*>"> + <DisplayString>{{ size={m_holder.m_size} }}</DisplayString> + <Expand> + <Item Name="[size]" ExcludeView="simple">m_holder.m_size</Item> + <Item Name="[capacity]" ExcludeView="simple">static_capacity</Item> + <ArrayItems> + <Size>m_holder.m_size</Size> + <ValuePointer>($T1*)m_holder.storage.data</ValuePointer> + </ArrayItems> + </Expand> + </Type> + +</AutoVisualizer> diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index d51d58d5..6f9f5e30 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -67,24 +67,31 @@ add_library(CemuCafe HW/Espresso/Recompiler/PPCFunctionBoundaryTracker.h HW/Espresso/Recompiler/PPCRecompiler.cpp HW/Espresso/Recompiler/PPCRecompiler.h - HW/Espresso/Recompiler/PPCRecompilerImlAnalyzer.cpp + HW/Espresso/Recompiler/IML/IML.h + HW/Espresso/Recompiler/IML/IMLSegment.cpp + HW/Espresso/Recompiler/IML/IMLSegment.h + HW/Espresso/Recompiler/IML/IMLInstruction.cpp + HW/Espresso/Recompiler/IML/IMLInstruction.h + HW/Espresso/Recompiler/IML/IMLDebug.cpp + HW/Espresso/Recompiler/IML/IMLAnalyzer.cpp + HW/Espresso/Recompiler/IML/IMLOptimizer.cpp + HW/Espresso/Recompiler/IML/IMLRegisterAllocator.cpp + HW/Espresso/Recompiler/IML/IMLRegisterAllocator.h + HW/Espresso/Recompiler/IML/IMLRegisterAllocatorRanges.cpp + HW/Espresso/Recompiler/IML/IMLRegisterAllocatorRanges.h HW/Espresso/Recompiler/PPCRecompilerImlGen.cpp HW/Espresso/Recompiler/PPCRecompilerImlGenFPU.cpp HW/Espresso/Recompiler/PPCRecompilerIml.h - HW/Espresso/Recompiler/PPCRecompilerImlOptimizer.cpp - HW/Espresso/Recompiler/PPCRecompilerImlRanges.cpp - HW/Espresso/Recompiler/PPCRecompilerImlRanges.h - HW/Espresso/Recompiler/PPCRecompilerImlRegisterAllocator2.cpp - HW/Espresso/Recompiler/PPCRecompilerImlRegisterAllocator.cpp HW/Espresso/Recompiler/PPCRecompilerIntermediate.cpp - HW/Espresso/Recompiler/PPCRecompilerX64AVX.cpp - HW/Espresso/Recompiler/PPCRecompilerX64BMI.cpp - HW/Espresso/Recompiler/PPCRecompilerX64.cpp - HW/Espresso/Recompiler/PPCRecompilerX64FPU.cpp - HW/Espresso/Recompiler/PPCRecompilerX64Gen.cpp - HW/Espresso/Recompiler/PPCRecompilerX64GenFPU.cpp - HW/Espresso/Recompiler/PPCRecompilerX64.h - HW/Espresso/Recompiler/x64Emit.hpp + HW/Espresso/Recompiler/BackendX64/BackendX64AVX.cpp + HW/Espresso/Recompiler/BackendX64/BackendX64BMI.cpp + HW/Espresso/Recompiler/BackendX64/BackendX64.cpp + HW/Espresso/Recompiler/BackendX64/BackendX64FPU.cpp + HW/Espresso/Recompiler/BackendX64/BackendX64Gen.cpp + HW/Espresso/Recompiler/BackendX64/BackendX64GenFPU.cpp + HW/Espresso/Recompiler/BackendX64/BackendX64.h + HW/Espresso/Recompiler/BackendX64/X64Emit.hpp + HW/Espresso/Recompiler/BackendX64/x86Emitter.h HW/Latte/Common/RegisterSerializer.cpp HW/Latte/Common/RegisterSerializer.h HW/Latte/Common/ShaderSerializer.cpp diff --git a/src/Cafe/HW/Espresso/EspressoISA.h b/src/Cafe/HW/Espresso/EspressoISA.h index b3ae45c3..e66e1424 100644 --- a/src/Cafe/HW/Espresso/EspressoISA.h +++ b/src/Cafe/HW/Espresso/EspressoISA.h @@ -91,13 +91,15 @@ namespace Espresso BCCTR = 528 }; - enum class OPCODE_31 + enum class Opcode31 { - + TW = 4, + MFTB = 371, }; inline PrimaryOpcode GetPrimaryOpcode(uint32 opcode) { return (PrimaryOpcode)(opcode >> 26); }; inline Opcode19 GetGroup19Opcode(uint32 opcode) { return (Opcode19)((opcode >> 1) & 0x3FF); }; + inline Opcode31 GetGroup31Opcode(uint32 opcode) { return (Opcode31)((opcode >> 1) & 0x3FF); }; struct BOField { @@ -132,6 +134,12 @@ namespace Espresso uint8 bo; }; + // returns true if LK bit is set, only valid for branch instructions + inline bool DecodeLK(uint32 opcode) + { + return (opcode & 1) != 0; + } + inline void _decodeForm_I(uint32 opcode, uint32& LI, bool& AA, bool& LK) { LI = opcode & 0x3fffffc; @@ -183,13 +191,7 @@ namespace Espresso _decodeForm_D_branch(opcode, BD, BO, BI, AA, LK); } - inline void decodeOp_BCLR(uint32 opcode, BOField& BO, uint32& BI, bool& LK) - { - // form XL (with BD field expected to be zero) - _decodeForm_XL(opcode, BO, BI, LK); - } - - inline void decodeOp_BCCTR(uint32 opcode, BOField& BO, uint32& BI, bool& LK) + inline void decodeOp_BCSPR(uint32 opcode, BOField& BO, uint32& BI, bool& LK) // BCLR and BCSPR { // form XL (with BD field expected to be zero) _decodeForm_XL(opcode, BO, BI, LK); diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterALU.hpp b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterALU.hpp index fe9316f0..769344f8 100644 --- a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterALU.hpp +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterALU.hpp @@ -3,12 +3,12 @@ static void PPCInterpreter_setXerOV(PPCInterpreter_t* hCPU, bool hasOverflow) { if (hasOverflow) { - hCPU->spr.XER |= XER_SO; - hCPU->spr.XER |= XER_OV; + hCPU->xer_so = 1; + hCPU->xer_ov = 1; } else { - hCPU->spr.XER &= ~XER_OV; + hCPU->xer_ov = 0; } } @@ -246,7 +246,7 @@ static void PPCInterpreter_SUBFCO(PPCInterpreter_t* hCPU, uint32 opcode) uint32 a = hCPU->gpr[rA]; uint32 b = hCPU->gpr[rB]; hCPU->gpr[rD] = ~a + b + 1; - // update xer + // update carry if (ppc_carry_3(~a, b, 1)) hCPU->xer_ca = 1; else @@ -848,8 +848,7 @@ static void PPCInterpreter_CMP(PPCInterpreter_t* hCPU, uint32 opcode) hCPU->cr[cr * 4 + CR_BIT_GT] = 1; else hCPU->cr[cr * 4 + CR_BIT_EQ] = 1; - if ((hCPU->spr.XER & XER_SO) != 0) - hCPU->cr[cr * 4 + CR_BIT_SO] = 1; + hCPU->cr[cr * 4 + CR_BIT_SO] = hCPU->xer_so; PPCInterpreter_nextInstruction(hCPU); } @@ -871,8 +870,7 @@ static void PPCInterpreter_CMPL(PPCInterpreter_t* hCPU, uint32 opcode) hCPU->cr[cr * 4 + CR_BIT_GT] = 1; else hCPU->cr[cr * 4 + CR_BIT_EQ] = 1; - if ((hCPU->spr.XER & XER_SO) != 0) - hCPU->cr[cr * 4 + CR_BIT_SO] = 1; + hCPU->cr[cr * 4 + CR_BIT_SO] = hCPU->xer_so; PPCInterpreter_nextInstruction(hCPU); } @@ -895,8 +893,7 @@ static void PPCInterpreter_CMPI(PPCInterpreter_t* hCPU, uint32 opcode) hCPU->cr[cr * 4 + CR_BIT_GT] = 1; else hCPU->cr[cr * 4 + CR_BIT_EQ] = 1; - if (hCPU->spr.XER & XER_SO) - hCPU->cr[cr * 4 + CR_BIT_SO] = 1; + hCPU->cr[cr * 4 + CR_BIT_SO] = hCPU->xer_so; PPCInterpreter_nextInstruction(hCPU); } @@ -919,8 +916,7 @@ static void PPCInterpreter_CMPLI(PPCInterpreter_t* hCPU, uint32 opcode) hCPU->cr[cr * 4 + CR_BIT_GT] = 1; else hCPU->cr[cr * 4 + CR_BIT_EQ] = 1; - if (hCPU->spr.XER & XER_SO) - hCPU->cr[cr * 4 + CR_BIT_SO] = 1; + hCPU->cr[cr * 4 + CR_BIT_SO] = hCPU->xer_so; PPCInterpreter_nextInstruction(hCPU); } diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterFPU.cpp b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterFPU.cpp index aed571d7..2c99b84c 100644 --- a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterFPU.cpp +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterFPU.cpp @@ -32,7 +32,7 @@ espresso_frsqrte_entry_t frsqrteLookupTable[32] = {0x20c1000, 0x35e},{0x1f12000, 0x332},{0x1d79000, 0x30a},{0x1bf4000, 0x2e6}, }; -double frsqrte_espresso(double input) +ATTR_MS_ABI double frsqrte_espresso(double input) { unsigned long long x = *(unsigned long long*)&input; @@ -111,7 +111,7 @@ espresso_fres_entry_t fresLookupTable[32] = {0x88400, 0x11a}, {0x65000, 0x11a}, {0x41c00, 0x108}, {0x20c00, 0x106} }; -double fres_espresso(double input) +ATTR_MS_ABI double fres_espresso(double input) { // based on testing we know that fres uses only the first 15 bits of the mantissa // seee eeee eeee mmmm mmmm mmmm mmmx xxxx .... (s = sign, e = exponent, m = mantissa, x = not used) diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h index bc8458d9..896fd21c 100644 --- a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h @@ -50,9 +50,9 @@ #define CR_BIT_EQ 2 #define CR_BIT_SO 3 -#define XER_SO (1<<31) // summary overflow bit -#define XER_OV (1<<30) // overflow bit #define XER_BIT_CA (29) // carry bit index. To accelerate frequent access, this bit is stored as a separate uint8 +#define XER_BIT_SO (31) // summary overflow, counterpart to CR SO +#define XER_BIT_OV (30) // FPSCR #define FPSCR_VXSNAN (1<<24) @@ -118,7 +118,8 @@ static inline void ppc_update_cr0(PPCInterpreter_t* hCPU, uint32 r) { - hCPU->cr[CR_BIT_SO] = (hCPU->spr.XER&XER_SO) ? 1 : 0; + cemu_assert_debug(hCPU->xer_so <= 1); + hCPU->cr[CR_BIT_SO] = hCPU->xer_so; hCPU->cr[CR_BIT_LT] = ((r != 0) ? 1 : 0) & ((r & 0x80000000) ? 1 : 0); hCPU->cr[CR_BIT_EQ] = (r == 0); hCPU->cr[CR_BIT_GT] = hCPU->cr[CR_BIT_EQ] ^ hCPU->cr[CR_BIT_LT] ^ 1; // this works because EQ and LT can never be set at the same time. So the only case where GT becomes 1 is when LT=0 and EQ=0 @@ -190,8 +191,8 @@ inline double roundTo25BitAccuracy(double d) return *(double*)&v; } -double fres_espresso(double input); -double frsqrte_espresso(double input); +ATTR_MS_ABI double fres_espresso(double input); +ATTR_MS_ABI double frsqrte_espresso(double input); void fcmpu_espresso(PPCInterpreter_t* hCPU, int crfD, double a, double b); diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterLoadStore.hpp b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterLoadStore.hpp index 694e05e6..26467458 100644 --- a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterLoadStore.hpp +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterLoadStore.hpp @@ -85,7 +85,8 @@ static void PPCInterpreter_STWCX(PPCInterpreter_t* hCPU, uint32 Opcode) ppc_setCRBit(hCPU, CR_BIT_GT, 0); ppc_setCRBit(hCPU, CR_BIT_EQ, 1); } - ppc_setCRBit(hCPU, CR_BIT_SO, (hCPU->spr.XER&XER_SO) != 0 ? 1 : 0); + cemu_assert_debug(hCPU->xer_so <= 1); + ppc_setCRBit(hCPU, CR_BIT_SO, hCPU->xer_so); // remove reservation hCPU->reservedMemAddr = 0; hCPU->reservedMemValue = 0; diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterMain.cpp b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterMain.cpp index ace1601f..08d6765a 100644 --- a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterMain.cpp +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterMain.cpp @@ -63,16 +63,24 @@ void PPCInterpreter_setDEC(PPCInterpreter_t* hCPU, uint32 newValue) uint32 PPCInterpreter_getXER(PPCInterpreter_t* hCPU) { uint32 xerValue = hCPU->spr.XER; - xerValue &= ~(1<<XER_BIT_CA); - if( hCPU->xer_ca ) - xerValue |= (1<<XER_BIT_CA); + xerValue &= ~(1 << XER_BIT_CA); + xerValue &= ~(1 << XER_BIT_SO); + xerValue &= ~(1 << XER_BIT_OV); + if (hCPU->xer_ca) + xerValue |= (1 << XER_BIT_CA); + if (hCPU->xer_so) + xerValue |= (1 << XER_BIT_SO); + if (hCPU->xer_ov) + xerValue |= (1 << XER_BIT_OV); return xerValue; } void PPCInterpreter_setXER(PPCInterpreter_t* hCPU, uint32 v) { hCPU->spr.XER = v; - hCPU->xer_ca = (v>>XER_BIT_CA)&1; + hCPU->xer_ca = (v >> XER_BIT_CA) & 1; + hCPU->xer_so = (v >> XER_BIT_SO) & 1; + hCPU->xer_ov = (v >> XER_BIT_OV) & 1; } uint32 PPCInterpreter_getCoreIndex(PPCInterpreter_t* hCPU) diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterOPC.cpp b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterOPC.cpp index 12f86427..d6b643ee 100644 --- a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterOPC.cpp +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterOPC.cpp @@ -5,7 +5,6 @@ #include "Cafe/OS/libs/coreinit/coreinit_CodeGen.h" #include "../Recompiler/PPCRecompiler.h" -#include "../Recompiler/PPCRecompilerX64.h" #include <float.h> #include "Cafe/HW/Latte/Core/LatteBufferCache.h" diff --git a/src/Cafe/HW/Espresso/PPCState.h b/src/Cafe/HW/Espresso/PPCState.h index c315ed0e..8f27ee93 100644 --- a/src/Cafe/HW/Espresso/PPCState.h +++ b/src/Cafe/HW/Espresso/PPCState.h @@ -49,6 +49,8 @@ struct PPCInterpreter_t uint32 fpscr; uint8 cr[32]; // 0 -> bit not set, 1 -> bit set (upper 7 bits of each byte must always be zero) (cr0 starts at index 0, cr1 at index 4 ..) uint8 xer_ca; // carry from xer + uint8 xer_so; + uint8 xer_ov; uint8 LSQE; uint8 PSE; // thread remaining cycles @@ -67,7 +69,8 @@ struct PPCInterpreter_t uint32 reservedMemValue; // temporary storage for recompiler FPR_t temporaryFPR[8]; - uint32 temporaryGPR[4]; + uint32 temporaryGPR[4]; // deprecated, refactor backend dependency on this away + uint32 temporaryGPR_reg[4]; // values below this are not used by Cafe OS usermode struct { diff --git a/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64.cpp b/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64.cpp new file mode 100644 index 00000000..6a8aac2b --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64.cpp @@ -0,0 +1,1668 @@ +#include "Cafe/HW/Espresso/PPCState.h" +#include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" +#include "Cafe/HW/Espresso/Interpreter/PPCInterpreterHelper.h" +#include "../PPCRecompiler.h" +#include "../PPCRecompilerIml.h" +#include "BackendX64.h" +#include "Cafe/OS/libs/coreinit/coreinit_Time.h" +#include "util/MemMapper/MemMapper.h" +#include "Common/cpu_features.h" +#include <boost/container/static_vector.hpp> + +static x86Assembler64::GPR32 _reg32(IMLReg physReg) +{ + cemu_assert_debug(physReg.GetRegFormat() == IMLRegFormat::I32); + IMLRegID regId = physReg.GetRegID(); + cemu_assert_debug(regId < 16); + return (x86Assembler64::GPR32)regId; +} + +static uint32 _reg64(IMLReg physReg) +{ + cemu_assert_debug(physReg.GetRegFormat() == IMLRegFormat::I64); + IMLRegID regId = physReg.GetRegID(); + cemu_assert_debug(regId < 16); + return regId; +} + +uint32 _regF64(IMLReg physReg) +{ + cemu_assert_debug(physReg.GetRegFormat() == IMLRegFormat::F64); + IMLRegID regId = physReg.GetRegID(); + cemu_assert_debug(regId >= IMLArchX86::PHYSREG_FPR_BASE && regId < IMLArchX86::PHYSREG_FPR_BASE+16); + regId -= IMLArchX86::PHYSREG_FPR_BASE; + return regId; +} + +static x86Assembler64::GPR8_REX _reg8(IMLReg physReg) +{ + cemu_assert_debug(physReg.GetRegFormat() == IMLRegFormat::I32); // for now these are represented as 32bit + return (x86Assembler64::GPR8_REX)physReg.GetRegID(); +} + +static x86Assembler64::GPR32 _reg32_from_reg8(x86Assembler64::GPR8_REX regId) +{ + return (x86Assembler64::GPR32)regId; +} + +static x86Assembler64::GPR8_REX _reg8_from_reg32(x86Assembler64::GPR32 regId) +{ + return (x86Assembler64::GPR8_REX)regId; +} + +static x86Assembler64::GPR8_REX _reg8_from_reg64(uint32 regId) +{ + return (x86Assembler64::GPR8_REX)regId; +} + +static x86Assembler64::GPR64 _reg64_from_reg32(x86Assembler64::GPR32 regId) +{ + return (x86Assembler64::GPR64)regId; +} + +X86Cond _x86Cond(IMLCondition imlCond) +{ + switch (imlCond) + { + case IMLCondition::EQ: + return X86_CONDITION_Z; + case IMLCondition::NEQ: + return X86_CONDITION_NZ; + case IMLCondition::UNSIGNED_GT: + return X86_CONDITION_NBE; + case IMLCondition::UNSIGNED_LT: + return X86_CONDITION_B; + case IMLCondition::SIGNED_GT: + return X86_CONDITION_NLE; + case IMLCondition::SIGNED_LT: + return X86_CONDITION_L; + default: + break; + } + cemu_assert_suspicious(); + return X86_CONDITION_Z; +} + +X86Cond _x86CondInverted(IMLCondition imlCond) +{ + switch (imlCond) + { + case IMLCondition::EQ: + return X86_CONDITION_NZ; + case IMLCondition::NEQ: + return X86_CONDITION_Z; + case IMLCondition::UNSIGNED_GT: + return X86_CONDITION_BE; + case IMLCondition::UNSIGNED_LT: + return X86_CONDITION_NB; + case IMLCondition::SIGNED_GT: + return X86_CONDITION_LE; + case IMLCondition::SIGNED_LT: + return X86_CONDITION_NL; + default: + break; + } + cemu_assert_suspicious(); + return X86_CONDITION_Z; +} + +X86Cond _x86Cond(IMLCondition imlCond, bool condIsInverted) +{ + if (condIsInverted) + return _x86CondInverted(imlCond); + return _x86Cond(imlCond); +} + +/* +* Remember current instruction output offset for reloc +* The instruction generated after this method has been called will be adjusted +*/ +void PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext_t* x64GenContext, void* extraInfo = nullptr) +{ + x64GenContext->relocateOffsetTable2.emplace_back(x64GenContext->emitter->GetWriteIndex(), extraInfo); +} + +void PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext_t* x64GenContext, sint32 jumpInstructionOffset, sint32 destinationOffset) +{ + uint8* instructionData = x64GenContext->emitter->GetBufferPtr() + jumpInstructionOffset; + if (instructionData[0] == 0x0F && (instructionData[1] >= 0x80 && instructionData[1] <= 0x8F)) + { + // far conditional jump + *(uint32*)(instructionData + 2) = (destinationOffset - (jumpInstructionOffset + 6)); + } + else if (instructionData[0] >= 0x70 && instructionData[0] <= 0x7F) + { + // short conditional jump + sint32 distance = (sint32)((destinationOffset - (jumpInstructionOffset + 2))); + cemu_assert_debug(distance >= -128 && distance <= 127); + *(uint8*)(instructionData + 1) = (uint8)distance; + } + else if (instructionData[0] == 0xE9) + { + *(uint32*)(instructionData + 1) = (destinationOffset - (jumpInstructionOffset + 5)); + } + else if (instructionData[0] == 0xEB) + { + sint32 distance = (sint32)((destinationOffset - (jumpInstructionOffset + 2))); + cemu_assert_debug(distance >= -128 && distance <= 127); + *(uint8*)(instructionData + 1) = (uint8)distance; + } + else + { + assert_dbg(); + } +} + +void* ATTR_MS_ABI PPCRecompiler_virtualHLE(PPCInterpreter_t* hCPU, uint32 hleFuncId) +{ + void* prevRSPTemp = hCPU->rspTemp; + if( hleFuncId == 0xFFD0 ) + { + hCPU->remainingCycles -= 500; // let subtract about 500 cycles for each HLE call + hCPU->gpr[3] = 0; + PPCInterpreter_nextInstruction(hCPU); + return hCPU; + } + else + { + auto hleCall = PPCInterpreter_getHLECall(hleFuncId); + cemu_assert(hleCall != nullptr); + hleCall(hCPU); + } + hCPU->rspTemp = prevRSPTemp; + return PPCInterpreter_getCurrentInstance(); +} + +bool PPCRecompilerX64Gen_imlInstruction_macro(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) +{ + if (imlInstruction->operation == PPCREC_IML_MACRO_B_TO_REG) + { + //x64Gen_int3(x64GenContext); + uint32 branchDstReg = _reg32(imlInstruction->op_macro.paramReg); + if(X86_REG_RDX != branchDstReg) + x64Gen_mov_reg64_reg64(x64GenContext, X86_REG_RDX, branchDstReg); + // potential optimization: Use branchDstReg directly if possible instead of moving to RDX/EDX + // JMP [offset+RDX*(8/4)+R15] + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0xFF); + x64Gen_writeU8(x64GenContext, 0xA4); + x64Gen_writeU8(x64GenContext, 0x57); + x64Gen_writeU32(x64GenContext, (uint32)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); + return true; + } + else if( imlInstruction->operation == PPCREC_IML_MACRO_BL ) + { + // MOV DWORD [SPR_LinkRegister], newLR + uint32 newLR = imlInstruction->op_macro.param + 4; + x64Gen_mov_mem32Reg64_imm32(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, spr.LR), newLR); + // remember new instruction pointer in RDX + uint32 newIP = imlInstruction->op_macro.param2; + x64Gen_mov_reg64Low32_imm32(x64GenContext, X86_REG_RDX, newIP); + // since RDX is constant we can use JMP [R15+const_offset] if jumpTableOffset+RDX*2 does not exceed the 2GB boundary + uint64 lookupOffset = (uint64)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable) + (uint64)newIP * 2ULL; + if (lookupOffset >= 0x80000000ULL) + { + // JMP [offset+RDX*(8/4)+R15] + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0xFF); + x64Gen_writeU8(x64GenContext, 0xA4); + x64Gen_writeU8(x64GenContext, 0x57); + x64Gen_writeU32(x64GenContext, (uint32)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); + } + else + { + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0xFF); + x64Gen_writeU8(x64GenContext, 0xA7); + x64Gen_writeU32(x64GenContext, (uint32)lookupOffset); + } + return true; + } + else if( imlInstruction->operation == PPCREC_IML_MACRO_B_FAR ) + { + // remember new instruction pointer in RDX + uint32 newIP = imlInstruction->op_macro.param2; + x64Gen_mov_reg64Low32_imm32(x64GenContext, X86_REG_RDX, newIP); + // Since RDX is constant we can use JMP [R15+const_offset] if jumpTableOffset+RDX*2 does not exceed the 2GB boundary + uint64 lookupOffset = (uint64)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable) + (uint64)newIP * 2ULL; + if (lookupOffset >= 0x80000000ULL) + { + // JMP [offset+RDX*(8/4)+R15] + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0xFF); + x64Gen_writeU8(x64GenContext, 0xA4); + x64Gen_writeU8(x64GenContext, 0x57); + x64Gen_writeU32(x64GenContext, (uint32)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); + } + else + { + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0xFF); + x64Gen_writeU8(x64GenContext, 0xA7); + x64Gen_writeU32(x64GenContext, (uint32)lookupOffset); + } + return true; + } + else if( imlInstruction->operation == PPCREC_IML_MACRO_LEAVE ) + { + uint32 currentInstructionAddress = imlInstruction->op_macro.param; + // remember PC value in REG_EDX + x64Gen_mov_reg64Low32_imm32(x64GenContext, X86_REG_RDX, currentInstructionAddress); + + uint32 newIP = 0; // special value for recompiler exit + uint64 lookupOffset = (uint64)&(((PPCRecompilerInstanceData_t*)NULL)->ppcRecompilerDirectJumpTable) + (uint64)newIP * 2ULL; + // JMP [R15+offset] + x64Gen_writeU8(x64GenContext, 0x41); + x64Gen_writeU8(x64GenContext, 0xFF); + x64Gen_writeU8(x64GenContext, 0xA7); + x64Gen_writeU32(x64GenContext, (uint32)lookupOffset); + return true; + } + else if( imlInstruction->operation == PPCREC_IML_MACRO_DEBUGBREAK ) + { + x64Gen_mov_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, imlInstruction->op_macro.param2); + x64Gen_int3(x64GenContext); + return true; + } + else if( imlInstruction->operation == PPCREC_IML_MACRO_COUNT_CYCLES ) + { + uint32 cycleCount = imlInstruction->op_macro.param; + x64Gen_sub_mem32reg64_imm32(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, remainingCycles), cycleCount); + return true; + } + else if( imlInstruction->operation == PPCREC_IML_MACRO_HLE ) + { + uint32 ppcAddress = imlInstruction->op_macro.param; + uint32 funcId = imlInstruction->op_macro.param2; + // update instruction pointer + x64Gen_mov_mem32Reg64_imm32(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, instructionPointer), ppcAddress); + // set parameters + x64Gen_mov_reg64_reg64(x64GenContext, X86_REG_RCX, REG_RESV_HCPU); + x64Gen_mov_reg64_imm64(x64GenContext, X86_REG_RDX, funcId); + // restore stackpointer from hCPU->rspTemp + x64Emit_mov_reg64_mem64(x64GenContext, X86_REG_RSP, REG_RESV_HCPU, offsetof(PPCInterpreter_t, rspTemp)); + // reserve space on stack for call parameters + x64Gen_sub_reg64_imm32(x64GenContext, X86_REG_RSP, 8*11); // must be uneven number in order to retain stack 0x10 alignment + x64Gen_mov_reg64_imm64(x64GenContext, X86_REG_RBP, 0); + // call HLE function + x64Gen_mov_reg64_imm64(x64GenContext, X86_REG_RAX, (uint64)PPCRecompiler_virtualHLE); + x64Gen_call_reg64(x64GenContext, X86_REG_RAX); + // restore RSP to hCPU (from RAX, result of PPCRecompiler_virtualHLE) + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_HCPU, X86_REG_RAX); + // MOV R15, ppcRecompilerInstanceData + x64Gen_mov_reg64_imm64(x64GenContext, REG_RESV_RECDATA, (uint64)ppcRecompilerInstanceData); + // MOV R13, memory_base + x64Gen_mov_reg64_imm64(x64GenContext, REG_RESV_MEMBASE, (uint64)memory_base); + // check if cycles where decreased beyond zero, if yes -> leave recompiler + x64Gen_bt_mem8(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, remainingCycles), 31); // check if negative + sint32 jumpInstructionOffset1 = x64GenContext->emitter->GetWriteIndex(); + x64Gen_jmpc_near(x64GenContext, X86_CONDITION_NOT_CARRY, 0); + + x64Emit_mov_reg64_mem32(x64GenContext, X86_REG_RDX, REG_RESV_HCPU, offsetof(PPCInterpreter_t, instructionPointer)); + // set EAX to 0 (we assume that ppcRecompilerDirectJumpTable[0] will be a recompiler escape function) + x64Gen_xor_reg32_reg32(x64GenContext, X86_REG_RAX, X86_REG_RAX); + // ADD RAX, REG_RESV_RECDATA + x64Gen_add_reg64_reg64(x64GenContext, X86_REG_RAX, REG_RESV_RECDATA); + // JMP [recompilerCallTable+EAX/4*8] + x64Gen_jmp_memReg64(x64GenContext, X86_REG_RAX, (uint32)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset1, x64GenContext->emitter->GetWriteIndex()); + // check if instruction pointer was changed + // assign new instruction pointer to EAX + x64Emit_mov_reg64_mem32(x64GenContext, X86_REG_RAX, REG_RESV_HCPU, offsetof(PPCInterpreter_t, instructionPointer)); + // remember instruction pointer in REG_EDX + x64Gen_mov_reg64_reg64(x64GenContext, X86_REG_RDX, X86_REG_RAX); + // EAX *= 2 + x64Gen_add_reg64_reg64(x64GenContext, X86_REG_RAX, X86_REG_RAX); + // ADD RAX, REG_RESV_RECDATA + x64Gen_add_reg64_reg64(x64GenContext, X86_REG_RAX, REG_RESV_RECDATA); + // JMP [ppcRecompilerDirectJumpTable+RAX/4*8] + x64Gen_jmp_memReg64(x64GenContext, X86_REG_RAX, (uint32)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); + return true; + } + else + { + debug_printf("Unknown recompiler macro operation %d\n", imlInstruction->operation); + assert_dbg(); + } + return false; +} + +/* +* Load from memory +*/ +bool PPCRecompilerX64Gen_imlInstruction_load(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction, bool indexed) +{ + cemu_assert_debug(imlInstruction->op_storeLoad.registerData.GetRegFormat() == IMLRegFormat::I32); + cemu_assert_debug(imlInstruction->op_storeLoad.registerMem.GetRegFormat() == IMLRegFormat::I32); + if (indexed) + cemu_assert_debug(imlInstruction->op_storeLoad.registerMem2.GetRegFormat() == IMLRegFormat::I32); + + IMLRegID realRegisterData = imlInstruction->op_storeLoad.registerData.GetRegID(); + IMLRegID realRegisterMem = imlInstruction->op_storeLoad.registerMem.GetRegID(); + IMLRegID realRegisterMem2 = PPC_REC_INVALID_REGISTER; + if( indexed ) + realRegisterMem2 = imlInstruction->op_storeLoad.registerMem2.GetRegID(); + if( indexed && realRegisterMem == realRegisterMem2 ) + { + return false; + } + if( indexed && realRegisterData == realRegisterMem2 ) + { + // for indexed memory access realRegisterData must not be the same register as the second memory register, + // this can easily be worked around by swapping realRegisterMem and realRegisterMem2 + std::swap(realRegisterMem, realRegisterMem2); + } + + bool signExtend = imlInstruction->op_storeLoad.flags2.signExtend; + bool switchEndian = imlInstruction->op_storeLoad.flags2.swapEndian; + if( imlInstruction->op_storeLoad.copyWidth == 32 ) + { + if (indexed) + { + x64Gen_lea_reg64Low32_reg64Low32PlusReg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem, realRegisterMem2); + } + if( g_CPUFeatures.x86.movbe && switchEndian ) + { + if (indexed) + { + x64Gen_movBEZeroExtend_reg64_mem32Reg64PlusReg64(x64GenContext, realRegisterData, REG_RESV_MEMBASE, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32); + } + else + { + x64Gen_movBEZeroExtend_reg64_mem32Reg64PlusReg64(x64GenContext, realRegisterData, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32); + } + } + else + { + if (indexed) + { + x64Emit_mov_reg32_mem32(x64GenContext, realRegisterData, REG_RESV_MEMBASE, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32); + if (switchEndian) + x64Gen_bswap_reg64Lower32bit(x64GenContext, realRegisterData); + } + else + { + x64Emit_mov_reg32_mem32(x64GenContext, realRegisterData, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32); + if (switchEndian) + x64Gen_bswap_reg64Lower32bit(x64GenContext, realRegisterData); + } + } + } + else if( imlInstruction->op_storeLoad.copyWidth == 16 ) + { + if (indexed) + { + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + } + if(g_CPUFeatures.x86.movbe && switchEndian ) + { + x64Gen_movBEZeroExtend_reg64Low16_mem16Reg64PlusReg64(x64GenContext, realRegisterData, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32); + if( indexed && realRegisterMem != realRegisterData ) + x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + } + else + { + x64Gen_movZeroExtend_reg64Low16_mem16Reg64PlusReg64(x64GenContext, realRegisterData, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32); + if( indexed && realRegisterMem != realRegisterData ) + x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + if( switchEndian ) + x64Gen_rol_reg64Low16_imm8(x64GenContext, realRegisterData, 8); + } + if( signExtend ) + x64Gen_movSignExtend_reg64Low32_reg64Low16(x64GenContext, realRegisterData, realRegisterData); + else + x64Gen_movZeroExtend_reg64Low32_reg64Low16(x64GenContext, realRegisterData, realRegisterData); + } + else if( imlInstruction->op_storeLoad.copyWidth == 8 ) + { + if( indexed ) + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + if( signExtend ) + x64Gen_movSignExtend_reg64Low32_mem8Reg64PlusReg64(x64GenContext, realRegisterData, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32); + else + x64Emit_movZX_reg32_mem8(x64GenContext, realRegisterData, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32); + if( indexed && realRegisterMem != realRegisterData ) + x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + } + else + return false; + return true; +} + +/* +* Write to memory +*/ +bool PPCRecompilerX64Gen_imlInstruction_store(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction, bool indexed) +{ + cemu_assert_debug(imlInstruction->op_storeLoad.registerData.GetRegFormat() == IMLRegFormat::I32); + cemu_assert_debug(imlInstruction->op_storeLoad.registerMem.GetRegFormat() == IMLRegFormat::I32); + if (indexed) + cemu_assert_debug(imlInstruction->op_storeLoad.registerMem2.GetRegFormat() == IMLRegFormat::I32); + + IMLRegID realRegisterData = imlInstruction->op_storeLoad.registerData.GetRegID(); + IMLRegID realRegisterMem = imlInstruction->op_storeLoad.registerMem.GetRegID(); + IMLRegID realRegisterMem2 = PPC_REC_INVALID_REGISTER; + if (indexed) + realRegisterMem2 = imlInstruction->op_storeLoad.registerMem2.GetRegID(); + + if (indexed && realRegisterMem == realRegisterMem2) + { + return false; + } + if (indexed && realRegisterData == realRegisterMem2) + { + // for indexed memory access realRegisterData must not be the same register as the second memory register, + // this can easily be worked around by swapping realRegisterMem and realRegisterMem2 + std::swap(realRegisterMem, realRegisterMem2); + } + + bool signExtend = imlInstruction->op_storeLoad.flags2.signExtend; + bool swapEndian = imlInstruction->op_storeLoad.flags2.swapEndian; + if (imlInstruction->op_storeLoad.copyWidth == 32) + { + uint32 valueRegister; + if ((swapEndian == false || g_CPUFeatures.x86.movbe) && realRegisterMem != realRegisterData) + { + valueRegister = realRegisterData; + } + else + { + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, realRegisterData); + valueRegister = REG_RESV_TEMP; + } + if (!g_CPUFeatures.x86.movbe && swapEndian) + x64Gen_bswap_reg64Lower32bit(x64GenContext, valueRegister); + if (indexed) + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + if (g_CPUFeatures.x86.movbe && swapEndian) + x64Gen_movBETruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32, valueRegister); + else + x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32, valueRegister); + if (indexed) + x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + } + else if (imlInstruction->op_storeLoad.copyWidth == 16) + { + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, realRegisterData); + if (swapEndian) + x64Gen_rol_reg64Low16_imm8(x64GenContext, REG_RESV_TEMP, 8); + if (indexed) + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + x64Gen_movTruncate_mem16Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP); + if (indexed) + x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + // todo: Optimize this, e.g. by using MOVBE + } + else if (imlInstruction->op_storeLoad.copyWidth == 8) + { + if (indexed && realRegisterMem == realRegisterData) + { + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, realRegisterData); + realRegisterData = REG_RESV_TEMP; + } + if (indexed) + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + x64Gen_movTruncate_mem8Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32, realRegisterData); + if (indexed) + x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); + } + else + return false; + return true; +} + +void PPCRecompilerX64Gen_imlInstruction_atomic_cmp_store(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) +{ + auto regBoolOut = _reg32_from_reg8(_reg8(imlInstruction->op_atomic_compare_store.regBoolOut)); + auto regEA = _reg32(imlInstruction->op_atomic_compare_store.regEA); + auto regVal = _reg32(imlInstruction->op_atomic_compare_store.regWriteValue); + auto regCmp = _reg32(imlInstruction->op_atomic_compare_store.regCompareValue); + + cemu_assert_debug(regBoolOut == X86_REG_EAX); + cemu_assert_debug(regEA != X86_REG_EAX); + cemu_assert_debug(regVal != X86_REG_EAX); + cemu_assert_debug(regCmp != X86_REG_EAX); + + x64GenContext->emitter->MOV_dd(X86_REG_EAX, regCmp); + x64GenContext->emitter->LockPrefix(); + x64GenContext->emitter->CMPXCHG_dd_l(REG_RESV_MEMBASE, 0, _reg64_from_reg32(regEA), 1, regVal); + x64GenContext->emitter->SETcc_b(X86Cond::X86_CONDITION_Z, regBoolOut); + x64GenContext->emitter->AND_di32(regBoolOut, 1); // SETcc doesn't clear the upper bits so we do it manually here +} + +void PPCRecompilerX64Gen_imlInstruction_call_imm(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) +{ + // the register allocator takes care of spilling volatile registers and moving parameters to the right registers, so we don't need to do any special handling here + x64GenContext->emitter->SUB_qi8(X86_REG_RSP, 0x20); // reserve enough space for any parameters while keeping stack alignment of 16 intact + x64GenContext->emitter->MOV_qi64(X86_REG_RAX, imlInstruction->op_call_imm.callAddress); + x64GenContext->emitter->CALL_q(X86_REG_RAX); + x64GenContext->emitter->ADD_qi8(X86_REG_RSP, 0x20); + // a note about the stack pointer: + // currently the code generated by generateEnterRecompilerCode makes sure the stack is 16 byte aligned, so we don't need to fix it up here +} + +bool PPCRecompilerX64Gen_imlInstruction_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) +{ + auto regR = _reg32(imlInstruction->op_r_r.regR); + auto regA = _reg32(imlInstruction->op_r_r.regA); + + if (imlInstruction->operation == PPCREC_IML_OP_ASSIGN) + { + // registerResult = registerA + if (regR != regA) + x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, regR, regA); + } + else if (imlInstruction->operation == PPCREC_IML_OP_ENDIAN_SWAP) + { + if (regA != regR) + x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, regR, regA); // if movbe is available we can move and swap in a single instruction? + x64Gen_bswap_reg64Lower32bit(x64GenContext, regR); + } + else if( imlInstruction->operation == PPCREC_IML_OP_ASSIGN_S8_TO_S32 ) + { + x64Gen_movSignExtend_reg64Low32_reg64Low8(x64GenContext, regR, regA); + } + else if (imlInstruction->operation == PPCREC_IML_OP_ASSIGN_S16_TO_S32) + { + x64Gen_movSignExtend_reg64Low32_reg64Low16(x64GenContext, regR, reg32ToReg16(regA)); + } + else if( imlInstruction->operation == PPCREC_IML_OP_NOT ) + { + // copy register content if different registers + if( regR != regA ) + x64Gen_mov_reg64_reg64(x64GenContext, regR, regA); + x64Gen_not_reg64Low32(x64GenContext, regR); + } + else if (imlInstruction->operation == PPCREC_IML_OP_NEG) + { + // copy register content if different registers + if (regR != regA) + x64Gen_mov_reg64_reg64(x64GenContext, regR, regA); + x64Gen_neg_reg64Low32(x64GenContext, regR); + } + else if( imlInstruction->operation == PPCREC_IML_OP_CNTLZW ) + { + // count leading zeros + // LZCNT instruction (part of SSE4, CPUID.80000001H:ECX.ABM[Bit 5]) + if(g_CPUFeatures.x86.lzcnt) + { + x64Gen_lzcnt_reg64Low32_reg64Low32(x64GenContext, regR, regA); + } + else + { + x64Gen_test_reg64Low32_reg64Low32(x64GenContext, regA, regA); + sint32 jumpInstructionOffset1 = x64GenContext->emitter->GetWriteIndex(); + x64Gen_jmpc_near(x64GenContext, X86_CONDITION_EQUAL, 0); + x64Gen_bsr_reg64Low32_reg64Low32(x64GenContext, regR, regA); + x64Gen_neg_reg64Low32(x64GenContext, regR); + x64Gen_add_reg64Low32_imm32(x64GenContext, regR, 32-1); + sint32 jumpInstructionOffset2 = x64GenContext->emitter->GetWriteIndex(); + x64Gen_jmpc_near(x64GenContext, X86_CONDITION_NONE, 0); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset1, x64GenContext->emitter->GetWriteIndex()); + x64Gen_mov_reg64Low32_imm32(x64GenContext, regR, 32); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset2, x64GenContext->emitter->GetWriteIndex()); + } + } + else if( imlInstruction->operation == PPCREC_IML_OP_X86_CMP) + { + x64GenContext->emitter->CMP_dd(regR, regA); + } + else + { + debug_printf("PPCRecompilerX64Gen_imlInstruction_r_r(): Unsupported operation 0x%x\n", imlInstruction->operation); + return false; + } + return true; +} + +bool PPCRecompilerX64Gen_imlInstruction_r_s32(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) +{ + auto regR = _reg32(imlInstruction->op_r_immS32.regR); + + if( imlInstruction->operation == PPCREC_IML_OP_ASSIGN ) + { + x64Gen_mov_reg64Low32_imm32(x64GenContext, regR, (uint32)imlInstruction->op_r_immS32.immS32); + } + else if( imlInstruction->operation == PPCREC_IML_OP_LEFT_ROTATE ) + { + cemu_assert_debug((imlInstruction->op_r_immS32.immS32 & 0x80) == 0); + x64Gen_rol_reg64Low32_imm8(x64GenContext, regR, (uint8)imlInstruction->op_r_immS32.immS32); + } + else if( imlInstruction->operation == PPCREC_IML_OP_X86_CMP) + { + sint32 imm = imlInstruction->op_r_immS32.immS32; + x64GenContext->emitter->CMP_di32(regR, imm); + } + else + { + debug_printf("PPCRecompilerX64Gen_imlInstruction_r_s32(): Unsupported operation 0x%x\n", imlInstruction->operation); + return false; + } + return true; +} + +bool PPCRecompilerX64Gen_imlInstruction_r_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) +{ + auto rRegResult = _reg32(imlInstruction->op_r_r_r.regR); + auto rRegOperand1 = _reg32(imlInstruction->op_r_r_r.regA); + auto rRegOperand2 = _reg32(imlInstruction->op_r_r_r.regB); + + if (imlInstruction->operation == PPCREC_IML_OP_ADD) + { + // registerResult = registerOperand1 + registerOperand2 + if( (rRegResult == rRegOperand1) || (rRegResult == rRegOperand2) ) + { + // be careful not to overwrite the operand before we use it + if( rRegResult == rRegOperand1 ) + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); + else + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand1); + } + else + { + // copy operand1 to destination register before doing addition + x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, rRegOperand1); + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); + } + } + else if( imlInstruction->operation == PPCREC_IML_OP_SUB ) + { + if( rRegOperand1 == rRegOperand2 ) + { + // result = operand1 - operand1 -> 0 + x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegResult); + } + else if( rRegResult == rRegOperand1 ) + { + // result = result - operand2 + x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); + } + else if ( rRegResult == rRegOperand2 ) + { + // result = operand1 - result + x64Gen_neg_reg64Low32(x64GenContext, rRegResult); + x64Gen_add_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand1); + } + else + { + x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, rRegOperand1); + x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); + } + } + else if (imlInstruction->operation == PPCREC_IML_OP_OR || imlInstruction->operation == PPCREC_IML_OP_AND || imlInstruction->operation == PPCREC_IML_OP_XOR) + { + if (rRegResult == rRegOperand2) + std::swap(rRegOperand1, rRegOperand2); + + if (rRegResult != rRegOperand1) + x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, rRegOperand1); + + if (imlInstruction->operation == PPCREC_IML_OP_OR) + x64Gen_or_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); + else if (imlInstruction->operation == PPCREC_IML_OP_AND) + x64Gen_and_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); + else + x64Gen_xor_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); + } + else if( imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_SIGNED ) + { + // registerResult = registerOperand1 * registerOperand2 + if( (rRegResult == rRegOperand1) || (rRegResult == rRegOperand2) ) + { + // be careful not to overwrite the operand before we use it + if( rRegResult == rRegOperand1 ) + x64Gen_imul_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); + else + x64Gen_imul_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand1); + } + else + { + // copy operand1 to destination register before doing multiplication + x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, rRegOperand1); + // add operand2 + x64Gen_imul_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); + } + } + else if( imlInstruction->operation == PPCREC_IML_OP_SLW || imlInstruction->operation == PPCREC_IML_OP_SRW ) + { + // registerResult = registerOperand1(rA) >> registerOperand2(rB) (up to 63 bits) + + if (g_CPUFeatures.x86.bmi2 && imlInstruction->operation == PPCREC_IML_OP_SRW) + { + // use BMI2 SHRX if available + x64Gen_shrx_reg64_reg64_reg64(x64GenContext, rRegResult, rRegOperand1, rRegOperand2); + } + else if (g_CPUFeatures.x86.bmi2 && imlInstruction->operation == PPCREC_IML_OP_SLW) + { + // use BMI2 SHLX if available + x64Gen_shlx_reg64_reg64_reg64(x64GenContext, rRegResult, rRegOperand1, rRegOperand2); + x64Gen_and_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegResult); // trim result to 32bit + } + else + { + // lazy and slow way to do shift by register without relying on ECX/CL or BMI2 + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperand1); + for (sint32 b = 0; b < 6; b++) + { + x64Gen_test_reg64Low32_imm32(x64GenContext, rRegOperand2, (1 << b)); + sint32 jumpInstructionOffset = x64GenContext->emitter->GetWriteIndex(); + x64Gen_jmpc_near(x64GenContext, X86_CONDITION_EQUAL, 0); // jump if bit not set + if (b == 5) + { + x64Gen_xor_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, REG_RESV_TEMP); + } + else + { + if (imlInstruction->operation == PPCREC_IML_OP_SLW) + x64Gen_shl_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, (1 << b)); + else + x64Gen_shr_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, (1 << b)); + } + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset, x64GenContext->emitter->GetWriteIndex()); + } + x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, REG_RESV_TEMP); + } + } + else if( imlInstruction->operation == PPCREC_IML_OP_LEFT_ROTATE ) + { + // todo: Use BMI2 rotate if available + // check if CL/ECX/RCX is available + if( rRegResult != X86_REG_RCX && rRegOperand1 != X86_REG_RCX && rRegOperand2 != X86_REG_RCX ) + { + // swap operand 2 with RCX + x64Gen_xchg_reg64_reg64(x64GenContext, X86_REG_RCX, rRegOperand2); + // move operand 1 to temp register + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperand1); + // rotate + x64Gen_rol_reg64Low32_cl(x64GenContext, REG_RESV_TEMP); + // undo swap operand 2 with RCX + x64Gen_xchg_reg64_reg64(x64GenContext, X86_REG_RCX, rRegOperand2); + // copy to result register + x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, REG_RESV_TEMP); + } + else + { + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperand1); + // lazy and slow way to do shift by register without relying on ECX/CL + for(sint32 b=0; b<5; b++) + { + x64Gen_test_reg64Low32_imm32(x64GenContext, rRegOperand2, (1<<b)); + sint32 jumpInstructionOffset = x64GenContext->emitter->GetWriteIndex(); + x64Gen_jmpc_near(x64GenContext, X86_CONDITION_EQUAL, 0); // jump if bit not set + x64Gen_rol_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, (1<<b)); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset, x64GenContext->emitter->GetWriteIndex()); + } + x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, REG_RESV_TEMP); + } + } + else if (imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_S || + imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_U || + imlInstruction->operation == PPCREC_IML_OP_LEFT_SHIFT) + { + if(g_CPUFeatures.x86.bmi2) + { + if (imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_S) + x64Gen_sarx_reg32_reg32_reg32(x64GenContext, rRegResult, rRegOperand1, rRegOperand2); + else if (imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_U) + x64Gen_shrx_reg32_reg32_reg32(x64GenContext, rRegResult, rRegOperand1, rRegOperand2); + else if (imlInstruction->operation == PPCREC_IML_OP_LEFT_SHIFT) + x64Gen_shlx_reg32_reg32_reg32(x64GenContext, rRegResult, rRegOperand1, rRegOperand2); + } + else + { + cemu_assert_debug(rRegOperand2 == X86_REG_ECX); + bool useTempReg = rRegResult == X86_REG_ECX && rRegOperand1 != X86_REG_ECX; + auto origRegResult = rRegResult; + if(useTempReg) + { + x64GenContext->emitter->MOV_dd(REG_RESV_TEMP, rRegOperand1); + rRegResult = REG_RESV_TEMP; + } + if(rRegOperand1 != rRegResult) + x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, rRegOperand1); + if (imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_S) + x64GenContext->emitter->SAR_d_CL(rRegResult); + else if (imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_U) + x64GenContext->emitter->SHR_d_CL(rRegResult); + else if (imlInstruction->operation == PPCREC_IML_OP_LEFT_SHIFT) + x64GenContext->emitter->SHL_d_CL(rRegResult); + if(useTempReg) + x64GenContext->emitter->MOV_dd(origRegResult, REG_RESV_TEMP); + } + } + else if( imlInstruction->operation == PPCREC_IML_OP_DIVIDE_SIGNED || imlInstruction->operation == PPCREC_IML_OP_DIVIDE_UNSIGNED ) + { + x64Emit_mov_mem32_reg32(x64GenContext, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[0]), X86_REG_EAX); + x64Emit_mov_mem32_reg32(x64GenContext, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[1]), X86_REG_EDX); + // mov operand 2 to temp register + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperand2); + // mov operand1 to EAX + x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, X86_REG_EAX, rRegOperand1); + // sign or zero extend EAX to EDX:EAX based on division sign mode + if( imlInstruction->operation == PPCREC_IML_OP_DIVIDE_SIGNED ) + x64Gen_cdq(x64GenContext); + else + x64Gen_xor_reg64Low32_reg64Low32(x64GenContext, X86_REG_EDX, X86_REG_EDX); + // make sure we avoid division by zero + x64Gen_test_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, REG_RESV_TEMP); + x64Gen_jmpc_near(x64GenContext, X86_CONDITION_EQUAL, 3); + // divide + if( imlInstruction->operation == PPCREC_IML_OP_DIVIDE_SIGNED ) + x64Gen_idiv_reg64Low32(x64GenContext, REG_RESV_TEMP); + else + x64Gen_div_reg64Low32(x64GenContext, REG_RESV_TEMP); + // result of division is now stored in EAX, move it to result register + if( rRegResult != X86_REG_EAX ) + x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, X86_REG_EAX); + // restore EAX / EDX + if( rRegResult != X86_REG_RAX ) + x64Emit_mov_reg64_mem32(x64GenContext, X86_REG_EAX, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[0])); + if( rRegResult != X86_REG_RDX ) + x64Emit_mov_reg64_mem32(x64GenContext, X86_REG_EDX, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[1])); + } + else if( imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_HIGH_SIGNED || imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_HIGH_UNSIGNED ) + { + x64Emit_mov_mem32_reg32(x64GenContext, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[0]), X86_REG_EAX); + x64Emit_mov_mem32_reg32(x64GenContext, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[1]), X86_REG_EDX); + // mov operand 2 to temp register + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperand2); + // mov operand1 to EAX + x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, X86_REG_EAX, rRegOperand1); + if( imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_HIGH_SIGNED ) + { + // zero extend EAX to EDX:EAX + x64Gen_xor_reg64Low32_reg64Low32(x64GenContext, X86_REG_EDX, X86_REG_EDX); + } + else + { + // sign extend EAX to EDX:EAX + x64Gen_cdq(x64GenContext); + } + // multiply + if( imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_HIGH_SIGNED ) + x64Gen_imul_reg64Low32(x64GenContext, REG_RESV_TEMP); + else + x64Gen_mul_reg64Low32(x64GenContext, REG_RESV_TEMP); + // result of multiplication is now stored in EDX:EAX, move it to result register + if( rRegResult != X86_REG_EDX ) + x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, X86_REG_EDX); + // restore EAX / EDX + if( rRegResult != X86_REG_RAX ) + x64Emit_mov_reg64_mem32(x64GenContext, X86_REG_EAX, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[0])); + if( rRegResult != X86_REG_RDX ) + x64Emit_mov_reg64_mem32(x64GenContext, X86_REG_EDX, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[1])); + } + else + { + debug_printf("PPCRecompilerX64Gen_imlInstruction_r_r_r(): Unsupported operation 0x%x\n", imlInstruction->operation); + return false; + } + return true; +} + +bool PPCRecompilerX64Gen_imlInstruction_r_r_r_carry(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) +{ + auto regR = _reg32(imlInstruction->op_r_r_r_carry.regR); + auto regA = _reg32(imlInstruction->op_r_r_r_carry.regA); + auto regB = _reg32(imlInstruction->op_r_r_r_carry.regB); + auto regCarry = _reg32(imlInstruction->op_r_r_r_carry.regCarry); + bool carryRegIsShared = regCarry == regA || regCarry == regB; + cemu_assert_debug(regCarry != regR); // two outputs sharing the same register is undefined behavior + + switch (imlInstruction->operation) + { + case PPCREC_IML_OP_ADD: + if (regB == regR) + std::swap(regB, regA); + if (regR != regA) + x64GenContext->emitter->MOV_dd(regR, regA); + if(!carryRegIsShared) + x64GenContext->emitter->XOR_dd(regCarry, regCarry); + x64GenContext->emitter->ADD_dd(regR, regB); + x64GenContext->emitter->SETcc_b(X86_CONDITION_B, _reg8_from_reg32(regCarry)); // below condition checks carry flag + if(carryRegIsShared) + x64GenContext->emitter->AND_di8(regCarry, 1); // clear upper bits + break; + case PPCREC_IML_OP_ADD_WITH_CARRY: + // assumes that carry is already correctly initialized as 0 or 1 + if (regB == regR) + std::swap(regB, regA); + if (regR != regA) + x64GenContext->emitter->MOV_dd(regR, regA); + x64GenContext->emitter->BT_du8(regCarry, 0); // copy carry register to x86 carry flag + x64GenContext->emitter->ADC_dd(regR, regB); + x64GenContext->emitter->SETcc_b(X86_CONDITION_B, _reg8_from_reg32(regCarry)); + break; + default: + cemu_assert_unimplemented(); + return false; + } + return true; +} + +bool PPCRecompilerX64Gen_IsSameCompare(IMLInstruction* imlInstructionA, IMLInstruction* imlInstructionB) +{ + if(imlInstructionA->type != imlInstructionB->type) + return false; + if(imlInstructionA->type == PPCREC_IML_TYPE_COMPARE) + return imlInstructionA->op_compare.regA == imlInstructionB->op_compare.regA && imlInstructionA->op_compare.regB == imlInstructionB->op_compare.regB; + else if(imlInstructionA->type == PPCREC_IML_TYPE_COMPARE_S32) + return imlInstructionA->op_compare_s32.regA == imlInstructionB->op_compare_s32.regA && imlInstructionA->op_compare_s32.immS32 == imlInstructionB->op_compare_s32.immS32; + return false; +} + +bool PPCRecompilerX64Gen_imlInstruction_compare_x(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction, sint32& extraInstructionsProcessed) +{ + extraInstructionsProcessed = 0; + boost::container::static_vector<IMLInstruction*, 4> compareInstructions; + compareInstructions.push_back(imlInstruction); + for(sint32 i=1; i<4; i++) + { + IMLInstruction* nextIns = x64GenContext->GetNextInstruction(i); + if(!nextIns || !PPCRecompilerX64Gen_IsSameCompare(imlInstruction, nextIns)) + break; + compareInstructions.push_back(nextIns); + } + auto OperandOverlapsWithR = [&](IMLInstruction* ins) -> bool + { + cemu_assert_debug(ins->type == PPCREC_IML_TYPE_COMPARE || ins->type == PPCREC_IML_TYPE_COMPARE_S32); + if(ins->type == PPCREC_IML_TYPE_COMPARE) + return _reg32_from_reg8(_reg8(ins->op_compare.regR)) == _reg32(ins->op_compare.regA) || _reg32_from_reg8(_reg8(ins->op_compare.regR)) == _reg32(ins->op_compare.regB); + else /* PPCREC_IML_TYPE_COMPARE_S32 */ + return _reg32_from_reg8(_reg8(ins->op_compare_s32.regR)) == _reg32(ins->op_compare_s32.regA); + }; + auto GetRegR = [](IMLInstruction* insn) + { + return insn->type == PPCREC_IML_TYPE_COMPARE ? _reg32_from_reg8(_reg8(insn->op_compare.regR)) : _reg32_from_reg8(_reg8(insn->op_compare_s32.regR)); + }; + // prefer XOR method for zeroing out registers if possible + for(auto& it : compareInstructions) + { + if(OperandOverlapsWithR(it)) + continue; + auto regR = GetRegR(it); + x64GenContext->emitter->XOR_dd(regR, regR); // zero bytes unaffected by SETcc + } + // emit the compare instruction + if(imlInstruction->type == PPCREC_IML_TYPE_COMPARE) + { + auto regA = _reg32(imlInstruction->op_compare.regA); + auto regB = _reg32(imlInstruction->op_compare.regB); + x64GenContext->emitter->CMP_dd(regA, regB); + } + else if(imlInstruction->type == PPCREC_IML_TYPE_COMPARE_S32) + { + auto regA = _reg32(imlInstruction->op_compare_s32.regA); + sint32 imm = imlInstruction->op_compare_s32.immS32; + x64GenContext->emitter->CMP_di32(regA, imm); + } + // emit the SETcc instructions + for(auto& it : compareInstructions) + { + auto regR = _reg8(it->op_compare.regR); + X86Cond cond = _x86Cond(it->op_compare.cond); + if(OperandOverlapsWithR(it)) + x64GenContext->emitter->MOV_di32(_reg32_from_reg8(regR), 0); + x64GenContext->emitter->SETcc_b(cond, regR); + } + extraInstructionsProcessed = (sint32)compareInstructions.size() - 1; + return true; +} + +bool PPCRecompilerX64Gen_imlInstruction_cjump2(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction, IMLSegment* imlSegment) +{ + auto regBool = _reg8(imlInstruction->op_conditional_jump.registerBool); + bool mustBeTrue = imlInstruction->op_conditional_jump.mustBeTrue; + x64GenContext->emitter->TEST_bb(regBool, regBool); + PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, imlSegment->nextSegmentBranchTaken); + x64GenContext->emitter->Jcc_j32(mustBeTrue ? X86_CONDITION_NZ : X86_CONDITION_Z, 0); + return true; +} + +void PPCRecompilerX64Gen_imlInstruction_x86_eflags_jcc(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction, IMLSegment* imlSegment) +{ + X86Cond cond = _x86Cond(imlInstruction->op_x86_eflags_jcc.cond, imlInstruction->op_x86_eflags_jcc.invertedCondition); + PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, imlSegment->nextSegmentBranchTaken); + x64GenContext->emitter->Jcc_j32(cond, 0); +} + +bool PPCRecompilerX64Gen_imlInstruction_jump2(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction, IMLSegment* imlSegment) +{ + PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, imlSegment->nextSegmentBranchTaken); + x64GenContext->emitter->JMP_j32(0); + return true; +} + +bool PPCRecompilerX64Gen_imlInstruction_r_r_s32(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) +{ + auto regR = _reg32(imlInstruction->op_r_r_s32.regR); + auto regA = _reg32(imlInstruction->op_r_r_s32.regA); + uint32 immS32 = imlInstruction->op_r_r_s32.immS32; + + if( imlInstruction->operation == PPCREC_IML_OP_ADD ) + { + uint32 immU32 = (uint32)imlInstruction->op_r_r_s32.immS32; + if(regR != regA) + x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, regR, regA); + x64Gen_add_reg64Low32_imm32(x64GenContext, regR, (uint32)immU32); + } + else if (imlInstruction->operation == PPCREC_IML_OP_SUB) + { + if (regR != regA) + x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, regR, regA); + x64Gen_sub_reg64Low32_imm32(x64GenContext, regR, immS32); + } + else if (imlInstruction->operation == PPCREC_IML_OP_AND || + imlInstruction->operation == PPCREC_IML_OP_OR || + imlInstruction->operation == PPCREC_IML_OP_XOR) + { + if (regR != regA) + x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, regR, regA); + if (imlInstruction->operation == PPCREC_IML_OP_AND) + x64Gen_and_reg64Low32_imm32(x64GenContext, regR, immS32); + else if (imlInstruction->operation == PPCREC_IML_OP_OR) + x64Gen_or_reg64Low32_imm32(x64GenContext, regR, immS32); + else // XOR + x64Gen_xor_reg64Low32_imm32(x64GenContext, regR, immS32); + } + else if( imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_SIGNED ) + { + // registerResult = registerOperand * immS32 + sint32 immS32 = (uint32)imlInstruction->op_r_r_s32.immS32; + x64Gen_mov_reg64_imm64(x64GenContext, REG_RESV_TEMP, (sint64)immS32); // todo: Optimize + if( regR != regA ) + x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, regR, regA); + x64Gen_imul_reg64Low32_reg64Low32(x64GenContext, regR, REG_RESV_TEMP); + } + else if (imlInstruction->operation == PPCREC_IML_OP_LEFT_SHIFT || + imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_U || + imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_S) + { + if( regA != regR ) + x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, regR, regA); + if (imlInstruction->operation == PPCREC_IML_OP_LEFT_SHIFT) + x64Gen_shl_reg64Low32_imm8(x64GenContext, regR, imlInstruction->op_r_r_s32.immS32); + else if (imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_U) + x64Gen_shr_reg64Low32_imm8(x64GenContext, regR, imlInstruction->op_r_r_s32.immS32); + else // RIGHT_SHIFT_S + x64Gen_sar_reg64Low32_imm8(x64GenContext, regR, imlInstruction->op_r_r_s32.immS32); + } + else + { + debug_printf("PPCRecompilerX64Gen_imlInstruction_r_r_s32(): Unsupported operation 0x%x\n", imlInstruction->operation); + return false; + } + return true; +} + +bool PPCRecompilerX64Gen_imlInstruction_r_r_s32_carry(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) +{ + auto regR = _reg32(imlInstruction->op_r_r_s32_carry.regR); + auto regA = _reg32(imlInstruction->op_r_r_s32_carry.regA); + sint32 immS32 = imlInstruction->op_r_r_s32_carry.immS32; + auto regCarry = _reg32(imlInstruction->op_r_r_s32_carry.regCarry); + cemu_assert_debug(regCarry != regR); // we dont allow two different outputs sharing the same register + + bool delayCarryInit = regCarry == regA; + + switch (imlInstruction->operation) + { + case PPCREC_IML_OP_ADD: + if(!delayCarryInit) + x64GenContext->emitter->XOR_dd(regCarry, regCarry); + if (regR != regA) + x64GenContext->emitter->MOV_dd(regR, regA); + x64GenContext->emitter->ADD_di32(regR, immS32); + if(delayCarryInit) + x64GenContext->emitter->MOV_di32(regCarry, 0); + x64GenContext->emitter->SETcc_b(X86_CONDITION_B, _reg8_from_reg32(regCarry)); + break; + case PPCREC_IML_OP_ADD_WITH_CARRY: + // assumes that carry is already correctly initialized as 0 or 1 + cemu_assert_debug(regCarry != regR); + if (regR != regA) + x64GenContext->emitter->MOV_dd(regR, regA); + x64GenContext->emitter->BT_du8(regCarry, 0); // copy carry register to x86 carry flag + x64GenContext->emitter->ADC_di32(regR, immS32); + x64GenContext->emitter->SETcc_b(X86_CONDITION_B, _reg8_from_reg32(regCarry)); + break; + default: + cemu_assert_unimplemented(); + return false; + } + return true; +} + +bool PPCRecompilerX64Gen_imlInstruction_conditionalJumpCycleCheck(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) +{ + // some tests (all performed on a i7-4790K) + // 1) DEC [mem] + JNS has significantly worse performance than BT + JNC (probably due to additional memory write and direct dependency) + // 2) CMP [mem], 0 + JG has about equal (or slightly worse) performance than BT + JNC + + // BT + x64Gen_bt_mem8(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, remainingCycles), 31); // check if negative + cemu_assert_debug(x64GenContext->currentSegment->GetBranchTaken()); + PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, x64GenContext->currentSegment->GetBranchTaken()); + x64Gen_jmpc_far(x64GenContext, X86_CONDITION_CARRY, 0); + return true; +} + +void PPCRecompilerX64Gen_imlInstruction_r_name(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) +{ + uint32 name = imlInstruction->op_r_name.name; + if (imlInstruction->op_r_name.regR.GetBaseFormat() == IMLRegFormat::I64) + { + auto regR = _reg64(imlInstruction->op_r_name.regR); + if (name >= PPCREC_NAME_R0 && name < PPCREC_NAME_R0 + 32) + { + x64Emit_mov_reg64_mem32(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, gpr) + sizeof(uint32) * (name - PPCREC_NAME_R0)); + } + else if (name >= PPCREC_NAME_SPR0 && name < PPCREC_NAME_SPR0 + 999) + { + sint32 sprIndex = (name - PPCREC_NAME_SPR0); + if (sprIndex == SPR_LR) + x64Emit_mov_reg64_mem32(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, spr.LR)); + else if (sprIndex == SPR_CTR) + x64Emit_mov_reg64_mem32(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, spr.CTR)); + else if (sprIndex == SPR_XER) + x64Emit_mov_reg64_mem32(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, spr.XER)); + else if (sprIndex >= SPR_UGQR0 && sprIndex <= SPR_UGQR7) + { + sint32 memOffset = offsetof(PPCInterpreter_t, spr.UGQR) + sizeof(PPCInterpreter_t::spr.UGQR[0]) * (sprIndex - SPR_UGQR0); + x64Emit_mov_reg64_mem32(x64GenContext, regR, REG_RESV_HCPU, memOffset); + } + else + assert_dbg(); + } + else if (name >= PPCREC_NAME_TEMPORARY && name < PPCREC_NAME_TEMPORARY + 4) + { + x64Emit_mov_reg64_mem32(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryGPR_reg) + sizeof(uint32) * (name - PPCREC_NAME_TEMPORARY)); + } + else if (name == PPCREC_NAME_XER_CA) + { + x64Emit_movZX_reg64_mem8(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, xer_ca)); + } + else if (name == PPCREC_NAME_XER_SO) + { + x64Emit_movZX_reg64_mem8(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, xer_so)); + } + else if (name >= PPCREC_NAME_CR && name <= PPCREC_NAME_CR_LAST) + { + x64Emit_movZX_reg64_mem8(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, cr) + (name - PPCREC_NAME_CR)); + } + else if (name == PPCREC_NAME_CPU_MEMRES_EA) + { + x64Emit_mov_reg64_mem32(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, reservedMemAddr)); + } + else if (name == PPCREC_NAME_CPU_MEMRES_VAL) + { + x64Emit_mov_reg64_mem32(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, reservedMemValue)); + } + else + assert_dbg(); + } + else if (imlInstruction->op_r_name.regR.GetBaseFormat() == IMLRegFormat::F64) + { + auto regR = _regF64(imlInstruction->op_r_name.regR); + if (name >= PPCREC_NAME_FPR0 && name < (PPCREC_NAME_FPR0 + 32)) + { + x64Gen_movupd_xmmReg_memReg128(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, fpr) + sizeof(FPR_t) * (name - PPCREC_NAME_FPR0)); + } + else if (name >= PPCREC_NAME_TEMPORARY_FPR0 || name < (PPCREC_NAME_TEMPORARY_FPR0 + 8)) + { + x64Gen_movupd_xmmReg_memReg128(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR) + sizeof(FPR_t) * (name - PPCREC_NAME_TEMPORARY_FPR0)); + } + else + { + cemu_assert_debug(false); + } + } + else + DEBUG_BREAK; + +} + +void PPCRecompilerX64Gen_imlInstruction_name_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) +{ + uint32 name = imlInstruction->op_r_name.name; + + if (imlInstruction->op_r_name.regR.GetBaseFormat() == IMLRegFormat::I64) + { + auto regR = _reg64(imlInstruction->op_r_name.regR); + if (name >= PPCREC_NAME_R0 && name < PPCREC_NAME_R0 + 32) + { + x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, gpr) + sizeof(uint32) * (name - PPCREC_NAME_R0), regR); + } + else if (name >= PPCREC_NAME_SPR0 && name < PPCREC_NAME_SPR0 + 999) + { + uint32 sprIndex = (name - PPCREC_NAME_SPR0); + if (sprIndex == SPR_LR) + x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, spr.LR), regR); + else if (sprIndex == SPR_CTR) + x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, spr.CTR), regR); + else if (sprIndex == SPR_XER) + x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, spr.XER), regR); + else if (sprIndex >= SPR_UGQR0 && sprIndex <= SPR_UGQR7) + { + sint32 memOffset = offsetof(PPCInterpreter_t, spr.UGQR) + sizeof(PPCInterpreter_t::spr.UGQR[0]) * (sprIndex - SPR_UGQR0); + x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, memOffset, regR); + } + else + assert_dbg(); + } + else if (name >= PPCREC_NAME_TEMPORARY && name < PPCREC_NAME_TEMPORARY + 4) + { + x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryGPR_reg) + sizeof(uint32) * (name - PPCREC_NAME_TEMPORARY), regR); + } + else if (name == PPCREC_NAME_XER_CA) + { + x64GenContext->emitter->MOV_bb_l(REG_RESV_HCPU, offsetof(PPCInterpreter_t, xer_ca), X86_REG_NONE, 0, _reg8_from_reg64(regR)); + } + else if (name == PPCREC_NAME_XER_SO) + { + x64GenContext->emitter->MOV_bb_l(REG_RESV_HCPU, offsetof(PPCInterpreter_t, xer_so), X86_REG_NONE, 0, _reg8_from_reg64(regR)); + } + else if (name >= PPCREC_NAME_CR && name <= PPCREC_NAME_CR_LAST) + { + x64GenContext->emitter->MOV_bb_l(REG_RESV_HCPU, offsetof(PPCInterpreter_t, cr) + (name - PPCREC_NAME_CR), X86_REG_NONE, 0, _reg8_from_reg64(regR)); + } + else if (name == PPCREC_NAME_CPU_MEMRES_EA) + { + x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, reservedMemAddr), regR); + } + else if (name == PPCREC_NAME_CPU_MEMRES_VAL) + { + x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, reservedMemValue), regR); + } + else + assert_dbg(); + } + else if (imlInstruction->op_r_name.regR.GetBaseFormat() == IMLRegFormat::F64) + { + auto regR = _regF64(imlInstruction->op_r_name.regR); + uint32 name = imlInstruction->op_r_name.name; + if (name >= PPCREC_NAME_FPR0 && name < (PPCREC_NAME_FPR0 + 32)) + { + x64Gen_movupd_memReg128_xmmReg(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, fpr) + sizeof(FPR_t) * (name - PPCREC_NAME_FPR0)); + } + else if (name >= PPCREC_NAME_TEMPORARY_FPR0 && name < (PPCREC_NAME_TEMPORARY_FPR0 + 8)) + { + x64Gen_movupd_memReg128_xmmReg(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR) + sizeof(FPR_t) * (name - PPCREC_NAME_TEMPORARY_FPR0)); + } + else + { + cemu_assert_debug(false); + } + } + else + DEBUG_BREAK; + + +} + +uint8* codeMemoryBlock = nullptr; +sint32 codeMemoryBlockIndex = 0; +sint32 codeMemoryBlockSize = 0; + +std::mutex mtx_allocExecutableMemory; + +uint8* PPCRecompilerX86_allocateExecutableMemory(sint32 size) +{ + std::lock_guard<std::mutex> lck(mtx_allocExecutableMemory); + if( codeMemoryBlockIndex+size > codeMemoryBlockSize ) + { + // allocate new block + codeMemoryBlockSize = std::max(1024*1024*4, size+1024); // 4MB (or more if the function is larger than 4MB) + codeMemoryBlockIndex = 0; + codeMemoryBlock = (uint8*)MemMapper::AllocateMemory(nullptr, codeMemoryBlockSize, MemMapper::PAGE_PERMISSION::P_RWX); + } + uint8* codeMem = codeMemoryBlock + codeMemoryBlockIndex; + codeMemoryBlockIndex += size; + // pad to 4 byte alignment + while (codeMemoryBlockIndex & 3) + { + codeMemoryBlock[codeMemoryBlockIndex] = 0x90; + codeMemoryBlockIndex++; + } + return codeMem; +} + +bool PPCRecompiler_generateX64Code(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext) +{ + x64GenContext_t x64GenContext{}; + + // generate iml instruction code + bool codeGenerationFailed = false; + for (IMLSegment* segIt : ppcImlGenContext->segmentList2) + { + x64GenContext.currentSegment = segIt; + segIt->x64Offset = x64GenContext.emitter->GetWriteIndex(); + for(size_t i=0; i<segIt->imlList.size(); i++) + { + x64GenContext.m_currentInstructionEmitIndex = i; + IMLInstruction* imlInstruction = segIt->imlList.data() + i; + + if( imlInstruction->type == PPCREC_IML_TYPE_R_NAME ) + { + PPCRecompilerX64Gen_imlInstruction_r_name(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); + } + else if( imlInstruction->type == PPCREC_IML_TYPE_NAME_R ) + { + PPCRecompilerX64Gen_imlInstruction_name_r(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); + } + else if( imlInstruction->type == PPCREC_IML_TYPE_R_R ) + { + if( PPCRecompilerX64Gen_imlInstruction_r_r(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction) == false ) + codeGenerationFailed = true; + } + else if (imlInstruction->type == PPCREC_IML_TYPE_R_S32) + { + if (PPCRecompilerX64Gen_imlInstruction_r_s32(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction) == false) + codeGenerationFailed = true; + } + else if (imlInstruction->type == PPCREC_IML_TYPE_R_R_S32) + { + if (PPCRecompilerX64Gen_imlInstruction_r_r_s32(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction) == false) + codeGenerationFailed = true; + } + else if (imlInstruction->type == PPCREC_IML_TYPE_R_R_S32_CARRY) + { + if (PPCRecompilerX64Gen_imlInstruction_r_r_s32_carry(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction) == false) + codeGenerationFailed = true; + } + else if (imlInstruction->type == PPCREC_IML_TYPE_R_R_R) + { + if (PPCRecompilerX64Gen_imlInstruction_r_r_r(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction) == false) + codeGenerationFailed = true; + } + else if (imlInstruction->type == PPCREC_IML_TYPE_R_R_R_CARRY) + { + if (PPCRecompilerX64Gen_imlInstruction_r_r_r_carry(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction) == false) + codeGenerationFailed = true; + } + else if (imlInstruction->type == PPCREC_IML_TYPE_COMPARE || imlInstruction->type == PPCREC_IML_TYPE_COMPARE_S32) + { + sint32 extraInstructionsProcessed; + PPCRecompilerX64Gen_imlInstruction_compare_x(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, extraInstructionsProcessed); + i += extraInstructionsProcessed; + } + else if (imlInstruction->type == PPCREC_IML_TYPE_CONDITIONAL_JUMP) + { + if (PPCRecompilerX64Gen_imlInstruction_cjump2(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, segIt) == false) + codeGenerationFailed = true; + } + else if(imlInstruction->type == PPCREC_IML_TYPE_X86_EFLAGS_JCC) + { + PPCRecompilerX64Gen_imlInstruction_x86_eflags_jcc(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, segIt); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_JUMP) + { + if (PPCRecompilerX64Gen_imlInstruction_jump2(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, segIt) == false) + codeGenerationFailed = true; + } + else if( imlInstruction->type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK ) + { + PPCRecompilerX64Gen_imlInstruction_conditionalJumpCycleCheck(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); + } + else if( imlInstruction->type == PPCREC_IML_TYPE_MACRO ) + { + if( PPCRecompilerX64Gen_imlInstruction_macro(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction) == false ) + { + codeGenerationFailed = true; + } + } + else if( imlInstruction->type == PPCREC_IML_TYPE_LOAD ) + { + if( PPCRecompilerX64Gen_imlInstruction_load(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, false) == false ) + { + codeGenerationFailed = true; + } + } + else if( imlInstruction->type == PPCREC_IML_TYPE_LOAD_INDEXED ) + { + if( PPCRecompilerX64Gen_imlInstruction_load(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, true) == false ) + { + codeGenerationFailed = true; + } + } + else if( imlInstruction->type == PPCREC_IML_TYPE_STORE ) + { + if( PPCRecompilerX64Gen_imlInstruction_store(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, false) == false ) + { + codeGenerationFailed = true; + } + } + else if( imlInstruction->type == PPCREC_IML_TYPE_STORE_INDEXED ) + { + if( PPCRecompilerX64Gen_imlInstruction_store(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, true) == false ) + { + codeGenerationFailed = true; + } + } + else if (imlInstruction->type == PPCREC_IML_TYPE_ATOMIC_CMP_STORE) + { + PPCRecompilerX64Gen_imlInstruction_atomic_cmp_store(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_CALL_IMM) + { + PPCRecompilerX64Gen_imlInstruction_call_imm(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); + } + else if( imlInstruction->type == PPCREC_IML_TYPE_NO_OP ) + { + // no op + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD ) + { + if( PPCRecompilerX64Gen_imlInstruction_fpr_load(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, false) == false ) + { + codeGenerationFailed = true; + } + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED ) + { + if( PPCRecompilerX64Gen_imlInstruction_fpr_load(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, true) == false ) + { + codeGenerationFailed = true; + } + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE ) + { + if( PPCRecompilerX64Gen_imlInstruction_fpr_store(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, false) == false ) + { + codeGenerationFailed = true; + } + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE_INDEXED ) + { + if( PPCRecompilerX64Gen_imlInstruction_fpr_store(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, true) == false ) + { + codeGenerationFailed = true; + } + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R ) + { + PPCRecompilerX64Gen_imlInstruction_fpr_r_r(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R ) + { + PPCRecompilerX64Gen_imlInstruction_fpr_r_r_r(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R_R ) + { + PPCRecompilerX64Gen_imlInstruction_fpr_r_r_r_r(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); + } + else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R ) + { + PPCRecompilerX64Gen_imlInstruction_fpr_r(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_COMPARE) + { + PPCRecompilerX64Gen_imlInstruction_fpr_compare(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); + } + else + { + debug_printf("PPCRecompiler_generateX64Code(): Unsupported iml type 0x%x\n", imlInstruction->type); + assert_dbg(); + } + } + } + // handle failed code generation + if( codeGenerationFailed ) + { + return false; + } + // allocate executable memory + uint8* executableMemory = PPCRecompilerX86_allocateExecutableMemory(x64GenContext.emitter->GetBuffer().size_bytes()); + size_t baseAddress = (size_t)executableMemory; + // fix relocs + for(auto& relocIt : x64GenContext.relocateOffsetTable2) + { + // search for segment that starts with this offset + uint32 ppcOffset = (uint32)(size_t)relocIt.extraInfo; + uint32 x64Offset = 0xFFFFFFFF; + + IMLSegment* destSegment = (IMLSegment*)relocIt.extraInfo; + x64Offset = destSegment->x64Offset; + + uint32 relocBase = relocIt.offset; + uint8* relocInstruction = x64GenContext.emitter->GetBufferPtr()+relocBase; + if( relocInstruction[0] == 0x0F && (relocInstruction[1] >= 0x80 && relocInstruction[1] <= 0x8F) ) + { + // Jcc relativeImm32 + sint32 distanceNearJump = (sint32)((baseAddress + x64Offset) - (baseAddress + relocBase + 2)); + if (distanceNearJump >= -128 && distanceNearJump < 127) // disabled + { + // convert to near Jcc + *(uint8*)(relocInstruction + 0) = (uint8)(relocInstruction[1]-0x80 + 0x70); + // patch offset + *(uint8*)(relocInstruction + 1) = (uint8)distanceNearJump; + // replace unused 4 bytes with NOP instruction + relocInstruction[2] = 0x0F; + relocInstruction[3] = 0x1F; + relocInstruction[4] = 0x40; + relocInstruction[5] = 0x00; + } + else + { + // patch offset + *(uint32*)(relocInstruction + 2) = (uint32)((baseAddress + x64Offset) - (baseAddress + relocBase + 6)); + } + } + else if( relocInstruction[0] == 0xE9 ) + { + // JMP relativeImm32 + *(uint32*)(relocInstruction+1) = (uint32)((baseAddress+x64Offset)-(baseAddress+relocBase+5)); + } + else + assert_dbg(); + } + + // copy code to executable memory + std::span<uint8> codeBuffer = x64GenContext.emitter->GetBuffer(); + memcpy(executableMemory, codeBuffer.data(), codeBuffer.size_bytes()); + // set code + PPCRecFunction->x86Code = executableMemory; + PPCRecFunction->x86Size = codeBuffer.size_bytes(); + return true; +} + +void PPCRecompilerX64Gen_generateEnterRecompilerCode() +{ + x64GenContext_t x64GenContext{}; + + // start of recompiler entry function (15 regs) + x64Gen_push_reg64(&x64GenContext, X86_REG_RAX); + x64Gen_push_reg64(&x64GenContext, X86_REG_RCX); + x64Gen_push_reg64(&x64GenContext, X86_REG_RDX); + x64Gen_push_reg64(&x64GenContext, X86_REG_RBX); + x64Gen_push_reg64(&x64GenContext, X86_REG_RBP); + x64Gen_push_reg64(&x64GenContext, X86_REG_RDI); + x64Gen_push_reg64(&x64GenContext, X86_REG_RSI); + x64Gen_push_reg64(&x64GenContext, X86_REG_R8); + x64Gen_push_reg64(&x64GenContext, X86_REG_R9); + x64Gen_push_reg64(&x64GenContext, X86_REG_R10); + x64Gen_push_reg64(&x64GenContext, X86_REG_R11); + x64Gen_push_reg64(&x64GenContext, X86_REG_R12); + x64Gen_push_reg64(&x64GenContext, X86_REG_R13); + x64Gen_push_reg64(&x64GenContext, X86_REG_R14); + x64Gen_push_reg64(&x64GenContext, X86_REG_R15); + + // 000000007775EF04 | E8 00 00 00 00 call +0x00 + x64Gen_writeU8(&x64GenContext, 0xE8); + x64Gen_writeU8(&x64GenContext, 0x00); + x64Gen_writeU8(&x64GenContext, 0x00); + x64Gen_writeU8(&x64GenContext, 0x00); + x64Gen_writeU8(&x64GenContext, 0x00); + //000000007775EF09 | 48 83 04 24 05 add qword ptr ss:[rsp],5 + x64Gen_writeU8(&x64GenContext, 0x48); + x64Gen_writeU8(&x64GenContext, 0x83); + x64Gen_writeU8(&x64GenContext, 0x04); + x64Gen_writeU8(&x64GenContext, 0x24); + uint32 jmpPatchOffset = x64GenContext.emitter->GetWriteIndex(); + x64Gen_writeU8(&x64GenContext, 0); // skip the distance until after the JMP + x64Emit_mov_mem64_reg64(&x64GenContext, X86_REG_RDX, offsetof(PPCInterpreter_t, rspTemp), X86_REG_RSP); + + // MOV RSP, RDX (ppc interpreter instance) + x64Gen_mov_reg64_reg64(&x64GenContext, REG_RESV_HCPU, X86_REG_RDX); + // MOV R15, ppcRecompilerInstanceData + x64Gen_mov_reg64_imm64(&x64GenContext, REG_RESV_RECDATA, (uint64)ppcRecompilerInstanceData); + // MOV R13, memory_base + x64Gen_mov_reg64_imm64(&x64GenContext, REG_RESV_MEMBASE, (uint64)memory_base); + + //JMP recFunc + x64Gen_jmp_reg64(&x64GenContext, X86_REG_RCX); // call argument 1 + + x64GenContext.emitter->GetBuffer()[jmpPatchOffset] = (x64GenContext.emitter->GetWriteIndex() -(jmpPatchOffset-4)); + + //recompilerExit1: + x64Gen_pop_reg64(&x64GenContext, X86_REG_R15); + x64Gen_pop_reg64(&x64GenContext, X86_REG_R14); + x64Gen_pop_reg64(&x64GenContext, X86_REG_R13); + x64Gen_pop_reg64(&x64GenContext, X86_REG_R12); + x64Gen_pop_reg64(&x64GenContext, X86_REG_R11); + x64Gen_pop_reg64(&x64GenContext, X86_REG_R10); + x64Gen_pop_reg64(&x64GenContext, X86_REG_R9); + x64Gen_pop_reg64(&x64GenContext, X86_REG_R8); + x64Gen_pop_reg64(&x64GenContext, X86_REG_RSI); + x64Gen_pop_reg64(&x64GenContext, X86_REG_RDI); + x64Gen_pop_reg64(&x64GenContext, X86_REG_RBP); + x64Gen_pop_reg64(&x64GenContext, X86_REG_RBX); + x64Gen_pop_reg64(&x64GenContext, X86_REG_RDX); + x64Gen_pop_reg64(&x64GenContext, X86_REG_RCX); + x64Gen_pop_reg64(&x64GenContext, X86_REG_RAX); + // RET + x64Gen_ret(&x64GenContext); + + uint8* executableMemory = PPCRecompilerX86_allocateExecutableMemory(x64GenContext.emitter->GetBuffer().size_bytes()); + // copy code to executable memory + memcpy(executableMemory, x64GenContext.emitter->GetBuffer().data(), x64GenContext.emitter->GetBuffer().size_bytes()); + PPCRecompiler_enterRecompilerCode = (void ATTR_MS_ABI (*)(uint64,uint64))executableMemory; +} + + +void* PPCRecompilerX64Gen_generateLeaveRecompilerCode() +{ + x64GenContext_t x64GenContext{}; + + // update instruction pointer + // LR is in EDX + x64Emit_mov_mem32_reg32(&x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, instructionPointer), X86_REG_EDX); + // MOV RSP, [hCPU->rspTemp] + x64Emit_mov_reg64_mem64(&x64GenContext, X86_REG_RSP, REG_RESV_HCPU, offsetof(PPCInterpreter_t, rspTemp)); + // RET + x64Gen_ret(&x64GenContext); + + uint8* executableMemory = PPCRecompilerX86_allocateExecutableMemory(x64GenContext.emitter->GetBuffer().size_bytes()); + // copy code to executable memory + memcpy(executableMemory, x64GenContext.emitter->GetBuffer().data(), x64GenContext.emitter->GetBuffer().size_bytes()); + return executableMemory; +} + +void PPCRecompilerX64Gen_generateRecompilerInterfaceFunctions() +{ + PPCRecompilerX64Gen_generateEnterRecompilerCode(); + PPCRecompiler_leaveRecompilerCode_unvisited = (void ATTR_MS_ABI (*)())PPCRecompilerX64Gen_generateLeaveRecompilerCode(); + PPCRecompiler_leaveRecompilerCode_visited = (void ATTR_MS_ABI (*)())PPCRecompilerX64Gen_generateLeaveRecompilerCode(); + cemu_assert_debug(PPCRecompiler_leaveRecompilerCode_unvisited != PPCRecompiler_leaveRecompilerCode_visited); +} + diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64.h b/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64.h similarity index 81% rename from src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64.h rename to src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64.h index 1d37a77e..e4d1f5a9 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64.h +++ b/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64.h @@ -1,104 +1,56 @@ -typedef struct +#include "../PPCRecompiler.h" // todo - get rid of dependency + +#include "x86Emitter.h" + +struct x64RelocEntry_t { + x64RelocEntry_t(uint32 offset, void* extraInfo) : offset(offset), extraInfo(extraInfo) {}; + uint32 offset; - uint8 type; void* extraInfo; -}x64RelocEntry_t; +}; -typedef struct +struct x64GenContext_t { - uint8* codeBuffer; - sint32 codeBufferIndex; - sint32 codeBufferSize; - // cr state - sint32 activeCRRegister; // current x86 condition flags reflect this cr* register - sint32 activeCRState; // describes the way in which x86 flags map to the cr register (signed / unsigned) + IMLSegment* currentSegment{}; + x86Assembler64* emitter; + sint32 m_currentInstructionEmitIndex; + + x64GenContext_t() + { + emitter = new x86Assembler64(); + } + + ~x64GenContext_t() + { + delete emitter; + } + + IMLInstruction* GetNextInstruction(sint32 relativeIndex = 1) + { + sint32 index = m_currentInstructionEmitIndex + relativeIndex; + if(index < 0 || index >= (sint32)currentSegment->imlList.size()) + return nullptr; + return currentSegment->imlList.data() + index; + } + // relocate offsets - x64RelocEntry_t* relocateOffsetTable; - sint32 relocateOffsetTableSize; - sint32 relocateOffsetTableCount; -}x64GenContext_t; - -// Some of these are defined by winnt.h and gnu headers -#undef REG_EAX -#undef REG_ECX -#undef REG_EDX -#undef REG_EBX -#undef REG_ESP -#undef REG_EBP -#undef REG_ESI -#undef REG_EDI -#undef REG_NONE -#undef REG_RAX -#undef REG_RCX -#undef REG_RDX -#undef REG_RBX -#undef REG_RSP -#undef REG_RBP -#undef REG_RSI -#undef REG_RDI -#undef REG_R8 -#undef REG_R9 -#undef REG_R10 -#undef REG_R11 -#undef REG_R12 -#undef REG_R13 -#undef REG_R14 -#undef REG_R15 - -#define REG_EAX 0 -#define REG_ECX 1 -#define REG_EDX 2 -#define REG_EBX 3 -#define REG_ESP 4 // reserved for low half of hCPU pointer -#define REG_EBP 5 -#define REG_ESI 6 -#define REG_EDI 7 -#define REG_NONE -1 - -#define REG_RAX 0 -#define REG_RCX 1 -#define REG_RDX 2 -#define REG_RBX 3 -#define REG_RSP 4 // reserved for hCPU pointer -#define REG_RBP 5 -#define REG_RSI 6 -#define REG_RDI 7 -#define REG_R8 8 -#define REG_R9 9 -#define REG_R10 10 -#define REG_R11 11 -#define REG_R12 12 -#define REG_R13 13 // reserved to hold pointer to memory base? (Not decided yet) -#define REG_R14 14 // reserved as temporary register -#define REG_R15 15 // reserved for pointer to ppcRecompilerInstanceData - -#define REG_AL 0 -#define REG_CL 1 -#define REG_DL 2 -#define REG_BL 3 -#define REG_AH 4 -#define REG_CH 5 -#define REG_DH 6 -#define REG_BH 7 + std::vector<x64RelocEntry_t> relocateOffsetTable2; +}; // reserved registers -#define REG_RESV_TEMP (REG_R14) -#define REG_RESV_HCPU (REG_RSP) -#define REG_RESV_MEMBASE (REG_R13) -#define REG_RESV_RECDATA (REG_R15) +#define REG_RESV_TEMP (X86_REG_R14) +#define REG_RESV_HCPU (X86_REG_RSP) +#define REG_RESV_MEMBASE (X86_REG_R13) +#define REG_RESV_RECDATA (X86_REG_R15) // reserved floating-point registers #define REG_RESV_FPR_TEMP (15) +#define reg32ToReg16(__x) (__x) // deprecated -extern sint32 x64Gen_registerMap[12]; - -#define tempToRealRegister(__x) (x64Gen_registerMap[__x]) -#define tempToRealFPRRegister(__x) (__x) -#define reg32ToReg16(__x) (__x) - +// deprecated condition flags enum { X86_CONDITION_EQUAL, // or zero @@ -119,36 +71,23 @@ enum X86_CONDITION_NONE, // no condition, jump always }; -#define PPCREC_CR_TEMPORARY (8) // never stored -#define PPCREC_CR_STATE_TYPE_UNSIGNED_ARITHMETIC (0) // for signed arithmetic operations (ADD, CMPI) -#define PPCREC_CR_STATE_TYPE_SIGNED_ARITHMETIC (1) // for unsigned arithmetic operations (ADD, CMPI) -#define PPCREC_CR_STATE_TYPE_LOGICAL (2) // for unsigned operations (CMPLI) - -#define X86_RELOC_MAKE_RELATIVE (0) // make code imm relative to instruction -#define X64_RELOC_LINK_TO_PPC (1) // translate from ppc address to x86 offset -#define X64_RELOC_LINK_TO_SEGMENT (2) // link to beginning of segment - -#define PPC_X64_GPR_USABLE_REGISTERS (16-4) -#define PPC_X64_FPR_USABLE_REGISTERS (16-1) // Use XMM0 - XMM14, XMM15 is the temp register - - -bool PPCRecompiler_generateX64Code(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext); - -void PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext); +bool PPCRecompiler_generateX64Code(struct PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext); void PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext_t* x64GenContext, sint32 jumpInstructionOffset, sint32 destinationOffset); void PPCRecompilerX64Gen_generateRecompilerInterfaceFunctions(); -void PPCRecompilerX64Gen_imlInstruction_fpr_r_name(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction); -void PPCRecompilerX64Gen_imlInstruction_fpr_name_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction); -bool PPCRecompilerX64Gen_imlInstruction_fpr_load(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction, bool indexed); -bool PPCRecompilerX64Gen_imlInstruction_fpr_store(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction, bool indexed); +void PPCRecompilerX64Gen_imlInstruction_fpr_r_name(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction); +void PPCRecompilerX64Gen_imlInstruction_fpr_name_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction); +bool PPCRecompilerX64Gen_imlInstruction_fpr_load(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction, bool indexed); +bool PPCRecompilerX64Gen_imlInstruction_fpr_store(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction, bool indexed); -void PPCRecompilerX64Gen_imlInstruction_fpr_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction); -void PPCRecompilerX64Gen_imlInstruction_fpr_r_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction); -void PPCRecompilerX64Gen_imlInstruction_fpr_r_r_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction); -void PPCRecompilerX64Gen_imlInstruction_fpr_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction); +void PPCRecompilerX64Gen_imlInstruction_fpr_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction); +void PPCRecompilerX64Gen_imlInstruction_fpr_r_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction); +void PPCRecompilerX64Gen_imlInstruction_fpr_r_r_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction); +void PPCRecompilerX64Gen_imlInstruction_fpr_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction); + +void PPCRecompilerX64Gen_imlInstruction_fpr_compare(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction); // ASM gen void x64Gen_writeU8(x64GenContext_t* x64GenContext, uint8 v); @@ -196,9 +135,6 @@ void x64Gen_or_reg64Low8_mem8Reg64(x64GenContext_t* x64GenContext, sint32 dstReg void x64Gen_and_reg64Low8_mem8Reg64(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegister64, sint32 memImmS32); void x64Gen_mov_mem8Reg64_reg64Low8(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegister64, sint32 memImmS32); -void x64Gen_lock_cmpxchg_mem32Reg64PlusReg64_reg64(x64GenContext_t* x64GenContext, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32, sint32 srcRegister); -void x64Gen_lock_cmpxchg_mem32Reg64_reg64(x64GenContext_t* x64GenContext, sint32 memRegister64, sint32 memImmS32, sint32 srcRegister); - void x64Gen_add_reg64_reg64(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); void x64Gen_add_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); void x64Gen_add_reg64_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32); @@ -207,9 +143,6 @@ void x64Gen_sub_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 des void x64Gen_sub_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32); void x64Gen_sub_reg64_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32); void x64Gen_sub_mem32reg64_imm32(x64GenContext_t* x64GenContext, sint32 memRegister, sint32 memImmS32, uint64 immU32); -void x64Gen_sbb_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); -void x64Gen_adc_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); -void x64Gen_adc_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32); void x64Gen_dec_mem32(x64GenContext_t* x64GenContext, sint32 memoryRegister, uint32 memoryImmU32); void x64Gen_imul_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 operandRegister); void x64Gen_idiv_reg64Low32(x64GenContext_t* x64GenContext, sint32 operandRegister); @@ -241,9 +174,7 @@ void x64Gen_not_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister); void x64Gen_neg_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister); void x64Gen_cdq(x64GenContext_t* x64GenContext); -void x64Gen_bswap_reg64(x64GenContext_t* x64GenContext, sint32 destRegister); void x64Gen_bswap_reg64Lower32bit(x64GenContext_t* x64GenContext, sint32 destRegister); -void x64Gen_bswap_reg64Lower16bit(x64GenContext_t* x64GenContext, sint32 destRegister); void x64Gen_lzcnt_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); void x64Gen_bsr_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister); @@ -329,4 +260,8 @@ void x64Gen_movBEZeroExtend_reg64Low16_mem16Reg64PlusReg64(x64GenContext_t* x64G void x64Gen_movBETruncate_mem32Reg64PlusReg64_reg64(x64GenContext_t* x64GenContext, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32, sint32 srcRegister); void x64Gen_shrx_reg64_reg64_reg64(x64GenContext_t* x64GenContext, sint32 registerDst, sint32 registerA, sint32 registerB); -void x64Gen_shlx_reg64_reg64_reg64(x64GenContext_t* x64GenContext, sint32 registerDst, sint32 registerA, sint32 registerB); \ No newline at end of file +void x64Gen_shrx_reg32_reg32_reg32(x64GenContext_t* x64GenContext, sint32 registerDst, sint32 registerA, sint32 registerB); +void x64Gen_sarx_reg64_reg64_reg64(x64GenContext_t* x64GenContext, sint32 registerDst, sint32 registerA, sint32 registerB); +void x64Gen_sarx_reg32_reg32_reg32(x64GenContext_t* x64GenContext, sint32 registerDst, sint32 registerA, sint32 registerB); +void x64Gen_shlx_reg64_reg64_reg64(x64GenContext_t* x64GenContext, sint32 registerDst, sint32 registerA, sint32 registerB); +void x64Gen_shlx_reg32_reg32_reg32(x64GenContext_t* x64GenContext, sint32 registerDst, sint32 registerA, sint32 registerB); \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64AVX.cpp b/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64AVX.cpp similarity index 92% rename from src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64AVX.cpp rename to src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64AVX.cpp index 619c3985..b0ef8640 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64AVX.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64AVX.cpp @@ -1,5 +1,4 @@ -#include "PPCRecompiler.h" -#include "PPCRecompilerX64.h" +#include "BackendX64.h" void _x64Gen_writeMODRMDeprecated(x64GenContext_t* x64GenContext, sint32 dataRegister, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32); @@ -21,11 +20,10 @@ void _x64Gen_vex128_nds(x64GenContext_t* x64GenContext, uint8 opcodeMap, uint8 a x64Gen_writeU8(x64GenContext, opcode); } -#define VEX_PP_0F 0 // guessed +#define VEX_PP_0F 0 #define VEX_PP_66_0F 1 -#define VEX_PP_F3_0F 2 // guessed -#define VEX_PP_F2_0F 3 // guessed - +#define VEX_PP_F3_0F 2 +#define VEX_PP_F2_0F 3 void x64Gen_avx_VPUNPCKHQDQ_xmm_xmm_xmm(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 srcRegisterA, sint32 srcRegisterB) { diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64BMI.cpp b/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64BMI.cpp similarity index 67% rename from src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64BMI.cpp rename to src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64BMI.cpp index 5a71e93d..bbb707e0 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64BMI.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64BMI.cpp @@ -1,5 +1,4 @@ -#include "PPCRecompiler.h" -#include "PPCRecompilerX64.h" +#include "BackendX64.h" void _x64Gen_writeMODRMDeprecated(x64GenContext_t* x64GenContext, sint32 dataRegister, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32); @@ -69,6 +68,34 @@ void x64Gen_shrx_reg64_reg64_reg64(x64GenContext_t* x64GenContext, sint32 regist x64Gen_writeU8(x64GenContext, 0xC0 + (registerDst & 7) * 8 + (registerA & 7)); } +void x64Gen_shrx_reg32_reg32_reg32(x64GenContext_t* x64GenContext, sint32 registerDst, sint32 registerA, sint32 registerB) +{ + x64Gen_writeU8(x64GenContext, 0xC4); + x64Gen_writeU8(x64GenContext, 0xE2 - ((registerDst >= 8) ? 0x80 : 0) - ((registerA >= 8) ? 0x20 : 0)); + x64Gen_writeU8(x64GenContext, 0x7B - registerB * 8); + x64Gen_writeU8(x64GenContext, 0xF7); + x64Gen_writeU8(x64GenContext, 0xC0 + (registerDst & 7) * 8 + (registerA & 7)); +} + +void x64Gen_sarx_reg64_reg64_reg64(x64GenContext_t* x64GenContext, sint32 registerDst, sint32 registerA, sint32 registerB) +{ + // SARX reg64, reg64, reg64 + x64Gen_writeU8(x64GenContext, 0xC4); + x64Gen_writeU8(x64GenContext, 0xE2 - ((registerDst >= 8) ? 0x80 : 0) - ((registerA >= 8) ? 0x20 : 0)); + x64Gen_writeU8(x64GenContext, 0xFA - registerB * 8); + x64Gen_writeU8(x64GenContext, 0xF7); + x64Gen_writeU8(x64GenContext, 0xC0 + (registerDst & 7) * 8 + (registerA & 7)); +} + +void x64Gen_sarx_reg32_reg32_reg32(x64GenContext_t* x64GenContext, sint32 registerDst, sint32 registerA, sint32 registerB) +{ + x64Gen_writeU8(x64GenContext, 0xC4); + x64Gen_writeU8(x64GenContext, 0xE2 - ((registerDst >= 8) ? 0x80 : 0) - ((registerA >= 8) ? 0x20 : 0)); + x64Gen_writeU8(x64GenContext, 0x7A - registerB * 8); + x64Gen_writeU8(x64GenContext, 0xF7); + x64Gen_writeU8(x64GenContext, 0xC0 + (registerDst & 7) * 8 + (registerA & 7)); +} + void x64Gen_shlx_reg64_reg64_reg64(x64GenContext_t* x64GenContext, sint32 registerDst, sint32 registerA, sint32 registerB) { // SHLX reg64, reg64, reg64 @@ -77,4 +104,13 @@ void x64Gen_shlx_reg64_reg64_reg64(x64GenContext_t* x64GenContext, sint32 regist x64Gen_writeU8(x64GenContext, 0xF9 - registerB * 8); x64Gen_writeU8(x64GenContext, 0xF7); x64Gen_writeU8(x64GenContext, 0xC0 + (registerDst & 7) * 8 + (registerA & 7)); +} + +void x64Gen_shlx_reg32_reg32_reg32(x64GenContext_t* x64GenContext, sint32 registerDst, sint32 registerA, sint32 registerB) +{ + x64Gen_writeU8(x64GenContext, 0xC4); + x64Gen_writeU8(x64GenContext, 0xE2 - ((registerDst >= 8) ? 0x80 : 0) - ((registerA >= 8) ? 0x20 : 0)); + x64Gen_writeU8(x64GenContext, 0x79 - registerB * 8); + x64Gen_writeU8(x64GenContext, 0xF7); + x64Gen_writeU8(x64GenContext, 0xC0 + (registerDst & 7) * 8 + (registerA & 7)); } \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64FPU.cpp b/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64FPU.cpp similarity index 51% rename from src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64FPU.cpp rename to src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64FPU.cpp index d83f67de..4d9a538d 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64FPU.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64FPU.cpp @@ -1,47 +1,43 @@ -#include "PPCRecompiler.h" -#include "PPCRecompilerIml.h" -#include "PPCRecompilerX64.h" -#include "asm/x64util.h" +#include "../PPCRecompiler.h" +#include "../IML/IML.h" +#include "BackendX64.h" #include "Common/cpu_features.h" -void PPCRecompilerX64Gen_imlInstruction_fpr_r_name(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) +#include "asm/x64util.h" // for recompiler_fres / frsqrte + +uint32 _regF64(IMLReg physReg); + +uint32 _regI32(IMLReg r) { - uint32 name = imlInstruction->op_r_name.name; - if( name >= PPCREC_NAME_FPR0 && name < (PPCREC_NAME_FPR0+32) ) - { - x64Gen_movupd_xmmReg_memReg128(x64GenContext, tempToRealFPRRegister(imlInstruction->op_r_name.registerIndex), REG_ESP, offsetof(PPCInterpreter_t, fpr)+sizeof(FPR_t)*(name-PPCREC_NAME_FPR0)); - } - else if( name >= PPCREC_NAME_TEMPORARY_FPR0 || name < (PPCREC_NAME_TEMPORARY_FPR0+8) ) - { - x64Gen_movupd_xmmReg_memReg128(x64GenContext, tempToRealFPRRegister(imlInstruction->op_r_name.registerIndex), REG_ESP, offsetof(PPCInterpreter_t, temporaryFPR)+sizeof(FPR_t)*(name-PPCREC_NAME_TEMPORARY_FPR0)); - } - else - { - cemu_assert_debug(false); - } + cemu_assert_debug(r.GetRegFormat() == IMLRegFormat::I32); + return (uint32)r.GetRegID(); } -void PPCRecompilerX64Gen_imlInstruction_fpr_name_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) +static x86Assembler64::GPR32 _reg32(sint8 physRegId) { - uint32 name = imlInstruction->op_r_name.name; - if( name >= PPCREC_NAME_FPR0 && name < (PPCREC_NAME_FPR0+32) ) - { - x64Gen_movupd_memReg128_xmmReg(x64GenContext, tempToRealFPRRegister(imlInstruction->op_r_name.registerIndex), REG_ESP, offsetof(PPCInterpreter_t, fpr)+sizeof(FPR_t)*(name-PPCREC_NAME_FPR0)); - } - else if( name >= PPCREC_NAME_TEMPORARY_FPR0 && name < (PPCREC_NAME_TEMPORARY_FPR0+8) ) - { - x64Gen_movupd_memReg128_xmmReg(x64GenContext, tempToRealFPRRegister(imlInstruction->op_r_name.registerIndex), REG_ESP, offsetof(PPCInterpreter_t, temporaryFPR)+sizeof(FPR_t)*(name-PPCREC_NAME_TEMPORARY_FPR0)); - } - else - { - cemu_assert_debug(false); - } + return (x86Assembler64::GPR32)physRegId; } -void PPCRecompilerX64Gen_imlInstr_gqr_generateScaleCode(ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, sint32 registerXMM, bool isLoad, bool scalePS1, sint32 registerGQR) +static x86Assembler64::GPR8_REX _reg8(IMLReg r) +{ + cemu_assert_debug(r.GetRegFormat() == IMLRegFormat::I32); // currently bool regs are implemented as 32bit registers + return (x86Assembler64::GPR8_REX)r.GetRegID(); +} + +static x86Assembler64::GPR32 _reg32_from_reg8(x86Assembler64::GPR8_REX regId) +{ + return (x86Assembler64::GPR32)regId; +} + +static x86Assembler64::GPR8_REX _reg8_from_reg32(x86Assembler64::GPR32 regId) +{ + return (x86Assembler64::GPR8_REX)regId; +} + +void PPCRecompilerX64Gen_imlInstr_gqr_generateScaleCode(ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, sint32 registerXMM, bool isLoad, bool scalePS1, IMLReg registerGQR) { // load GQR - x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, registerGQR); + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, _regI32(registerGQR)); // extract scale field and multiply by 16 to get array offset x64Gen_shr_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, (isLoad?16:0)+8-4); x64Gen_and_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, (0x3F<<4)); @@ -65,7 +61,7 @@ void PPCRecompilerX64Gen_imlInstr_gqr_generateScaleCode(ppcImlGenContext_t* ppcI // generate code for PSQ load for a particular type // if scaleGQR is -1 then a scale of 1.0 is assumed (no scale) -void PPCRecompilerX64Gen_imlInstr_psq_load(ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, uint8 mode, sint32 registerXMM, sint32 memReg, sint32 memRegEx, sint32 memImmS32, bool indexed, sint32 registerGQR = -1) +void PPCRecompilerX64Gen_imlInstr_psq_load(ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, uint8 mode, sint32 registerXMM, sint32 memReg, sint32 memRegEx, sint32 memImmS32, bool indexed, IMLReg registerGQR = IMLREG_INVALID) { if (mode == PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0_PS1) { @@ -74,8 +70,8 @@ void PPCRecompilerX64Gen_imlInstr_psq_load(ppcImlGenContext_t* ppcImlGenContext, assert_dbg(); } // optimized code for ps float load - x64Emit_mov_reg64_mem64(x64GenContext, REG_RESV_TEMP, REG_R13, memReg, memImmS32); - x64Gen_bswap_reg64(x64GenContext, REG_RESV_TEMP); + x64Emit_mov_reg64_mem64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, memReg, memImmS32); + x64GenContext->emitter->BSWAP_q(REG_RESV_TEMP); x64Gen_rol_reg64_imm8(x64GenContext, REG_RESV_TEMP, 32); // swap upper and lower DWORD x64Gen_movq_xmmReg_reg64(x64GenContext, registerXMM, REG_RESV_TEMP); x64Gen_cvtps2pd_xmmReg_xmmReg(x64GenContext, registerXMM, registerXMM); @@ -115,8 +111,8 @@ void PPCRecompilerX64Gen_imlInstr_psq_load(ppcImlGenContext_t* ppcImlGenContext, } else { - x64Emit_mov_mem32_reg64(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR), REG_RESV_TEMP); - x64Gen_movddup_xmmReg_memReg64(x64GenContext, REG_RESV_FPR_TEMP, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)); + x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR), REG_RESV_TEMP); + x64Gen_movddup_xmmReg_memReg64(x64GenContext, REG_RESV_FPR_TEMP, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)); } x64Gen_cvtss2sd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, REG_RESV_FPR_TEMP); // load constant 1.0 into lower half and upper half of temp register @@ -178,7 +174,7 @@ void PPCRecompilerX64Gen_imlInstr_psq_load(ppcImlGenContext_t* ppcImlGenContext, if (readSize == 16) { // half word - x64Gen_movZeroExtend_reg64Low16_mem16Reg64PlusReg64(x64GenContext, REG_RESV_TEMP, REG_R13, memReg, memOffset); + x64Gen_movZeroExtend_reg64Low16_mem16Reg64PlusReg64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, memReg, memOffset); x64Gen_rol_reg64Low16_imm8(x64GenContext, REG_RESV_TEMP, 8); // endian swap if (isSigned) x64Gen_movSignExtend_reg64Low32_reg64Low16(x64GenContext, REG_RESV_TEMP, REG_RESV_TEMP); @@ -188,7 +184,7 @@ void PPCRecompilerX64Gen_imlInstr_psq_load(ppcImlGenContext_t* ppcImlGenContext, else if (readSize == 8) { // byte - x64Emit_mov_reg64b_mem8(x64GenContext, REG_RESV_TEMP, REG_R13, memReg, memOffset); + x64Emit_mov_reg64b_mem8(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, memReg, memOffset); if (isSigned) x64Gen_movSignExtend_reg64Low32_reg64Low8(x64GenContext, REG_RESV_TEMP, REG_RESV_TEMP); else @@ -201,31 +197,31 @@ void PPCRecompilerX64Gen_imlInstr_psq_load(ppcImlGenContext_t* ppcImlGenContext, // convert the two integers to doubles x64Gen_cvtpi2pd_xmmReg_mem64Reg64(x64GenContext, registerXMM, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryGPR)); // scale - if (registerGQR >= 0) + if (registerGQR.IsValid()) PPCRecompilerX64Gen_imlInstr_gqr_generateScaleCode(ppcImlGenContext, x64GenContext, registerXMM, true, loadPS1, registerGQR); } } -void PPCRecompilerX64Gen_imlInstr_psq_load_generic(ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, uint8 mode, sint32 registerXMM, sint32 memReg, sint32 memRegEx, sint32 memImmS32, bool indexed, sint32 registerGQR) +void PPCRecompilerX64Gen_imlInstr_psq_load_generic(ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, uint8 mode, sint32 registerXMM, sint32 memReg, sint32 memRegEx, sint32 memImmS32, bool indexed, IMLReg registerGQR) { bool loadPS1 = (mode == PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1); // load GQR - x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, registerGQR); + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, _regI32(registerGQR)); // extract load type field x64Gen_shr_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, 16); x64Gen_and_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 7); // jump cases x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 4); // type 4 -> u8 - sint32 jumpOffset_caseU8 = x64GenContext->codeBufferIndex; + sint32 jumpOffset_caseU8 = x64GenContext->emitter->GetWriteIndex(); x64Gen_jmpc_far(x64GenContext, X86_CONDITION_EQUAL, 0); x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 5); // type 5 -> u16 - sint32 jumpOffset_caseU16 = x64GenContext->codeBufferIndex; + sint32 jumpOffset_caseU16 = x64GenContext->emitter->GetWriteIndex(); x64Gen_jmpc_far(x64GenContext, X86_CONDITION_EQUAL, 0); x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 6); // type 4 -> s8 - sint32 jumpOffset_caseS8 = x64GenContext->codeBufferIndex; + sint32 jumpOffset_caseS8 = x64GenContext->emitter->GetWriteIndex(); x64Gen_jmpc_far(x64GenContext, X86_CONDITION_EQUAL, 0); x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 7); // type 5 -> s16 - sint32 jumpOffset_caseS16 = x64GenContext->codeBufferIndex; + sint32 jumpOffset_caseS16 = x64GenContext->emitter->GetWriteIndex(); x64Gen_jmpc_far(x64GenContext, X86_CONDITION_EQUAL, 0); // default case -> float @@ -236,42 +232,41 @@ void PPCRecompilerX64Gen_imlInstr_psq_load_generic(ppcImlGenContext_t* ppcImlGen uint32 jumpOffset_endOfS8; PPCRecompilerX64Gen_imlInstr_psq_load(ppcImlGenContext, x64GenContext, loadPS1 ? PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0_PS1 : PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0, registerXMM, memReg, memRegEx, memImmS32, indexed, registerGQR); - jumpOffset_endOfFloat = x64GenContext->codeBufferIndex; + jumpOffset_endOfFloat = x64GenContext->emitter->GetWriteIndex(); x64Gen_jmp_imm32(x64GenContext, 0); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_caseU16, x64GenContext->codeBufferIndex); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_caseU16, x64GenContext->emitter->GetWriteIndex()); PPCRecompilerX64Gen_imlInstr_psq_load(ppcImlGenContext, x64GenContext, loadPS1 ? PPCREC_FPR_LD_MODE_PSQ_U16_PS0_PS1 : PPCREC_FPR_LD_MODE_PSQ_U16_PS0, registerXMM, memReg, memRegEx, memImmS32, indexed, registerGQR); - jumpOffset_endOfU8 = x64GenContext->codeBufferIndex; + jumpOffset_endOfU8 = x64GenContext->emitter->GetWriteIndex(); x64Gen_jmp_imm32(x64GenContext, 0); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_caseS16, x64GenContext->codeBufferIndex); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_caseS16, x64GenContext->emitter->GetWriteIndex()); PPCRecompilerX64Gen_imlInstr_psq_load(ppcImlGenContext, x64GenContext, loadPS1 ? PPCREC_FPR_LD_MODE_PSQ_S16_PS0_PS1 : PPCREC_FPR_LD_MODE_PSQ_S16_PS0, registerXMM, memReg, memRegEx, memImmS32, indexed, registerGQR); - jumpOffset_endOfU16 = x64GenContext->codeBufferIndex; + jumpOffset_endOfU16 = x64GenContext->emitter->GetWriteIndex(); x64Gen_jmp_imm32(x64GenContext, 0); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_caseU8, x64GenContext->codeBufferIndex); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_caseU8, x64GenContext->emitter->GetWriteIndex()); PPCRecompilerX64Gen_imlInstr_psq_load(ppcImlGenContext, x64GenContext, loadPS1 ? PPCREC_FPR_LD_MODE_PSQ_U8_PS0_PS1 : PPCREC_FPR_LD_MODE_PSQ_U8_PS0, registerXMM, memReg, memRegEx, memImmS32, indexed, registerGQR); - jumpOffset_endOfS8 = x64GenContext->codeBufferIndex; + jumpOffset_endOfS8 = x64GenContext->emitter->GetWriteIndex(); x64Gen_jmp_imm32(x64GenContext, 0); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_caseS8, x64GenContext->codeBufferIndex); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_caseS8, x64GenContext->emitter->GetWriteIndex()); PPCRecompilerX64Gen_imlInstr_psq_load(ppcImlGenContext, x64GenContext, loadPS1 ? PPCREC_FPR_LD_MODE_PSQ_S8_PS0_PS1 : PPCREC_FPR_LD_MODE_PSQ_S8_PS0, registerXMM, memReg, memRegEx, memImmS32, indexed, registerGQR); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_endOfFloat, x64GenContext->codeBufferIndex); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_endOfU8, x64GenContext->codeBufferIndex); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_endOfU16, x64GenContext->codeBufferIndex); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_endOfS8, x64GenContext->codeBufferIndex); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_endOfFloat, x64GenContext->emitter->GetWriteIndex()); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_endOfU8, x64GenContext->emitter->GetWriteIndex()); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_endOfU16, x64GenContext->emitter->GetWriteIndex()); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_endOfS8, x64GenContext->emitter->GetWriteIndex()); } // load from memory -bool PPCRecompilerX64Gen_imlInstruction_fpr_load(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction, bool indexed) +bool PPCRecompilerX64Gen_imlInstruction_fpr_load(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction, bool indexed) { - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - sint32 realRegisterXMM = tempToRealFPRRegister(imlInstruction->op_storeLoad.registerData); - sint32 realRegisterMem = tempToRealRegister(imlInstruction->op_storeLoad.registerMem); + sint32 realRegisterXMM = _regF64(imlInstruction->op_storeLoad.registerData); + sint32 realRegisterMem = _regI32(imlInstruction->op_storeLoad.registerMem); sint32 realRegisterMem2 = PPC_REC_INVALID_REGISTER; if( indexed ) - realRegisterMem2 = tempToRealRegister(imlInstruction->op_storeLoad.registerMem2); + realRegisterMem2 = _regI32(imlInstruction->op_storeLoad.registerMem2); uint8 mode = imlInstruction->op_storeLoad.mode; if( mode == PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1 ) @@ -281,29 +276,21 @@ bool PPCRecompilerX64Gen_imlInstruction_fpr_load(PPCRecFunction_t* PPCRecFunctio { x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem2); x64Gen_add_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem); - if( g_CPUFeatures.x86.movbe ) + if(g_CPUFeatures.x86.movbe) x64Gen_movBEZeroExtend_reg64_mem32Reg64PlusReg64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32); else x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32); } else { - if( g_CPUFeatures.x86.movbe ) + if(g_CPUFeatures.x86.movbe) x64Gen_movBEZeroExtend_reg64_mem32Reg64PlusReg64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32); else x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32); } - if( g_CPUFeatures.x86.movbe == false ) + if(g_CPUFeatures.x86.movbe == false ) x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); - if( g_CPUFeatures.x86.avx ) - { - x64Gen_movd_xmmReg_reg64Low32(x64GenContext, realRegisterXMM, REG_RESV_TEMP); - } - else - { - x64Emit_mov_mem32_reg64(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR), REG_RESV_TEMP); - x64Gen_movddup_xmmReg_memReg64(x64GenContext, realRegisterXMM, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)); - } + x64Gen_movd_xmmReg_reg64Low32(x64GenContext, realRegisterXMM, REG_RESV_TEMP); if (imlInstruction->op_storeLoad.flags2.notExpanded) { @@ -325,15 +312,15 @@ bool PPCRecompilerX64Gen_imlInstruction_fpr_load(PPCRecFunction_t* PPCRecFunctio x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem); x64Gen_add_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem2); // load value - x64Emit_mov_reg64_mem64(x64GenContext, REG_RESV_TEMP, REG_R13, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32+0); - x64Gen_bswap_reg64(x64GenContext, REG_RESV_TEMP); + x64Emit_mov_reg64_mem64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32+0); + x64GenContext->emitter->BSWAP_q(REG_RESV_TEMP); x64Gen_movq_xmmReg_reg64(x64GenContext, REG_RESV_FPR_TEMP, REG_RESV_TEMP); x64Gen_movsd_xmmReg_xmmReg(x64GenContext, realRegisterXMM, REG_RESV_FPR_TEMP); } else { - x64Emit_mov_reg64_mem64(x64GenContext, REG_RESV_TEMP, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32+0); - x64Gen_bswap_reg64(x64GenContext, REG_RESV_TEMP); + x64Emit_mov_reg64_mem64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32+0); + x64GenContext->emitter->BSWAP_q(REG_RESV_TEMP); x64Gen_movq_xmmReg_reg64(x64GenContext, REG_RESV_FPR_TEMP, REG_RESV_TEMP); x64Gen_movsd_xmmReg_xmmReg(x64GenContext, realRegisterXMM, REG_RESV_FPR_TEMP); } @@ -346,31 +333,31 @@ bool PPCRecompilerX64Gen_imlInstruction_fpr_load(PPCRecFunction_t* PPCRecFunctio x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem); x64Gen_add_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem2); // load double low part to temporaryFPR - x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_R13, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32+0); + x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32+0); x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); - x64Emit_mov_mem32_reg64(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)+4, REG_RESV_TEMP); + x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)+4, REG_RESV_TEMP); // calculate offset again x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem); x64Gen_add_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem2); // load double high part to temporaryFPR - x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_R13, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32+4); + x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32+4); x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); - x64Emit_mov_mem32_reg64(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)+0, REG_RESV_TEMP); + x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)+0, REG_RESV_TEMP); // load double from temporaryFPR - x64Gen_movlpd_xmmReg_memReg64(x64GenContext, realRegisterXMM, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)); + x64Gen_movlpd_xmmReg_memReg64(x64GenContext, realRegisterXMM, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)); } else { // load double low part to temporaryFPR - x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32+0); + x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32+0); x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); - x64Emit_mov_mem32_reg64(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)+4, REG_RESV_TEMP); + x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)+4, REG_RESV_TEMP); // load double high part to temporaryFPR - x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32+4); + x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32+4); x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); - x64Emit_mov_mem32_reg64(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)+0, REG_RESV_TEMP); + x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)+0, REG_RESV_TEMP); // load double from temporaryFPR - x64Gen_movlpd_xmmReg_memReg64(x64GenContext, realRegisterXMM, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)); + x64Gen_movlpd_xmmReg_memReg64(x64GenContext, realRegisterXMM, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)); } } } @@ -391,7 +378,7 @@ bool PPCRecompilerX64Gen_imlInstruction_fpr_load(PPCRecFunction_t* PPCRecFunctio else if (mode == PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1 || mode == PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0) { - PPCRecompilerX64Gen_imlInstr_psq_load_generic(ppcImlGenContext, x64GenContext, mode, realRegisterXMM, realRegisterMem, realRegisterMem2, imlInstruction->op_storeLoad.immS32, indexed, tempToRealRegister(imlInstruction->op_storeLoad.registerGQR)); + PPCRecompilerX64Gen_imlInstr_psq_load_generic(ppcImlGenContext, x64GenContext, mode, realRegisterXMM, realRegisterMem, realRegisterMem2, imlInstruction->op_storeLoad.immS32, indexed, imlInstruction->op_storeLoad.registerGQR); } else { @@ -400,7 +387,7 @@ bool PPCRecompilerX64Gen_imlInstruction_fpr_load(PPCRecFunction_t* PPCRecFunctio return true; } -void PPCRecompilerX64Gen_imlInstr_psq_store(ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, uint8 mode, sint32 registerXMM, sint32 memReg, sint32 memRegEx, sint32 memImmS32, bool indexed, sint32 registerGQR = -1) +void PPCRecompilerX64Gen_imlInstr_psq_store(ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, uint8 mode, sint32 registerXMM, sint32 memReg, sint32 memRegEx, sint32 memImmS32, bool indexed, IMLReg registerGQR = IMLREG_INVALID) { bool storePS1 = (mode == PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0_PS1 || mode == PPCREC_FPR_ST_MODE_PSQ_S8_PS0_PS1 || @@ -408,7 +395,7 @@ void PPCRecompilerX64Gen_imlInstr_psq_store(ppcImlGenContext_t* ppcImlGenContext mode == PPCREC_FPR_ST_MODE_PSQ_U16_PS0_PS1 || mode == PPCREC_FPR_ST_MODE_PSQ_S16_PS0_PS1); bool isFloat = mode == PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0 || mode == PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0_PS1; - if (registerGQR >= 0) + if (registerGQR.IsValid()) { // move to temporary xmm and update registerXMM x64Gen_movaps_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, registerXMM); @@ -420,15 +407,7 @@ void PPCRecompilerX64Gen_imlInstr_psq_store(ppcImlGenContext_t* ppcImlGenContext if (mode == PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0) { x64Gen_cvtsd2ss_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, registerXMM); - if (g_CPUFeatures.x86.avx) - { - x64Gen_movd_reg64Low32_xmmReg(x64GenContext, REG_RESV_TEMP, REG_RESV_FPR_TEMP); - } - else - { - x64Gen_movsd_memReg64_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)); - x64Emit_mov_reg64_mem32(x64GenContext, REG_RESV_TEMP, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)); - } + x64Gen_movd_reg64Low32_xmmReg(x64GenContext, REG_RESV_TEMP, REG_RESV_FPR_TEMP); if (g_CPUFeatures.x86.movbe == false) x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); if (indexed) @@ -437,9 +416,9 @@ void PPCRecompilerX64Gen_imlInstr_psq_store(ppcImlGenContext_t* ppcImlGenContext x64Gen_add_reg64Low32_reg64Low32(x64GenContext, memReg, memRegEx); } if (g_CPUFeatures.x86.movbe) - x64Gen_movBETruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_R13, memReg, memImmS32, REG_RESV_TEMP); + x64Gen_movBETruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, memReg, memImmS32, REG_RESV_TEMP); else - x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_R13, memReg, memImmS32, REG_RESV_TEMP); + x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, memReg, memImmS32, REG_RESV_TEMP); if (indexed) { x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, memReg, memRegEx); @@ -453,8 +432,8 @@ void PPCRecompilerX64Gen_imlInstr_psq_store(ppcImlGenContext_t* ppcImlGenContext x64Gen_cvtpd2ps_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, registerXMM); x64Gen_movq_reg64_xmmReg(x64GenContext, REG_RESV_TEMP, REG_RESV_FPR_TEMP); x64Gen_rol_reg64_imm8(x64GenContext, REG_RESV_TEMP, 32); // swap upper and lower DWORD - x64Gen_bswap_reg64(x64GenContext, REG_RESV_TEMP); - x64Gen_mov_mem64Reg64PlusReg64_reg64(x64GenContext, REG_RESV_TEMP, REG_R13, memReg, memImmS32); + x64GenContext->emitter->BSWAP_q(REG_RESV_TEMP); + x64Gen_mov_mem64Reg64PlusReg64_reg64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, memReg, memImmS32); return; } // store as integer @@ -510,16 +489,16 @@ void PPCRecompilerX64Gen_imlInstr_psq_store(ppcImlGenContext_t* ppcImlGenContext } // max(i, -clampMin) x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, clampMin); - sint32 jumpInstructionOffset1 = x64GenContext->codeBufferIndex; + sint32 jumpInstructionOffset1 = x64GenContext->emitter->GetWriteIndex(); x64Gen_jmpc_near(x64GenContext, X86_CONDITION_SIGNED_GREATER_EQUAL, 0); x64Gen_mov_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, clampMin); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset1, x64GenContext->codeBufferIndex); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset1, x64GenContext->emitter->GetWriteIndex()); // min(i, clampMax) x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, clampMax); - sint32 jumpInstructionOffset2 = x64GenContext->codeBufferIndex; + sint32 jumpInstructionOffset2 = x64GenContext->emitter->GetWriteIndex(); x64Gen_jmpc_near(x64GenContext, X86_CONDITION_SIGNED_LESS_EQUAL, 0); x64Gen_mov_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, clampMax); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset2, x64GenContext->codeBufferIndex); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset2, x64GenContext->emitter->GetWriteIndex()); // endian swap if( bitWriteSize == 16) x64Gen_rol_reg64Low16_imm8(x64GenContext, REG_RESV_TEMP, 8); @@ -534,25 +513,25 @@ void PPCRecompilerX64Gen_imlInstr_psq_store(ppcImlGenContext_t* ppcImlGenContext } } -void PPCRecompilerX64Gen_imlInstr_psq_store_generic(ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, uint8 mode, sint32 registerXMM, sint32 memReg, sint32 memRegEx, sint32 memImmS32, bool indexed, sint32 registerGQR) +void PPCRecompilerX64Gen_imlInstr_psq_store_generic(ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, uint8 mode, sint32 registerXMM, sint32 memReg, sint32 memRegEx, sint32 memImmS32, bool indexed, IMLReg registerGQR) { bool storePS1 = (mode == PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1); // load GQR - x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, registerGQR); + x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, _regI32(registerGQR)); // extract store type field x64Gen_and_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 7); // jump cases x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 4); // type 4 -> u8 - sint32 jumpOffset_caseU8 = x64GenContext->codeBufferIndex; + sint32 jumpOffset_caseU8 = x64GenContext->emitter->GetWriteIndex(); x64Gen_jmpc_far(x64GenContext, X86_CONDITION_EQUAL, 0); x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 5); // type 5 -> u16 - sint32 jumpOffset_caseU16 = x64GenContext->codeBufferIndex; + sint32 jumpOffset_caseU16 = x64GenContext->emitter->GetWriteIndex(); x64Gen_jmpc_far(x64GenContext, X86_CONDITION_EQUAL, 0); x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 6); // type 4 -> s8 - sint32 jumpOffset_caseS8 = x64GenContext->codeBufferIndex; + sint32 jumpOffset_caseS8 = x64GenContext->emitter->GetWriteIndex(); x64Gen_jmpc_far(x64GenContext, X86_CONDITION_EQUAL, 0); x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 7); // type 5 -> s16 - sint32 jumpOffset_caseS16 = x64GenContext->codeBufferIndex; + sint32 jumpOffset_caseS16 = x64GenContext->emitter->GetWriteIndex(); x64Gen_jmpc_far(x64GenContext, X86_CONDITION_EQUAL, 0); // default case -> float @@ -563,72 +542,55 @@ void PPCRecompilerX64Gen_imlInstr_psq_store_generic(ppcImlGenContext_t* ppcImlGe uint32 jumpOffset_endOfS8; PPCRecompilerX64Gen_imlInstr_psq_store(ppcImlGenContext, x64GenContext, storePS1 ? PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0_PS1 : PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0, registerXMM, memReg, memRegEx, memImmS32, indexed, registerGQR); - jumpOffset_endOfFloat = x64GenContext->codeBufferIndex; + jumpOffset_endOfFloat = x64GenContext->emitter->GetWriteIndex(); x64Gen_jmp_imm32(x64GenContext, 0); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_caseU16, x64GenContext->codeBufferIndex); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_caseU16, x64GenContext->emitter->GetWriteIndex()); PPCRecompilerX64Gen_imlInstr_psq_store(ppcImlGenContext, x64GenContext, storePS1 ? PPCREC_FPR_ST_MODE_PSQ_U16_PS0_PS1 : PPCREC_FPR_ST_MODE_PSQ_U16_PS0, registerXMM, memReg, memRegEx, memImmS32, indexed, registerGQR); - jumpOffset_endOfU8 = x64GenContext->codeBufferIndex; + jumpOffset_endOfU8 = x64GenContext->emitter->GetWriteIndex(); x64Gen_jmp_imm32(x64GenContext, 0); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_caseS16, x64GenContext->codeBufferIndex); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_caseS16, x64GenContext->emitter->GetWriteIndex()); PPCRecompilerX64Gen_imlInstr_psq_store(ppcImlGenContext, x64GenContext, storePS1 ? PPCREC_FPR_ST_MODE_PSQ_S16_PS0_PS1 : PPCREC_FPR_ST_MODE_PSQ_S16_PS0, registerXMM, memReg, memRegEx, memImmS32, indexed, registerGQR); - jumpOffset_endOfU16 = x64GenContext->codeBufferIndex; + jumpOffset_endOfU16 = x64GenContext->emitter->GetWriteIndex(); x64Gen_jmp_imm32(x64GenContext, 0); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_caseU8, x64GenContext->codeBufferIndex); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_caseU8, x64GenContext->emitter->GetWriteIndex()); PPCRecompilerX64Gen_imlInstr_psq_store(ppcImlGenContext, x64GenContext, storePS1 ? PPCREC_FPR_ST_MODE_PSQ_U8_PS0_PS1 : PPCREC_FPR_ST_MODE_PSQ_U8_PS0, registerXMM, memReg, memRegEx, memImmS32, indexed, registerGQR); - jumpOffset_endOfS8 = x64GenContext->codeBufferIndex; + jumpOffset_endOfS8 = x64GenContext->emitter->GetWriteIndex(); x64Gen_jmp_imm32(x64GenContext, 0); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_caseS8, x64GenContext->codeBufferIndex); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_caseS8, x64GenContext->emitter->GetWriteIndex()); PPCRecompilerX64Gen_imlInstr_psq_store(ppcImlGenContext, x64GenContext, storePS1 ? PPCREC_FPR_ST_MODE_PSQ_S8_PS0_PS1 : PPCREC_FPR_ST_MODE_PSQ_S8_PS0, registerXMM, memReg, memRegEx, memImmS32, indexed, registerGQR); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_endOfFloat, x64GenContext->codeBufferIndex); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_endOfU8, x64GenContext->codeBufferIndex); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_endOfU16, x64GenContext->codeBufferIndex); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_endOfS8, x64GenContext->codeBufferIndex); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_endOfFloat, x64GenContext->emitter->GetWriteIndex()); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_endOfU8, x64GenContext->emitter->GetWriteIndex()); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_endOfU16, x64GenContext->emitter->GetWriteIndex()); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_endOfS8, x64GenContext->emitter->GetWriteIndex()); } // store to memory -bool PPCRecompilerX64Gen_imlInstruction_fpr_store(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction, bool indexed) +bool PPCRecompilerX64Gen_imlInstruction_fpr_store(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction, bool indexed) { - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - sint32 realRegisterXMM = tempToRealFPRRegister(imlInstruction->op_storeLoad.registerData); - sint32 realRegisterMem = tempToRealRegister(imlInstruction->op_storeLoad.registerMem); + sint32 realRegisterXMM = _regF64(imlInstruction->op_storeLoad.registerData); + sint32 realRegisterMem = _regI32(imlInstruction->op_storeLoad.registerMem); sint32 realRegisterMem2 = PPC_REC_INVALID_REGISTER; if( indexed ) - realRegisterMem2 = tempToRealRegister(imlInstruction->op_storeLoad.registerMem2); + realRegisterMem2 = _regI32(imlInstruction->op_storeLoad.registerMem2); uint8 mode = imlInstruction->op_storeLoad.mode; if( mode == PPCREC_FPR_ST_MODE_SINGLE_FROM_PS0 ) { if (imlInstruction->op_storeLoad.flags2.notExpanded) { // value is already in single format - if (g_CPUFeatures.x86.avx) - { - x64Gen_movd_reg64Low32_xmmReg(x64GenContext, REG_RESV_TEMP, realRegisterXMM); - } - else - { - x64Gen_movsd_memReg64_xmmReg(x64GenContext, realRegisterXMM, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)); - x64Emit_mov_reg64_mem32(x64GenContext, REG_RESV_TEMP, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)); - } + x64Gen_movd_reg64Low32_xmmReg(x64GenContext, REG_RESV_TEMP, realRegisterXMM); } else { x64Gen_cvtsd2ss_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, realRegisterXMM); - if (g_CPUFeatures.x86.avx) - { - x64Gen_movd_reg64Low32_xmmReg(x64GenContext, REG_RESV_TEMP, REG_RESV_FPR_TEMP); - } - else - { - x64Gen_movsd_memReg64_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)); - x64Emit_mov_reg64_mem32(x64GenContext, REG_RESV_TEMP, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)); - } + x64Gen_movd_reg64Low32_xmmReg(x64GenContext, REG_RESV_TEMP, REG_RESV_FPR_TEMP); } - if( g_CPUFeatures.x86.movbe == false ) + if(g_CPUFeatures.x86.movbe == false ) x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); if( indexed ) { @@ -636,10 +598,10 @@ bool PPCRecompilerX64Gen_imlInstruction_fpr_store(PPCRecFunction_t* PPCRecFuncti assert_dbg(); x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); } - if( g_CPUFeatures.x86.movbe ) - x64Gen_movBETruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP); + if(g_CPUFeatures.x86.movbe) + x64Gen_movBETruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP); else - x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP); + x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP); if( indexed ) { x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); @@ -653,15 +615,15 @@ bool PPCRecompilerX64Gen_imlInstruction_fpr_store(PPCRecFunction_t* PPCRecFuncti assert_dbg(); x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); } - x64Gen_movsd_memReg64_xmmReg(x64GenContext, realRegisterXMM, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)); - // store double low part - x64Emit_mov_reg64_mem32(x64GenContext, REG_RESV_TEMP, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)+0); + x64Gen_movsd_memReg64_xmmReg(x64GenContext, realRegisterXMM, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)); + // store double low part + x64Emit_mov_reg64_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)+0); x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); - x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32+4, REG_RESV_TEMP); - // store double high part - x64Emit_mov_reg64_mem32(x64GenContext, REG_RESV_TEMP, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)+4); + x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32+4, REG_RESV_TEMP); + // store double high part + x64Emit_mov_reg64_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)+4); x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); - x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32+0, REG_RESV_TEMP); + x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32+0, REG_RESV_TEMP); if( indexed ) { x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); @@ -669,27 +631,18 @@ bool PPCRecompilerX64Gen_imlInstruction_fpr_store(PPCRecFunction_t* PPCRecFuncti } else if( mode == PPCREC_FPR_ST_MODE_UI32_FROM_PS0 ) { - if( g_CPUFeatures.x86.avx ) - { - x64Gen_movd_reg64Low32_xmmReg(x64GenContext, REG_RESV_TEMP, realRegisterXMM); - } - else - { - x64Gen_movsd_memReg64_xmmReg(x64GenContext, realRegisterXMM, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)); - x64Emit_mov_reg64_mem32(x64GenContext, REG_RESV_TEMP, REG_RSP, offsetof(PPCInterpreter_t, temporaryFPR)); - } + x64Gen_movd_reg64Low32_xmmReg(x64GenContext, REG_RESV_TEMP, realRegisterXMM); x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); if( indexed ) { - if( realRegisterMem == realRegisterMem2 ) - assert_dbg(); + cemu_assert_debug(realRegisterMem == realRegisterMem2); x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); - x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP); + x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP); x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); } else { - x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP); + x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP); } } else if(mode == PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0_PS1 || @@ -709,7 +662,7 @@ bool PPCRecompilerX64Gen_imlInstruction_fpr_store(PPCRecFunction_t* PPCRecFuncti else if (mode == PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1 || mode == PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0) { - PPCRecompilerX64Gen_imlInstr_psq_store_generic(ppcImlGenContext, x64GenContext, mode, realRegisterXMM, realRegisterMem, realRegisterMem2, imlInstruction->op_storeLoad.immS32, indexed, tempToRealRegister(imlInstruction->op_storeLoad.registerGQR)); + PPCRecompilerX64Gen_imlInstr_psq_store_generic(ppcImlGenContext, x64GenContext, mode, realRegisterXMM, realRegisterMem, realRegisterMem2, imlInstruction->op_storeLoad.immS32, indexed, imlInstruction->op_storeLoad.registerGQR); } else { @@ -727,275 +680,156 @@ void _swapPS0PS1(x64GenContext_t* x64GenContext, sint32 xmmReg) } // FPR op FPR -void PPCRecompilerX64Gen_imlInstruction_fpr_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) +void PPCRecompilerX64Gen_imlInstruction_fpr_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) { - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + uint32 regR = _regF64(imlInstruction->op_fpr_r_r.regR); + uint32 regA = _regF64(imlInstruction->op_fpr_r_r.regA); + if( imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP ) { - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - assert_dbg(); - } - x64Gen_movddup_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + x64Gen_movddup_xmmReg_xmmReg(x64GenContext, regR, regA); } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM_AND_TOP ) { - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - assert_dbg(); - } // VPUNPCKHQDQ - if (imlInstruction->op_fpr_r_r.registerResult == imlInstruction->op_fpr_r_r.registerOperand) + if (regR == regA) { // unpack top to bottom and top - x64Gen_unpckhpd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + x64Gen_unpckhpd_xmmReg_xmmReg(x64GenContext, regR, regA); } - //else if ( g_CPUFeatures.x86.avx ) + //else if ( hasAVXSupport ) //{ // // unpack top to bottom and top with non-destructive destination // // update: On Ivy Bridge this causes weird stalls? - // x64Gen_avx_VUNPCKHPD_xmm_xmm_xmm(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand, imlInstruction->op_fpr_r_r.registerOperand); + // x64Gen_avx_VUNPCKHPD_xmm_xmm_xmm(x64GenContext, registerResult, registerOperand, registerOperand); //} else { // move top to bottom - x64Gen_movhlps_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + x64Gen_movhlps_xmmReg_xmmReg(x64GenContext, regR, regA); // duplicate bottom - x64Gen_movddup_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerResult); + x64Gen_movddup_xmmReg_xmmReg(x64GenContext, regR, regR); } } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM ) { - cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); - x64Gen_movsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + x64Gen_movsd_xmmReg_xmmReg(x64GenContext, regR, regA); } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_TOP ) { - cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); - x64Gen_unpcklpd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + x64Gen_unpcklpd_xmmReg_xmmReg(x64GenContext, regR, regA); } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_BOTTOM_AND_TOP_SWAPPED ) { - cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); - if( imlInstruction->op_fpr_r_r.registerResult != imlInstruction->op_fpr_r_r.registerOperand ) - x64Gen_movaps_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); - _swapPS0PS1(x64GenContext, imlInstruction->op_fpr_r_r.registerResult); + if( regR != regA ) + x64Gen_movaps_xmmReg_xmmReg(x64GenContext, regR, regA); + _swapPS0PS1(x64GenContext, regR); } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_TOP_TO_TOP ) { - cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); - x64Gen_shufpd_xmmReg_xmmReg_imm8(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand, 2); + x64Gen_shufpd_xmmReg_xmmReg_imm8(x64GenContext, regR, regA, 2); } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM ) { - cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); // use unpckhpd here? - x64Gen_shufpd_xmmReg_xmmReg_imm8(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand, 3); - _swapPS0PS1(x64GenContext, imlInstruction->op_fpr_r_r.registerResult); + x64Gen_shufpd_xmmReg_xmmReg_imm8(x64GenContext, regR, regA, 3); + _swapPS0PS1(x64GenContext, regR); } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM ) { - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - assert_dbg(); - } - x64Gen_mulsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + x64Gen_mulsd_xmmReg_xmmReg(x64GenContext, regR, regA); } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_MULTIPLY_PAIR ) { - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - assert_dbg(); - } - x64Gen_mulpd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + x64Gen_mulpd_xmmReg_xmmReg(x64GenContext, regR, regA); } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_DIVIDE_BOTTOM ) { - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - assert_dbg(); - } - x64Gen_divsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + x64Gen_divsd_xmmReg_xmmReg(x64GenContext, regR, regA); } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_DIVIDE_PAIR) { - if (imlInstruction->crRegister != PPC_REC_INVALID_REGISTER) - { - assert_dbg(); - } - x64Gen_divpd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + x64Gen_divpd_xmmReg_xmmReg(x64GenContext, regR, regA); } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_ADD_BOTTOM ) { - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - assert_dbg(); - } - x64Gen_addsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + x64Gen_addsd_xmmReg_xmmReg(x64GenContext, regR, regA); } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_ADD_PAIR ) { - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - assert_dbg(); - } - x64Gen_addpd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + x64Gen_addpd_xmmReg_xmmReg(x64GenContext, regR, regA); } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_SUB_PAIR ) { - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - assert_dbg(); - } - x64Gen_subpd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + x64Gen_subpd_xmmReg_xmmReg(x64GenContext, regR, regA); } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_SUB_BOTTOM ) { - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - assert_dbg(); - } - x64Gen_subsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + x64Gen_subsd_xmmReg_xmmReg(x64GenContext, regR, regA); } else if( imlInstruction->operation == PPCREC_IML_OP_ASSIGN ) { - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - assert_dbg(); - } - x64Gen_movaps_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + x64Gen_movaps_xmmReg_xmmReg(x64GenContext, regR, regA); } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_BOTTOM_FCTIWZ ) { - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - assert_dbg(); - } - x64Gen_cvttsd2si_xmmReg_xmmReg(x64GenContext, REG_RESV_TEMP, imlInstruction->op_fpr_r_r.registerOperand); + x64Gen_cvttsd2si_xmmReg_xmmReg(x64GenContext, REG_RESV_TEMP, regA); x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, REG_RESV_TEMP); // move to FPR register - x64Gen_movq_xmmReg_reg64(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, REG_RESV_TEMP); - } - else if(imlInstruction->operation == PPCREC_IML_OP_FPR_FCMPU_BOTTOM || - imlInstruction->operation == PPCREC_IML_OP_FPR_FCMPU_TOP || - imlInstruction->operation == PPCREC_IML_OP_FPR_FCMPO_BOTTOM ) - { - if( imlInstruction->crRegister == PPC_REC_INVALID_REGISTER ) - { - assert_dbg(); - } - if (imlInstruction->operation == PPCREC_IML_OP_FPR_FCMPU_BOTTOM) - x64Gen_ucomisd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); - else if (imlInstruction->operation == PPCREC_IML_OP_FPR_FCMPU_TOP) - { - // temporarily switch top/bottom of both operands and compare - if (imlInstruction->op_fpr_r_r.registerResult == imlInstruction->op_fpr_r_r.registerOperand) - { - _swapPS0PS1(x64GenContext, imlInstruction->op_fpr_r_r.registerResult); - x64Gen_ucomisd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); - _swapPS0PS1(x64GenContext, imlInstruction->op_fpr_r_r.registerResult); - } - else - { - _swapPS0PS1(x64GenContext, imlInstruction->op_fpr_r_r.registerResult); - _swapPS0PS1(x64GenContext, imlInstruction->op_fpr_r_r.registerOperand); - x64Gen_ucomisd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); - _swapPS0PS1(x64GenContext, imlInstruction->op_fpr_r_r.registerResult); - _swapPS0PS1(x64GenContext, imlInstruction->op_fpr_r_r.registerOperand); - } - } - else - x64Gen_comisd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); - // todo: handle FPSCR updates - // update cr - sint32 crRegister = imlInstruction->crRegister; - // if the parity bit is set (NaN) we need to manually set CR LT, GT and EQ to 0 (comisd/ucomisd sets the respective flags to 1 in case of NaN) - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_PARITY, REG_RSP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_SO)); // unordered - sint32 jumpInstructionOffset1 = x64GenContext->codeBufferIndex; - x64Gen_jmpc_near(x64GenContext, X86_CONDITION_PARITY, 0); - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_UNSIGNED_BELOW, REG_RSP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_LT)); // same as X64_CONDITION_CARRY - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_UNSIGNED_ABOVE, REG_RSP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_GT)); - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_EQUAL, REG_RSP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_EQ)); - sint32 jumpInstructionOffset2 = x64GenContext->codeBufferIndex; - x64Gen_jmpc_near(x64GenContext, X86_CONDITION_NONE, 0); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset1, x64GenContext->codeBufferIndex); - x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_LT), 0); - x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_GT), 0); - x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_EQ), 0); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset2, x64GenContext->codeBufferIndex); - } - else if( imlInstruction->operation == PPCREC_IML_OP_FPR_BOTTOM_FRES_TO_BOTTOM_AND_TOP ) - { - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - assert_dbg(); - } - // move register to XMM15 - x64Gen_movsd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, imlInstruction->op_fpr_r_r.registerOperand); - - // call assembly routine to calculate accurate FRES result in XMM15 - x64Gen_mov_reg64_imm64(x64GenContext, REG_RESV_TEMP, (uint64)recompiler_fres); - x64Gen_call_reg64(x64GenContext, REG_RESV_TEMP); - - // copy result to bottom and top half of result register - x64Gen_movddup_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, REG_RESV_FPR_TEMP); + x64Gen_movq_xmmReg_reg64(x64GenContext, regR, REG_RESV_TEMP); } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_BOTTOM_RECIPROCAL_SQRT) { - cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); // move register to XMM15 - x64Gen_movsd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, imlInstruction->op_fpr_r_r.registerOperand); + x64Gen_movsd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, regA); // call assembly routine to calculate accurate FRSQRTE result in XMM15 x64Gen_mov_reg64_imm64(x64GenContext, REG_RESV_TEMP, (uint64)recompiler_frsqrte); x64Gen_call_reg64(x64GenContext, REG_RESV_TEMP); // copy result to bottom of result register - x64Gen_movsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, REG_RESV_FPR_TEMP); + x64Gen_movsd_xmmReg_xmmReg(x64GenContext, regR, REG_RESV_FPR_TEMP); } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_NEGATE_PAIR ) { - cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); // copy register - if( imlInstruction->op_fpr_r_r.registerResult != imlInstruction->op_fpr_r_r.registerOperand ) + if( regR != regA ) { - x64Gen_movaps_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + x64Gen_movaps_xmmReg_xmmReg(x64GenContext, regR, regA); } // toggle sign bits - x64Gen_xorps_xmmReg_mem128Reg64(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_xorNegateMaskPair)); + x64Gen_xorps_xmmReg_mem128Reg64(x64GenContext, regR, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_xorNegateMaskPair)); } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_ABS_PAIR ) { - cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); // copy register - if( imlInstruction->op_fpr_r_r.registerResult != imlInstruction->op_fpr_r_r.registerOperand ) + if( regR != regA ) { - x64Gen_movaps_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, imlInstruction->op_fpr_r_r.registerOperand); + x64Gen_movaps_xmmReg_xmmReg(x64GenContext, regR, regA); } // set sign bit to 0 - x64Gen_andps_xmmReg_mem128Reg64(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_andAbsMaskPair)); + x64Gen_andps_xmmReg_mem128Reg64(x64GenContext, regR, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_andAbsMaskPair)); } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_FRES_PAIR || imlInstruction->operation == PPCREC_IML_OP_FPR_FRSQRTE_PAIR) { - cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); // calculate bottom half of result - x64Gen_movsd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, imlInstruction->op_fpr_r_r.registerOperand); + x64Gen_movsd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, regA); if(imlInstruction->operation == PPCREC_IML_OP_FPR_FRES_PAIR) x64Gen_mov_reg64_imm64(x64GenContext, REG_RESV_TEMP, (uint64)recompiler_fres); else x64Gen_mov_reg64_imm64(x64GenContext, REG_RESV_TEMP, (uint64)recompiler_frsqrte); x64Gen_call_reg64(x64GenContext, REG_RESV_TEMP); // calculate fres result in xmm15 - x64Gen_movsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, REG_RESV_FPR_TEMP); + x64Gen_movsd_xmmReg_xmmReg(x64GenContext, regR, REG_RESV_FPR_TEMP); // calculate top half of result // todo - this top to bottom copy can be optimized? - x64Gen_shufpd_xmmReg_xmmReg_imm8(x64GenContext, REG_RESV_FPR_TEMP, imlInstruction->op_fpr_r_r.registerOperand, 3); + x64Gen_shufpd_xmmReg_xmmReg_imm8(x64GenContext, REG_RESV_FPR_TEMP, regA, 3); x64Gen_shufpd_xmmReg_xmmReg_imm8(x64GenContext, REG_RESV_FPR_TEMP, REG_RESV_FPR_TEMP, 1); // swap top and bottom x64Gen_call_reg64(x64GenContext, REG_RESV_TEMP); // calculate fres result in xmm15 - x64Gen_unpcklpd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r.registerResult, REG_RESV_FPR_TEMP); // copy bottom to top + x64Gen_unpcklpd_xmmReg_xmmReg(x64GenContext, regR, REG_RESV_FPR_TEMP); // copy bottom to top } else { @@ -1006,90 +840,84 @@ void PPCRecompilerX64Gen_imlInstruction_fpr_r_r(PPCRecFunction_t* PPCRecFunction /* * FPR = op (fprA, fprB) */ -void PPCRecompilerX64Gen_imlInstruction_fpr_r_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) +void PPCRecompilerX64Gen_imlInstruction_fpr_r_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) { - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + uint32 regR = _regF64(imlInstruction->op_fpr_r_r_r.regR); + uint32 regA = _regF64(imlInstruction->op_fpr_r_r_r.regA); + uint32 regB = _regF64(imlInstruction->op_fpr_r_r_r.regB); if (imlInstruction->operation == PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM) { - if (imlInstruction->crRegister != PPC_REC_INVALID_REGISTER) + if (regR == regA) { - assert_dbg(); + x64Gen_mulsd_xmmReg_xmmReg(x64GenContext, regR, regB); } - if (imlInstruction->op_fpr_r_r_r.registerResult == imlInstruction->op_fpr_r_r_r.registerOperandA) + else if (regR == regB) { - x64Gen_mulsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r.registerOperandB); - } - else if (imlInstruction->op_fpr_r_r_r.registerResult == imlInstruction->op_fpr_r_r_r.registerOperandB) - { - x64Gen_mulsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r.registerOperandA); + x64Gen_mulsd_xmmReg_xmmReg(x64GenContext, regR, regA); } else { - x64Gen_movsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r.registerOperandA); - x64Gen_mulsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r.registerOperandB); + x64Gen_movsd_xmmReg_xmmReg(x64GenContext, regR, regA); + x64Gen_mulsd_xmmReg_xmmReg(x64GenContext, regR, regB); } } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_ADD_BOTTOM) { - // registerResult(fp0) = registerOperandA(fp0) + registerOperandB(fp0) - cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); // todo: Use AVX 3-operand VADDSD if available - if (imlInstruction->op_fpr_r_r_r.registerResult == imlInstruction->op_fpr_r_r_r.registerOperandA) + if (regR == regA) { - x64Gen_addsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r.registerOperandB); + x64Gen_addsd_xmmReg_xmmReg(x64GenContext, regR, regB); } - else if (imlInstruction->op_fpr_r_r_r.registerResult == imlInstruction->op_fpr_r_r_r.registerOperandB) + else if (regR == regB) { - x64Gen_addsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r.registerOperandA); + x64Gen_addsd_xmmReg_xmmReg(x64GenContext, regR, regA); } else { - x64Gen_movaps_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r.registerOperandA); - x64Gen_addsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r.registerOperandB); + x64Gen_movaps_xmmReg_xmmReg(x64GenContext, regR, regA); + x64Gen_addsd_xmmReg_xmmReg(x64GenContext, regR, regB); } } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_SUB_PAIR) { // registerResult = registerOperandA - registerOperandB - cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); - if( imlInstruction->op_fpr_r_r_r.registerResult == imlInstruction->op_fpr_r_r_r.registerOperandA ) + if( regR == regA ) { - x64Gen_subpd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r.registerOperandB); + x64Gen_subpd_xmmReg_xmmReg(x64GenContext, regR, regB); } else if (g_CPUFeatures.x86.avx) { - x64Gen_avx_VSUBPD_xmm_xmm_xmm(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r.registerOperandA, imlInstruction->op_fpr_r_r_r.registerOperandB); + x64Gen_avx_VSUBPD_xmm_xmm_xmm(x64GenContext, regR, regA, regB); } - else if( imlInstruction->op_fpr_r_r_r.registerResult == imlInstruction->op_fpr_r_r_r.registerOperandB ) + else if( regR == regB ) { - x64Gen_movaps_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, imlInstruction->op_fpr_r_r_r.registerOperandA); - x64Gen_subpd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, imlInstruction->op_fpr_r_r_r.registerOperandB); - x64Gen_movaps_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, REG_RESV_FPR_TEMP); + x64Gen_movaps_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, regA); + x64Gen_subpd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, regB); + x64Gen_movaps_xmmReg_xmmReg(x64GenContext, regR, REG_RESV_FPR_TEMP); } else { - x64Gen_movaps_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r.registerOperandA); - x64Gen_subpd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r.registerOperandB); + x64Gen_movaps_xmmReg_xmmReg(x64GenContext, regR, regA); + x64Gen_subpd_xmmReg_xmmReg(x64GenContext, regR, regB); } } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_SUB_BOTTOM ) { - cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); - if( imlInstruction->op_fpr_r_r_r.registerResult == imlInstruction->op_fpr_r_r_r.registerOperandA ) + if( regR == regA ) { - x64Gen_subsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r.registerOperandB); + x64Gen_subsd_xmmReg_xmmReg(x64GenContext, regR, regB); } - else if( imlInstruction->op_fpr_r_r_r.registerResult == imlInstruction->op_fpr_r_r_r.registerOperandB ) + else if( regR == regB ) { - x64Gen_movsd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, imlInstruction->op_fpr_r_r_r.registerOperandA); - x64Gen_subsd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, imlInstruction->op_fpr_r_r_r.registerOperandB); - x64Gen_movsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, REG_RESV_FPR_TEMP); + x64Gen_movsd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, regA); + x64Gen_subsd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, regB); + x64Gen_movsd_xmmReg_xmmReg(x64GenContext, regR, REG_RESV_FPR_TEMP); } else { - x64Gen_movsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r.registerOperandA); - x64Gen_subsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r.registerOperandB); + x64Gen_movsd_xmmReg_xmmReg(x64GenContext, regR, regA); + x64Gen_subsd_xmmReg_xmmReg(x64GenContext, regR, regB); } } else @@ -1099,38 +927,39 @@ void PPCRecompilerX64Gen_imlInstruction_fpr_r_r_r(PPCRecFunction_t* PPCRecFuncti /* * FPR = op (fprA, fprB, fprC) */ -void PPCRecompilerX64Gen_imlInstruction_fpr_r_r_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) +void PPCRecompilerX64Gen_imlInstruction_fpr_r_r_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) { - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + uint32 regR = _regF64(imlInstruction->op_fpr_r_r_r_r.regR); + uint32 regA = _regF64(imlInstruction->op_fpr_r_r_r_r.regA); + uint32 regB = _regF64(imlInstruction->op_fpr_r_r_r_r.regB); + uint32 regC = _regF64(imlInstruction->op_fpr_r_r_r_r.regC); + if( imlInstruction->operation == PPCREC_IML_OP_FPR_SUM0 ) { - cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); - // todo: Investigate if there are other optimizations possible if the operand registers overlap // generic case // 1) move frA bottom to frTemp bottom and top - x64Gen_movddup_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, imlInstruction->op_fpr_r_r_r_r.registerOperandA); + x64Gen_movddup_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, regA); // 2) add frB (both halfs, lower half is overwritten in the next step) - x64Gen_addpd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, imlInstruction->op_fpr_r_r_r_r.registerOperandB); + x64Gen_addpd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, regB); // 3) Interleave top of frTemp and frC - x64Gen_unpckhpd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, imlInstruction->op_fpr_r_r_r_r.registerOperandC); + x64Gen_unpckhpd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, regC); // todo: We can optimize the REG_RESV_FPR_TEMP -> resultReg copy operation away when the result register does not overlap with any of the operand registers - x64Gen_movaps_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r_r.registerResult, REG_RESV_FPR_TEMP); + x64Gen_movaps_xmmReg_xmmReg(x64GenContext, regR, REG_RESV_FPR_TEMP); } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_SUM1 ) { - cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); // todo: Investigate if there are other optimizations possible if the operand registers overlap // 1) move frA bottom to frTemp bottom and top - x64Gen_movddup_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, imlInstruction->op_fpr_r_r_r_r.registerOperandA); + x64Gen_movddup_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, regA); // 2) add frB (both halfs, lower half is overwritten in the next step) - x64Gen_addpd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, imlInstruction->op_fpr_r_r_r_r.registerOperandB); + x64Gen_addpd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, regB); // 3) Copy bottom from frC - x64Gen_movsd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, imlInstruction->op_fpr_r_r_r_r.registerOperandC); + x64Gen_movsd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, regC); //// 4) Swap bottom and top half //x64Gen_shufpd_xmmReg_xmmReg_imm8(x64GenContext, REG_RESV_FPR_TEMP, REG_RESV_FPR_TEMP, 1); // todo: We can optimize the REG_RESV_FPR_TEMP -> resultReg copy operation away when the result register does not overlap with any of the operand registers - x64Gen_movaps_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r_r.registerResult, REG_RESV_FPR_TEMP); + x64Gen_movaps_xmmReg_xmmReg(x64GenContext, regR, REG_RESV_FPR_TEMP); //float s0 = (float)hCPU->fpr[frC].fp0; //float s1 = (float)(hCPU->fpr[frA].fp0 + hCPU->fpr[frB].fp1); @@ -1139,107 +968,135 @@ void PPCRecompilerX64Gen_imlInstruction_fpr_r_r_r_r(PPCRecFunction_t* PPCRecFunc } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_SELECT_BOTTOM ) { - cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); - x64Gen_comisd_xmmReg_mem64Reg64(x64GenContext, imlInstruction->op_fpr_r_r_r_r.registerOperandA, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_constDouble0_0)); - sint32 jumpInstructionOffset1 = x64GenContext->codeBufferIndex; + x64Gen_comisd_xmmReg_mem64Reg64(x64GenContext, regA, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_constDouble0_0)); + sint32 jumpInstructionOffset1 = x64GenContext->emitter->GetWriteIndex(); x64Gen_jmpc_near(x64GenContext, X86_CONDITION_UNSIGNED_BELOW, 0); // select C - x64Gen_movsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r_r.registerOperandC); - sint32 jumpInstructionOffset2 = x64GenContext->codeBufferIndex; + x64Gen_movsd_xmmReg_xmmReg(x64GenContext, regR, regC); + sint32 jumpInstructionOffset2 = x64GenContext->emitter->GetWriteIndex(); x64Gen_jmpc_near(x64GenContext, X86_CONDITION_NONE, 0); // select B - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset1, x64GenContext->codeBufferIndex); - x64Gen_movsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r_r.registerOperandB); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset1, x64GenContext->emitter->GetWriteIndex()); + x64Gen_movsd_xmmReg_xmmReg(x64GenContext, regR, regB); // end - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset2, x64GenContext->codeBufferIndex); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset2, x64GenContext->emitter->GetWriteIndex()); } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_SELECT_PAIR ) { - cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); // select bottom - x64Gen_comisd_xmmReg_mem64Reg64(x64GenContext, imlInstruction->op_fpr_r_r_r_r.registerOperandA, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_constDouble0_0)); - sint32 jumpInstructionOffset1_bottom = x64GenContext->codeBufferIndex; + x64Gen_comisd_xmmReg_mem64Reg64(x64GenContext, regA, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_constDouble0_0)); + sint32 jumpInstructionOffset1_bottom = x64GenContext->emitter->GetWriteIndex(); x64Gen_jmpc_near(x64GenContext, X86_CONDITION_UNSIGNED_BELOW, 0); // select C bottom - x64Gen_movsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r_r.registerOperandC); - sint32 jumpInstructionOffset2_bottom = x64GenContext->codeBufferIndex; + x64Gen_movsd_xmmReg_xmmReg(x64GenContext, regR, regC); + sint32 jumpInstructionOffset2_bottom = x64GenContext->emitter->GetWriteIndex(); x64Gen_jmpc_near(x64GenContext, X86_CONDITION_NONE, 0); // select B bottom - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset1_bottom, x64GenContext->codeBufferIndex); - x64Gen_movsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r_r.registerOperandB); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset1_bottom, x64GenContext->emitter->GetWriteIndex()); + x64Gen_movsd_xmmReg_xmmReg(x64GenContext, regR, regB); // end - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset2_bottom, x64GenContext->codeBufferIndex); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset2_bottom, x64GenContext->emitter->GetWriteIndex()); // select top - x64Gen_movhlps_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, imlInstruction->op_fpr_r_r_r_r.registerOperandA); // copy top to bottom (todo: May cause stall?) + x64Gen_movhlps_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, regA); // copy top to bottom (todo: May cause stall?) x64Gen_comisd_xmmReg_mem64Reg64(x64GenContext, REG_RESV_FPR_TEMP, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_constDouble0_0)); - sint32 jumpInstructionOffset1_top = x64GenContext->codeBufferIndex; + sint32 jumpInstructionOffset1_top = x64GenContext->emitter->GetWriteIndex(); x64Gen_jmpc_near(x64GenContext, X86_CONDITION_UNSIGNED_BELOW, 0); // select C top - //x64Gen_movsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r_r.registerOperandC); - x64Gen_shufpd_xmmReg_xmmReg_imm8(x64GenContext, imlInstruction->op_fpr_r_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r_r.registerOperandC, 2); - sint32 jumpInstructionOffset2_top = x64GenContext->codeBufferIndex; + //x64Gen_movsd_xmmReg_xmmReg(x64GenContext, registerResult, registerOperandC); + x64Gen_shufpd_xmmReg_xmmReg_imm8(x64GenContext, regR, regC, 2); + sint32 jumpInstructionOffset2_top = x64GenContext->emitter->GetWriteIndex(); x64Gen_jmpc_near(x64GenContext, X86_CONDITION_NONE, 0); // select B top - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset1_top, x64GenContext->codeBufferIndex); - //x64Gen_movsd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r_r.registerOperandB); - x64Gen_shufpd_xmmReg_xmmReg_imm8(x64GenContext, imlInstruction->op_fpr_r_r_r_r.registerResult, imlInstruction->op_fpr_r_r_r_r.registerOperandB, 2); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset1_top, x64GenContext->emitter->GetWriteIndex()); + //x64Gen_movsd_xmmReg_xmmReg(x64GenContext, registerResult, registerOperandB); + x64Gen_shufpd_xmmReg_xmmReg_imm8(x64GenContext, regR, regB, 2); // end - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset2_top, x64GenContext->codeBufferIndex); + PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset2_top, x64GenContext->emitter->GetWriteIndex()); } else assert_dbg(); } -/* - * Single FPR operation - */ -void PPCRecompilerX64Gen_imlInstruction_fpr_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) +void PPCRecompilerX64Gen_imlInstruction_fpr_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) { - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); + uint32 regR = _regF64(imlInstruction->op_fpr_r.regR); + if( imlInstruction->operation == PPCREC_IML_OP_FPR_NEGATE_BOTTOM ) { - cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); - // toggle sign bit - x64Gen_xorps_xmmReg_mem128Reg64(x64GenContext, imlInstruction->op_fpr_r.registerResult, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_xorNegateMaskBottom)); + x64Gen_xorps_xmmReg_mem128Reg64(x64GenContext, regR, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_xorNegateMaskBottom)); } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_ABS_BOTTOM ) { - cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); - // mask out sign bit - x64Gen_andps_xmmReg_mem128Reg64(x64GenContext, imlInstruction->op_fpr_r.registerResult, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_andAbsMaskBottom)); + x64Gen_andps_xmmReg_mem128Reg64(x64GenContext, regR, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_andAbsMaskBottom)); } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_NEGATIVE_ABS_BOTTOM ) { - cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); - // set sign bit - x64Gen_orps_xmmReg_mem128Reg64(x64GenContext, imlInstruction->op_fpr_r.registerResult, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_xorNegateMaskBottom)); + x64Gen_orps_xmmReg_mem128Reg64(x64GenContext, regR, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_xorNegateMaskBottom)); } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_BOTTOM ) { - cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); // convert to 32bit single - x64Gen_cvtsd2ss_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r.registerResult, imlInstruction->op_fpr_r.registerResult); + x64Gen_cvtsd2ss_xmmReg_xmmReg(x64GenContext, regR, regR); // convert back to 64bit double - x64Gen_cvtss2sd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r.registerResult, imlInstruction->op_fpr_r.registerResult); + x64Gen_cvtss2sd_xmmReg_xmmReg(x64GenContext, regR, regR); } else if( imlInstruction->operation == PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_PAIR ) { - cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); // convert to 32bit singles - x64Gen_cvtpd2ps_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r.registerResult, imlInstruction->op_fpr_r.registerResult); + x64Gen_cvtpd2ps_xmmReg_xmmReg(x64GenContext, regR, regR); // convert back to 64bit doubles - x64Gen_cvtps2pd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r.registerResult, imlInstruction->op_fpr_r.registerResult); + x64Gen_cvtps2pd_xmmReg_xmmReg(x64GenContext, regR, regR); } else if (imlInstruction->operation == PPCREC_IML_OP_FPR_EXPAND_BOTTOM32_TO_BOTTOM64_AND_TOP64) { - cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); // convert bottom to 64bit double - x64Gen_cvtss2sd_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r.registerResult, imlInstruction->op_fpr_r.registerResult); + x64Gen_cvtss2sd_xmmReg_xmmReg(x64GenContext, regR, regR); // copy to top half - x64Gen_movddup_xmmReg_xmmReg(x64GenContext, imlInstruction->op_fpr_r.registerResult, imlInstruction->op_fpr_r.registerResult); + x64Gen_movddup_xmmReg_xmmReg(x64GenContext, regR, regR); } else { cemu_assert_unimplemented(); } } + +void PPCRecompilerX64Gen_imlInstruction_fpr_compare(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) +{ + auto regR = _reg8(imlInstruction->op_fpr_compare.regR); + auto regA = _regF64(imlInstruction->op_fpr_compare.regA); + auto regB = _regF64(imlInstruction->op_fpr_compare.regB); + + x64GenContext->emitter->XOR_dd(_reg32_from_reg8(regR), _reg32_from_reg8(regR)); + x64Gen_ucomisd_xmmReg_xmmReg(x64GenContext, regA, regB); + + if (imlInstruction->op_fpr_compare.cond == IMLCondition::UNORDERED_GT) + { + // GT case can be covered with a single SETnbe which checks CF==0 && ZF==0 (unordered sets both) + x64GenContext->emitter->SETcc_b(X86Cond::X86_CONDITION_NBE, regR); + return; + } + else if (imlInstruction->op_fpr_compare.cond == IMLCondition::UNORDERED_U) + { + // unordered case can be checked via PF + x64GenContext->emitter->SETcc_b(X86Cond::X86_CONDITION_PE, regR); + return; + } + + // remember unordered state + auto regTmp = _reg32_from_reg8(_reg32(REG_RESV_TEMP)); + x64GenContext->emitter->SETcc_b(X86Cond::X86_CONDITION_PO, regTmp); // by reversing the parity we can avoid having to XOR the value for masking the LT/EQ conditions + + X86Cond x86Cond; + switch (imlInstruction->op_fpr_compare.cond) + { + case IMLCondition::UNORDERED_LT: + x64GenContext->emitter->SETcc_b(X86Cond::X86_CONDITION_B, regR); + break; + case IMLCondition::UNORDERED_EQ: + x64GenContext->emitter->SETcc_b(X86Cond::X86_CONDITION_Z, regR); + break; + default: + cemu_assert_unimplemented(); + } + x64GenContext->emitter->AND_bb(_reg8_from_reg32(regR), _reg8_from_reg32(regTmp)); // if unordered (PF=1) then force LT/GT/EQ to zero +} \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64Gen.cpp b/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64Gen.cpp similarity index 90% rename from src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64Gen.cpp rename to src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64Gen.cpp index 19327f46..efe929d0 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64Gen.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64Gen.cpp @@ -1,62 +1,31 @@ -#include "PPCRecompiler.h" -#include "PPCRecompilerIml.h" -#include "PPCRecompilerX64.h" +#include "BackendX64.h" // x86/x64 extension opcodes that could be useful: // ANDN // mulx, rorx, sarx, shlx, shrx // PDEP, PEXT -void x64Gen_checkBuffer(x64GenContext_t* x64GenContext) -{ - // todo -} - void x64Gen_writeU8(x64GenContext_t* x64GenContext, uint8 v) { - if( x64GenContext->codeBufferIndex+1 > x64GenContext->codeBufferSize ) - { - x64GenContext->codeBufferSize *= 2; - x64GenContext->codeBuffer = (uint8*)realloc(x64GenContext->codeBuffer, x64GenContext->codeBufferSize); - } - *(uint8*)(x64GenContext->codeBuffer+x64GenContext->codeBufferIndex) = v; - x64GenContext->codeBufferIndex++; + x64GenContext->emitter->_emitU8(v); } void x64Gen_writeU16(x64GenContext_t* x64GenContext, uint32 v) { - if( x64GenContext->codeBufferIndex+2 > x64GenContext->codeBufferSize ) - { - x64GenContext->codeBufferSize *= 2; - x64GenContext->codeBuffer = (uint8*)realloc(x64GenContext->codeBuffer, x64GenContext->codeBufferSize); - } - *(uint16*)(x64GenContext->codeBuffer+x64GenContext->codeBufferIndex) = v; - x64GenContext->codeBufferIndex += 2; + x64GenContext->emitter->_emitU16(v); } void x64Gen_writeU32(x64GenContext_t* x64GenContext, uint32 v) { - if( x64GenContext->codeBufferIndex+4 > x64GenContext->codeBufferSize ) - { - x64GenContext->codeBufferSize *= 2; - x64GenContext->codeBuffer = (uint8*)realloc(x64GenContext->codeBuffer, x64GenContext->codeBufferSize); - } - *(uint32*)(x64GenContext->codeBuffer+x64GenContext->codeBufferIndex) = v; - x64GenContext->codeBufferIndex += 4; + x64GenContext->emitter->_emitU32(v); } void x64Gen_writeU64(x64GenContext_t* x64GenContext, uint64 v) { - if( x64GenContext->codeBufferIndex+8 > x64GenContext->codeBufferSize ) - { - x64GenContext->codeBufferSize *= 2; - x64GenContext->codeBuffer = (uint8*)realloc(x64GenContext->codeBuffer, x64GenContext->codeBufferSize); - } - *(uint64*)(x64GenContext->codeBuffer+x64GenContext->codeBufferIndex) = v; - x64GenContext->codeBufferIndex += 8; + x64GenContext->emitter->_emitU64(v); } -#include "x64Emit.hpp" +#include "X64Emit.hpp" void _x64Gen_writeMODRMDeprecated(x64GenContext_t* x64GenContext, sint32 dataRegister, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32) { @@ -67,7 +36,7 @@ void _x64Gen_writeMODRMDeprecated(x64GenContext_t* x64GenContext, sint32 dataReg forceUseOffset = true; } - if (memRegisterB64 == REG_NONE) + if (memRegisterB64 == X86_REG_NONE) { // memRegisterA64 + memImmS32 uint8 modRM = (dataRegister & 7) * 8 + (memRegisterA64 & 7); @@ -352,7 +321,7 @@ void x64Gen_mov_mem32Reg64_imm32(x64GenContext_t* x64GenContext, sint32 memRegis void x64Gen_mov_mem64Reg64_imm32(x64GenContext_t* x64GenContext, sint32 memRegister, uint32 memImmU32, uint32 dataImmU32) { // MOV QWORD [<memReg>+<memImmU32>], dataImmU32 - if( memRegister == REG_R14 ) + if( memRegister == X86_REG_R14 ) { sint32 memImmS32 = (sint32)memImmU32; if( memImmS32 == 0 ) @@ -384,7 +353,7 @@ void x64Gen_mov_mem64Reg64_imm32(x64GenContext_t* x64GenContext, sint32 memRegis void x64Gen_mov_mem8Reg64_imm8(x64GenContext_t* x64GenContext, sint32 memRegister, uint32 memImmU32, uint8 dataImmU8) { // MOV BYTE [<memReg64>+<memImmU32>], dataImmU8 - if( memRegister == REG_RSP ) + if( memRegister == X86_REG_RSP ) { sint32 memImmS32 = (sint32)memImmU32; if( memImmS32 >= -128 && memImmS32 <= 127 ) @@ -625,7 +594,7 @@ void _x64_op_reg64Low_mem8Reg64(x64GenContext_t* x64GenContext, sint32 dstRegist if (memRegister64 >= 8) x64Gen_writeU8(x64GenContext, 0x41); x64Gen_writeU8(x64GenContext, opByte); - _x64Gen_writeMODRMDeprecated(x64GenContext, dstRegister, memRegister64, REG_NONE, memImmS32); + _x64Gen_writeMODRMDeprecated(x64GenContext, dstRegister, memRegister64, X86_REG_NONE, memImmS32); } void x64Gen_or_reg64Low8_mem8Reg64(x64GenContext_t* x64GenContext, sint32 dstRegister, sint32 memRegister64, sint32 memImmS32) @@ -643,40 +612,6 @@ void x64Gen_mov_mem8Reg64_reg64Low8(x64GenContext_t* x64GenContext, sint32 dstRe _x64_op_reg64Low_mem8Reg64(x64GenContext, dstRegister, memRegister64, memImmS32, 0x88); } -void x64Gen_lock_cmpxchg_mem32Reg64PlusReg64_reg64(x64GenContext_t* x64GenContext, sint32 memRegisterA64, sint32 memRegisterB64, sint32 memImmS32, sint32 srcRegister) -{ - // LOCK CMPXCHG DWORD [<reg64> + <reg64> + <imm64>], <srcReg64> (low dword) - x64Gen_writeU8(x64GenContext, 0xF0); // LOCK prefix - - if( srcRegister >= 8 || memRegisterA64 >= 8|| memRegisterB64 >= 8 ) - x64Gen_writeU8(x64GenContext, 0x40+((srcRegister>=8)?4:0)+((memRegisterA64>=8)?1:0)+((memRegisterB64>=8)?2:0)); - - x64Gen_writeU8(x64GenContext, 0x0F); - x64Gen_writeU8(x64GenContext, 0xB1); - - _x64Gen_writeMODRMDeprecated(x64GenContext, srcRegister, memRegisterA64, memRegisterB64, memImmS32); -} - -void x64Gen_lock_cmpxchg_mem32Reg64_reg64(x64GenContext_t* x64GenContext, sint32 memRegister64, sint32 memImmS32, sint32 srcRegister) -{ - // LOCK CMPXCHG DWORD [<reg64> + <imm64>], <srcReg64> (low dword) - x64Gen_writeU8(x64GenContext, 0xF0); // LOCK prefix - - if( srcRegister >= 8 || memRegister64 >= 8 ) - x64Gen_writeU8(x64GenContext, 0x40+((srcRegister>=8)?4:0)+((memRegister64>=8)?1:0)); - - x64Gen_writeU8(x64GenContext, 0x0F); - x64Gen_writeU8(x64GenContext, 0xB1); - - if( memImmS32 == 0 ) - { - x64Gen_writeU8(x64GenContext, 0x45+(srcRegister&7)*8); - x64Gen_writeU8(x64GenContext, 0x00); - } - else - assert_dbg(); -} - void x64Gen_add_reg64_reg64(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) { // ADD <destReg>, <srcReg> @@ -732,7 +667,7 @@ void x64Gen_add_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegis } else { - if( srcRegister == REG_RAX ) + if( srcRegister == X86_REG_RAX ) { // special EAX short form x64Gen_writeU8(x64GenContext, 0x05); @@ -772,7 +707,7 @@ void x64Gen_sub_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegis } else { - if( srcRegister == REG_RAX ) + if( srcRegister == X86_REG_RAX ) { // special EAX short form x64Gen_writeU8(x64GenContext, 0x2D); @@ -811,7 +746,7 @@ void x64Gen_sub_mem32reg64_imm32(x64GenContext_t* x64GenContext, sint32 memRegis { // SUB <mem32_memReg64>, <imm32> sint32 immS32 = (sint32)immU32; - if( memRegister == REG_RSP ) + if( memRegister == X86_REG_RSP ) { if( memImmS32 >= 128 ) { @@ -843,64 +778,11 @@ void x64Gen_sub_mem32reg64_imm32(x64GenContext_t* x64GenContext, sint32 memRegis } } -void x64Gen_sbb_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) -{ - // SBB <destReg64_low32>, <srcReg64_low32> - if( destRegister >= 8 && srcRegister >= 8 ) - x64Gen_writeU8(x64GenContext, 0x45); - else if( srcRegister >= 8 ) - x64Gen_writeU8(x64GenContext, 0x44); - else if( destRegister >= 8 ) - x64Gen_writeU8(x64GenContext, 0x41); - x64Gen_writeU8(x64GenContext, 0x19); - x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)*8+(destRegister&7)); -} - -void x64Gen_adc_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) -{ - // ADC <destReg64_low32>, <srcReg64_low32> - if( destRegister >= 8 && srcRegister >= 8 ) - x64Gen_writeU8(x64GenContext, 0x45); - else if( srcRegister >= 8 ) - x64Gen_writeU8(x64GenContext, 0x44); - else if( destRegister >= 8 ) - x64Gen_writeU8(x64GenContext, 0x41); - x64Gen_writeU8(x64GenContext, 0x11); - x64Gen_writeU8(x64GenContext, 0xC0+(srcRegister&7)*8+(destRegister&7)); -} - -void x64Gen_adc_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegister, uint32 immU32) -{ - sint32 immS32 = (sint32)immU32; - if( srcRegister >= 8 ) - x64Gen_writeU8(x64GenContext, 0x41); - if( immS32 >= -128 && immS32 <= 127 ) - { - x64Gen_writeU8(x64GenContext, 0x83); - x64Gen_writeU8(x64GenContext, 0xD0+(srcRegister&7)); - x64Gen_writeU8(x64GenContext, (uint8)immS32); - } - else - { - if( srcRegister == REG_RAX ) - { - // special EAX short form - x64Gen_writeU8(x64GenContext, 0x15); - } - else - { - x64Gen_writeU8(x64GenContext, 0x81); - x64Gen_writeU8(x64GenContext, 0xD0+(srcRegister&7)); - } - x64Gen_writeU32(x64GenContext, immU32); - } -} - void x64Gen_dec_mem32(x64GenContext_t* x64GenContext, sint32 memoryRegister, uint32 memoryImmU32) { // DEC dword [<reg64>+imm] sint32 memoryImmS32 = (sint32)memoryImmU32; - if (memoryRegister != REG_RSP) + if (memoryRegister != X86_REG_RSP) assert_dbg(); // not supported yet if (memoryImmS32 >= -128 && memoryImmS32 <= 127) { @@ -981,7 +863,7 @@ void x64Gen_and_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegis } else { - if( srcRegister == REG_RAX ) + if( srcRegister == X86_REG_RAX ) { // special EAX short form x64Gen_writeU8(x64GenContext, 0x25); @@ -1026,7 +908,7 @@ void x64Gen_test_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegi sint32 immS32 = (sint32)immU32; if( srcRegister >= 8 ) x64Gen_writeU8(x64GenContext, 0x41); - if( srcRegister == REG_RAX ) + if( srcRegister == X86_REG_RAX ) { // special EAX short form x64Gen_writeU8(x64GenContext, 0xA9); @@ -1052,7 +934,7 @@ void x64Gen_cmp_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegis } else { - if( srcRegister == REG_RAX ) + if( srcRegister == X86_REG_RAX ) { // special RAX short form x64Gen_writeU8(x64GenContext, 0x3D); @@ -1082,7 +964,7 @@ void x64Gen_cmp_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 des void x64Gen_cmp_reg64Low32_mem32reg64(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 memRegister, sint32 memImmS32) { // CMP <destReg64_lowDWORD>, DWORD [<memRegister>+<immS32>] - if( memRegister == REG_RSP ) + if( memRegister == X86_REG_RSP ) { if( memImmS32 >= -128 && memImmS32 <= 127 ) assert_dbg(); // todo -> Shorter instruction form @@ -1112,7 +994,7 @@ void x64Gen_or_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegist } else { - if( srcRegister == REG_RAX ) + if( srcRegister == X86_REG_RAX ) { // special EAX short form x64Gen_writeU8(x64GenContext, 0x0D); @@ -1172,7 +1054,7 @@ void x64Gen_xor_reg64Low32_imm32(x64GenContext_t* x64GenContext, sint32 srcRegis } else { - if( srcRegister == REG_RAX ) + if( srcRegister == X86_REG_RAX ) { // special EAX short form x64Gen_writeU8(x64GenContext, 0x35); @@ -1326,16 +1208,6 @@ void x64Gen_cdq(x64GenContext_t* x64GenContext) x64Gen_writeU8(x64GenContext, 0x99); } -void x64Gen_bswap_reg64(x64GenContext_t* x64GenContext, sint32 destRegister) -{ - if( destRegister >= 8 ) - x64Gen_writeU8(x64GenContext, 0x41|8); - else - x64Gen_writeU8(x64GenContext, 0x40|8); - x64Gen_writeU8(x64GenContext, 0x0F); - x64Gen_writeU8(x64GenContext, 0xC8+(destRegister&7)); -} - void x64Gen_bswap_reg64Lower32bit(x64GenContext_t* x64GenContext, sint32 destRegister) { if( destRegister >= 8 ) @@ -1344,16 +1216,6 @@ void x64Gen_bswap_reg64Lower32bit(x64GenContext_t* x64GenContext, sint32 destReg x64Gen_writeU8(x64GenContext, 0xC8+(destRegister&7)); } -void x64Gen_bswap_reg64Lower16bit(x64GenContext_t* x64GenContext, sint32 destRegister) -{ - assert_dbg(); // do not use this instruction, it's result is always undefined. Instead use ROL <reg16>, 8 - //x64Gen_writeU8(x64GenContext, 0x66); - //if( destRegister >= 8 ) - // x64Gen_writeU8(x64GenContext, 0x41); - //x64Gen_writeU8(x64GenContext, 0x0F); - //x64Gen_writeU8(x64GenContext, 0xC8+(destRegister&7)); -} - void x64Gen_lzcnt_reg64Low32_reg64Low32(x64GenContext_t* x64GenContext, sint32 destRegister, sint32 srcRegister) { // SSE4 @@ -1388,7 +1250,7 @@ void x64Gen_setcc_mem8(x64GenContext_t* x64GenContext, sint32 conditionType, sin { // SETcc [<reg64>+imm] sint32 memoryImmS32 = (sint32)memoryImmU32; - if( memoryRegister != REG_RSP ) + if( memoryRegister != X86_REG_RSP ) assert_dbg(); // not supported if( memoryRegister >= 8 ) assert_dbg(); // not supported @@ -1627,7 +1489,7 @@ void x64Gen_bt_mem8(x64GenContext_t* x64GenContext, sint32 memoryRegister, uint3 { // BT [<reg64>+imm], bitIndex (bit test) sint32 memoryImmS32 = (sint32)memoryImmU32; - if( memoryRegister != REG_RSP ) + if( memoryRegister != X86_REG_RSP ) assert_dbg(); // not supported yet if( memoryImmS32 >= -128 && memoryImmS32 <= 127 ) { @@ -1662,7 +1524,7 @@ void x64Gen_jmp_imm32(x64GenContext_t* x64GenContext, uint32 destImm32) void x64Gen_jmp_memReg64(x64GenContext_t* x64GenContext, sint32 memRegister, uint32 immU32) { - if( memRegister == REG_NONE ) + if( memRegister == X86_REG_NONE ) { assert_dbg(); } diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64GenFPU.cpp b/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64GenFPU.cpp similarity index 97% rename from src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64GenFPU.cpp rename to src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64GenFPU.cpp index 92289d68..882820e2 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64GenFPU.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64GenFPU.cpp @@ -1,6 +1,4 @@ -#include "PPCRecompiler.h" -#include "PPCRecompilerIml.h" -#include "PPCRecompilerX64.h" +#include "BackendX64.h" void x64Gen_genSSEVEXPrefix2(x64GenContext_t* x64GenContext, sint32 xmmRegister1, sint32 xmmRegister2, bool use64BitMode) { @@ -44,7 +42,7 @@ void x64Gen_movupd_xmmReg_memReg128(x64GenContext_t* x64GenContext, sint32 xmmRe // SSE2 // move two doubles from memory into xmm register // MOVUPD <xmm>, [<reg>+<imm>] - if( memRegister == REG_ESP ) + if( memRegister == X86_REG_ESP ) { // todo: Short form of instruction if memImmU32 is 0 or in -128 to 127 range // 66 0F 10 84 E4 23 01 00 00 @@ -56,7 +54,7 @@ void x64Gen_movupd_xmmReg_memReg128(x64GenContext_t* x64GenContext, sint32 xmmRe x64Gen_writeU8(x64GenContext, 0xE4); x64Gen_writeU32(x64GenContext, memImmU32); } - else if( memRegister == REG_NONE ) + else if( memRegister == X86_REG_NONE ) { assert_dbg(); //x64Gen_writeU8(x64GenContext, 0x66); @@ -76,7 +74,7 @@ void x64Gen_movupd_memReg128_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRe // SSE2 // move two doubles from memory into xmm register // MOVUPD [<reg>+<imm>], <xmm> - if( memRegister == REG_ESP ) + if( memRegister == X86_REG_ESP ) { // todo: Short form of instruction if memImmU32 is 0 or in -128 to 127 range x64Gen_writeU8(x64GenContext, 0x66); @@ -87,7 +85,7 @@ void x64Gen_movupd_memReg128_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRe x64Gen_writeU8(x64GenContext, 0xE4); x64Gen_writeU32(x64GenContext, memImmU32); } - else if( memRegister == REG_NONE ) + else if( memRegister == X86_REG_NONE ) { assert_dbg(); //x64Gen_writeU8(x64GenContext, 0x66); @@ -106,7 +104,7 @@ void x64Gen_movddup_xmmReg_memReg64(x64GenContext_t* x64GenContext, sint32 xmmRe { // SSE3 // move one double from memory into lower and upper half of a xmm register - if( memRegister == REG_RSP ) + if( memRegister == X86_REG_RSP ) { // MOVDDUP <xmm>, [<reg>+<imm>] // todo: Short form of instruction if memImmU32 is 0 or in -128 to 127 range @@ -119,7 +117,7 @@ void x64Gen_movddup_xmmReg_memReg64(x64GenContext_t* x64GenContext, sint32 xmmRe x64Gen_writeU8(x64GenContext, 0xE4); x64Gen_writeU32(x64GenContext, memImmU32); } - else if( memRegister == REG_R15 ) + else if( memRegister == X86_REG_R15 ) { // MOVDDUP <xmm>, [<reg>+<imm>] // todo: Short form of instruction if memImmU32 is 0 or in -128 to 127 range @@ -131,7 +129,7 @@ void x64Gen_movddup_xmmReg_memReg64(x64GenContext_t* x64GenContext, sint32 xmmRe x64Gen_writeU8(x64GenContext, 0x87+(xmmRegister&7)*8); x64Gen_writeU32(x64GenContext, memImmU32); } - else if( memRegister == REG_NONE ) + else if( memRegister == X86_REG_NONE ) { // MOVDDUP <xmm>, [<imm>] // 36 F2 0F 12 05 - 00 00 00 00 @@ -185,7 +183,7 @@ void x64Gen_movsd_memReg64_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegi { // SSE2 // move lower 64bits (double) of xmm register to memory location - if( memRegister == REG_NONE ) + if( memRegister == X86_REG_NONE ) { // MOVSD [<imm>], <xmm> // F2 0F 11 05 - 45 23 01 00 @@ -197,7 +195,7 @@ void x64Gen_movsd_memReg64_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegi //x64Gen_writeU8(x64GenContext, 0x05+xmmRegister*8); //x64Gen_writeU32(x64GenContext, memImmU32); } - else if( memRegister == REG_RSP ) + else if( memRegister == X86_REG_RSP ) { // MOVSD [RSP+<imm>], <xmm> // F2 0F 11 84 24 - 33 22 11 00 @@ -219,7 +217,7 @@ void x64Gen_movlpd_xmmReg_memReg64(x64GenContext_t* x64GenContext, sint32 xmmReg { // SSE3 // move one double from memory into lower half of a xmm register, leave upper half unchanged(?) - if( memRegister == REG_NONE ) + if( memRegister == X86_REG_NONE ) { // MOVLPD <xmm>, [<imm>] //x64Gen_writeU8(x64GenContext, 0x66); @@ -229,7 +227,7 @@ void x64Gen_movlpd_xmmReg_memReg64(x64GenContext_t* x64GenContext, sint32 xmmReg //x64Gen_writeU32(x64GenContext, memImmU32); assert_dbg(); } - else if( memRegister == REG_RSP ) + else if( memRegister == X86_REG_RSP ) { // MOVLPD <xmm>, [<reg64>+<imm>] // 66 0F 12 84 24 - 33 22 11 00 @@ -348,11 +346,11 @@ void x64Gen_mulpd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegist void x64Gen_mulpd_xmmReg_memReg128(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32) { // SSE2 - if (memRegister == REG_NONE) + if (memRegister == X86_REG_NONE) { assert_dbg(); } - else if (memRegister == REG_R14) + else if (memRegister == X86_REG_R14) { x64Gen_writeU8(x64GenContext, 0x66); x64Gen_writeU8(x64GenContext, (xmmRegister < 8) ? 0x41 : 0x45); @@ -404,7 +402,7 @@ void x64Gen_comisd_xmmReg_mem64Reg64(x64GenContext_t* x64GenContext, sint32 xmmR { // SSE2 // compare bottom double with double from memory location - if( memoryReg == REG_R15 ) + if( memoryReg == X86_REG_R15 ) { x64Gen_writeU8(x64GenContext, 0x66); x64Gen_genSSEVEXPrefix1(x64GenContext, xmmRegisterDest, true); @@ -432,7 +430,7 @@ void x64Gen_comiss_xmmReg_mem64Reg64(x64GenContext_t* x64GenContext, sint32 xmmR { // SSE2 // compare bottom float with float from memory location - if (memoryReg == REG_R15) + if (memoryReg == X86_REG_R15) { x64Gen_genSSEVEXPrefix1(x64GenContext, xmmRegisterDest, true); x64Gen_writeU8(x64GenContext, 0x0F); @@ -448,7 +446,7 @@ void x64Gen_orps_xmmReg_mem128Reg64(x64GenContext_t* x64GenContext, sint32 xmmRe { // SSE2 // and xmm register with 128 bit value from memory - if( memReg == REG_R15 ) + if( memReg == X86_REG_R15 ) { x64Gen_genSSEVEXPrefix2(x64GenContext, memReg, xmmRegisterDest, false); x64Gen_writeU8(x64GenContext, 0x0F); @@ -464,7 +462,7 @@ void x64Gen_xorps_xmmReg_mem128Reg64(x64GenContext_t* x64GenContext, sint32 xmmR { // SSE2 // xor xmm register with 128 bit value from memory - if( memReg == REG_R15 ) + if( memReg == X86_REG_R15 ) { x64Gen_genSSEVEXPrefix1(x64GenContext, xmmRegisterDest, true); // todo: should be x64Gen_genSSEVEXPrefix2() with memReg? x64Gen_writeU8(x64GenContext, 0x0F); @@ -479,11 +477,11 @@ void x64Gen_xorps_xmmReg_mem128Reg64(x64GenContext_t* x64GenContext, sint32 xmmR void x64Gen_andpd_xmmReg_memReg128(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32) { // SSE2 - if (memRegister == REG_NONE) + if (memRegister == X86_REG_NONE) { assert_dbg(); } - else if (memRegister == REG_R14) + else if (memRegister == X86_REG_R14) { x64Gen_writeU8(x64GenContext, 0x66); x64Gen_writeU8(x64GenContext, (xmmRegister < 8) ? 0x41 : 0x45); @@ -502,7 +500,7 @@ void x64Gen_andps_xmmReg_mem128Reg64(x64GenContext_t* x64GenContext, sint32 xmmR { // SSE2 // and xmm register with 128 bit value from memory - if( memReg == REG_R15 ) + if( memReg == X86_REG_R15 ) { x64Gen_genSSEVEXPrefix1(x64GenContext, xmmRegisterDest, true); // todo: should be x64Gen_genSSEVEXPrefix2() with memReg? x64Gen_writeU8(x64GenContext, 0x0F); @@ -528,7 +526,7 @@ void x64Gen_pcmpeqd_xmmReg_mem128Reg64(x64GenContext_t* x64GenContext, sint32 xm { // SSE2 // doubleword integer compare - if( memReg == REG_R15 ) + if( memReg == X86_REG_R15 ) { x64Gen_writeU8(x64GenContext, 0x66); x64Gen_genSSEVEXPrefix1(x64GenContext, xmmRegisterDest, true); @@ -610,7 +608,7 @@ void x64Gen_cvtpi2pd_xmmReg_mem64Reg64(x64GenContext_t* x64GenContext, sint32 xm { // SSE2 // converts two signed 32bit integers to two doubles - if( memReg == REG_RSP ) + if( memReg == X86_REG_RSP ) { x64Gen_writeU8(x64GenContext, 0x66); x64Gen_genSSEVEXPrefix1(x64GenContext, xmmRegisterDest, false); @@ -684,7 +682,7 @@ void x64Gen_rcpss_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegist void x64Gen_mulss_xmmReg_memReg64(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32) { // SSE2 - if( memRegister == REG_NONE ) + if( memRegister == X86_REG_NONE ) { assert_dbg(); } diff --git a/src/Cafe/HW/Espresso/Recompiler/x64Emit.hpp b/src/Cafe/HW/Espresso/Recompiler/BackendX64/X64Emit.hpp similarity index 99% rename from src/Cafe/HW/Espresso/Recompiler/x64Emit.hpp rename to src/Cafe/HW/Espresso/Recompiler/BackendX64/X64Emit.hpp index e936f1d8..b4021931 100644 --- a/src/Cafe/HW/Espresso/Recompiler/x64Emit.hpp +++ b/src/Cafe/HW/Espresso/Recompiler/BackendX64/X64Emit.hpp @@ -203,7 +203,6 @@ template<class opcodeBytes, typename TA, typename TB> void _x64Gen_writeMODRM_internal(x64GenContext_t* x64GenContext, TA opA, TB opB) { static_assert(TA::getType() == MODRM_OPR_TYPE::REG); - x64Gen_checkBuffer(x64GenContext); // REX prefix // 0100 WRXB if constexpr (TA::getType() == MODRM_OPR_TYPE::REG && TB::getType() == MODRM_OPR_TYPE::REG) diff --git a/src/Cafe/HW/Espresso/Recompiler/BackendX64/x86Emitter.h b/src/Cafe/HW/Espresso/Recompiler/BackendX64/x86Emitter.h new file mode 100644 index 00000000..eae3835d --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/BackendX64/x86Emitter.h @@ -0,0 +1,4335 @@ +#pragma once + +// x86-64 assembler/emitter +// auto generated. Do not edit this file manually + +typedef unsigned long long u64; +typedef unsigned int u32; +typedef unsigned short u16; +typedef unsigned char u8; +typedef signed long long s64; +typedef signed int s32; +typedef signed short s16; +typedef signed char s8; + +enum X86Reg : sint8 +{ + X86_REG_NONE = -1, + X86_REG_EAX = 0, + X86_REG_ECX = 1, + X86_REG_EDX = 2, + X86_REG_EBX = 3, + X86_REG_ESP = 4, + X86_REG_EBP = 5, + X86_REG_ESI = 6, + X86_REG_EDI = 7, + X86_REG_R8D = 8, + X86_REG_R9D = 9, + X86_REG_R10D = 10, + X86_REG_R11D = 11, + X86_REG_R12D = 12, + X86_REG_R13D = 13, + X86_REG_R14D = 14, + X86_REG_R15D = 15, + X86_REG_RAX = 0, + X86_REG_RCX = 1, + X86_REG_RDX = 2, + X86_REG_RBX = 3, + X86_REG_RSP = 4, + X86_REG_RBP = 5, + X86_REG_RSI = 6, + X86_REG_RDI = 7, + X86_REG_R8 = 8, + X86_REG_R9 = 9, + X86_REG_R10 = 10, + X86_REG_R11 = 11, + X86_REG_R12 = 12, + X86_REG_R13 = 13, + X86_REG_R14 = 14, + X86_REG_R15 = 15 +}; + +enum X86Cond : u8 +{ + X86_CONDITION_O = 0, + X86_CONDITION_NO = 1, + X86_CONDITION_B = 2, + X86_CONDITION_NB = 3, + X86_CONDITION_Z = 4, + X86_CONDITION_NZ = 5, + X86_CONDITION_BE = 6, + X86_CONDITION_NBE = 7, + X86_CONDITION_S = 8, + X86_CONDITION_NS = 9, + X86_CONDITION_PE = 10, + X86_CONDITION_PO = 11, + X86_CONDITION_L = 12, + X86_CONDITION_NL = 13, + X86_CONDITION_LE = 14, + X86_CONDITION_NLE = 15 +}; +class x86Assembler64 +{ +private: + std::vector<u8> m_buffer; + +public: + u8* GetBufferPtr() { return m_buffer.data(); }; + std::span<u8> GetBuffer() { return m_buffer; }; + u32 GetWriteIndex() { return (u32)m_buffer.size(); }; + void _emitU8(u8 v) { m_buffer.emplace_back(v); }; + void _emitU16(u16 v) { size_t writeIdx = m_buffer.size(); m_buffer.resize(writeIdx + 2); *(u16*)(m_buffer.data() + writeIdx) = v; }; + void _emitU32(u32 v) { size_t writeIdx = m_buffer.size(); m_buffer.resize(writeIdx + 4); *(u32*)(m_buffer.data() + writeIdx) = v; }; + void _emitU64(u64 v) { size_t writeIdx = m_buffer.size(); m_buffer.resize(writeIdx + 8); *(u64*)(m_buffer.data() + writeIdx) = v; }; + using GPR64 = X86Reg; + using GPR32 = X86Reg; + using GPR8_REX = X86Reg; + void LockPrefix() { _emitU8(0xF0); }; + void ADD_bb(GPR8_REX dst, GPR8_REX src) + { + if ((src >= 4) || (dst >= 4)) + { + _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); + } + _emitU8(0x00); + _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); + } + void ADD_bb_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR8_REX src) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((src >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((src >= 4) || (memReg & 8)) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x00); + _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void ADD_bb_r(GPR8_REX dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((dst >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((dst >= 4) || (memReg & 8)) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x02); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void ADD_dd(GPR32 dst, GPR32 src) + { + if (((src & 8) != 0) || ((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); + } + _emitU8(0x01); + _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); + } + void ADD_qq(GPR64 dst, GPR64 src) + { + _emitU8(0x48 | ((dst & 8) >> 3) | ((src & 8) >> 1)); + _emitU8(0x01); + _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); + } + void ADD_dd_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR32 src) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((src & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((src & 8) || (memReg & 8)) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x01); + _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void ADD_qq_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR64 src) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x01); + _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void ADD_dd_r(GPR32 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((dst & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((dst & 8) || (memReg & 8)) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x03); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void ADD_qq_r(GPR64 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x03); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void OR_bb(GPR8_REX dst, GPR8_REX src) + { + if ((src >= 4) || (dst >= 4)) + { + _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); + } + _emitU8(0x08); + _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); + } + void OR_bb_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR8_REX src) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((src >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((src >= 4) || (memReg & 8)) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x08); + _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void OR_bb_r(GPR8_REX dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((dst >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((dst >= 4) || (memReg & 8)) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x0a); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void OR_dd(GPR32 dst, GPR32 src) + { + if (((src & 8) != 0) || ((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); + } + _emitU8(0x09); + _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); + } + void OR_qq(GPR64 dst, GPR64 src) + { + _emitU8(0x48 | ((dst & 8) >> 3) | ((src & 8) >> 1)); + _emitU8(0x09); + _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); + } + void OR_dd_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR32 src) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((src & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((src & 8) || (memReg & 8)) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x09); + _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void OR_qq_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR64 src) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x09); + _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void OR_dd_r(GPR32 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((dst & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((dst & 8) || (memReg & 8)) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x0b); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void OR_qq_r(GPR64 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x0b); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void ADC_bb(GPR8_REX dst, GPR8_REX src) + { + if ((src >= 4) || (dst >= 4)) + { + _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); + } + _emitU8(0x10); + _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); + } + void ADC_bb_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR8_REX src) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((src >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((src >= 4) || (memReg & 8)) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x10); + _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void ADC_bb_r(GPR8_REX dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((dst >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((dst >= 4) || (memReg & 8)) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x12); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void ADC_dd(GPR32 dst, GPR32 src) + { + if (((src & 8) != 0) || ((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); + } + _emitU8(0x11); + _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); + } + void ADC_qq(GPR64 dst, GPR64 src) + { + _emitU8(0x48 | ((dst & 8) >> 3) | ((src & 8) >> 1)); + _emitU8(0x11); + _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); + } + void ADC_dd_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR32 src) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((src & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((src & 8) || (memReg & 8)) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x11); + _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void ADC_qq_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR64 src) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x11); + _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void ADC_dd_r(GPR32 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((dst & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((dst & 8) || (memReg & 8)) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x13); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void ADC_qq_r(GPR64 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x13); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void SBB_bb(GPR8_REX dst, GPR8_REX src) + { + if ((src >= 4) || (dst >= 4)) + { + _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); + } + _emitU8(0x18); + _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); + } + void SBB_bb_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR8_REX src) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((src >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((src >= 4) || (memReg & 8)) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x18); + _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void SBB_bb_r(GPR8_REX dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((dst >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((dst >= 4) || (memReg & 8)) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x1a); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void SBB_dd(GPR32 dst, GPR32 src) + { + if (((src & 8) != 0) || ((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); + } + _emitU8(0x19); + _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); + } + void SBB_qq(GPR64 dst, GPR64 src) + { + _emitU8(0x48 | ((dst & 8) >> 3) | ((src & 8) >> 1)); + _emitU8(0x19); + _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); + } + void SBB_dd_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR32 src) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((src & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((src & 8) || (memReg & 8)) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x19); + _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void SBB_qq_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR64 src) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x19); + _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void SBB_dd_r(GPR32 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((dst & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((dst & 8) || (memReg & 8)) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x1b); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void SBB_qq_r(GPR64 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x1b); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void AND_bb(GPR8_REX dst, GPR8_REX src) + { + if ((src >= 4) || (dst >= 4)) + { + _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); + } + _emitU8(0x20); + _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); + } + void AND_bb_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR8_REX src) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((src >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((src >= 4) || (memReg & 8)) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x20); + _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void AND_bb_r(GPR8_REX dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((dst >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((dst >= 4) || (memReg & 8)) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x22); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void AND_dd(GPR32 dst, GPR32 src) + { + if (((src & 8) != 0) || ((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); + } + _emitU8(0x21); + _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); + } + void AND_qq(GPR64 dst, GPR64 src) + { + _emitU8(0x48 | ((dst & 8) >> 3) | ((src & 8) >> 1)); + _emitU8(0x21); + _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); + } + void AND_dd_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR32 src) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((src & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((src & 8) || (memReg & 8)) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x21); + _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void AND_qq_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR64 src) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x21); + _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void AND_dd_r(GPR32 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((dst & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((dst & 8) || (memReg & 8)) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x23); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void AND_qq_r(GPR64 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x23); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void SUB_bb(GPR8_REX dst, GPR8_REX src) + { + if ((src >= 4) || (dst >= 4)) + { + _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); + } + _emitU8(0x28); + _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); + } + void SUB_bb_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR8_REX src) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((src >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((src >= 4) || (memReg & 8)) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x28); + _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void SUB_bb_r(GPR8_REX dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((dst >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((dst >= 4) || (memReg & 8)) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x2a); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void SUB_dd(GPR32 dst, GPR32 src) + { + if (((src & 8) != 0) || ((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); + } + _emitU8(0x29); + _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); + } + void SUB_qq(GPR64 dst, GPR64 src) + { + _emitU8(0x48 | ((dst & 8) >> 3) | ((src & 8) >> 1)); + _emitU8(0x29); + _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); + } + void SUB_dd_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR32 src) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((src & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((src & 8) || (memReg & 8)) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x29); + _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void SUB_qq_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR64 src) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x29); + _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void SUB_dd_r(GPR32 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((dst & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((dst & 8) || (memReg & 8)) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x2b); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void SUB_qq_r(GPR64 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x2b); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void XOR_bb(GPR8_REX dst, GPR8_REX src) + { + if ((src >= 4) || (dst >= 4)) + { + _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); + } + _emitU8(0x30); + _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); + } + void XOR_bb_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR8_REX src) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((src >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((src >= 4) || (memReg & 8)) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x30); + _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void XOR_bb_r(GPR8_REX dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((dst >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((dst >= 4) || (memReg & 8)) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x32); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void XOR_dd(GPR32 dst, GPR32 src) + { + if (((src & 8) != 0) || ((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); + } + _emitU8(0x31); + _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); + } + void XOR_qq(GPR64 dst, GPR64 src) + { + _emitU8(0x48 | ((dst & 8) >> 3) | ((src & 8) >> 1)); + _emitU8(0x31); + _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); + } + void XOR_dd_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR32 src) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((src & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((src & 8) || (memReg & 8)) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x31); + _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void XOR_qq_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR64 src) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x31); + _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void XOR_dd_r(GPR32 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((dst & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((dst & 8) || (memReg & 8)) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x33); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void XOR_qq_r(GPR64 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x33); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void CMP_bb(GPR8_REX dst, GPR8_REX src) + { + if ((src >= 4) || (dst >= 4)) + { + _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); + } + _emitU8(0x38); + _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); + } + void CMP_bb_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR8_REX src) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((src >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((src >= 4) || (memReg & 8)) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x38); + _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void CMP_bb_r(GPR8_REX dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((dst >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((dst >= 4) || (memReg & 8)) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x3a); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void CMP_dd(GPR32 dst, GPR32 src) + { + if (((src & 8) != 0) || ((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); + } + _emitU8(0x39); + _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); + } + void CMP_qq(GPR64 dst, GPR64 src) + { + _emitU8(0x48 | ((dst & 8) >> 3) | ((src & 8) >> 1)); + _emitU8(0x39); + _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); + } + void CMP_dd_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR32 src) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((src & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((src & 8) || (memReg & 8)) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x39); + _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void CMP_qq_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR64 src) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x39); + _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void CMP_dd_r(GPR32 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((dst & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((dst & 8) || (memReg & 8)) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x3b); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void CMP_qq_r(GPR64 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x3b); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void ADD_di32(GPR32 dst, s32 imm) + { + if (((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3)); + } + _emitU8(0x81); + _emitU8((3 << 6) | ((0 & 7) << 3) | (dst & 7)); + _emitU32((u32)imm); + } + void ADD_qi32(GPR64 dst, s32 imm) + { + _emitU8(0x48 | ((dst & 8) >> 3)); + _emitU8(0x81); + _emitU8((3 << 6) | ((0 & 7) << 3) | (dst & 7)); + _emitU32((u32)imm); + } + void ADD_di32_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((memReg & 8)) + _emitU8(0x40 | ((memReg & 8) >> 1)); + } + _emitU8(0x81); + _emitU8((mod << 6) | ((0 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU32((u32)imm); + } + void ADD_qi32_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x81); + _emitU8((mod << 6) | ((0 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU32((u32)imm); + } + void OR_di32(GPR32 dst, s32 imm) + { + if (((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3)); + } + _emitU8(0x81); + _emitU8((3 << 6) | ((1 & 7) << 3) | (dst & 7)); + _emitU32((u32)imm); + } + void OR_qi32(GPR64 dst, s32 imm) + { + _emitU8(0x48 | ((dst & 8) >> 3)); + _emitU8(0x81); + _emitU8((3 << 6) | ((1 & 7) << 3) | (dst & 7)); + _emitU32((u32)imm); + } + void OR_di32_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((memReg & 8)) + _emitU8(0x40 | ((memReg & 8) >> 1)); + } + _emitU8(0x81); + _emitU8((mod << 6) | ((1 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU32((u32)imm); + } + void OR_qi32_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x81); + _emitU8((mod << 6) | ((1 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU32((u32)imm); + } + void ADC_di32(GPR32 dst, s32 imm) + { + if (((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3)); + } + _emitU8(0x81); + _emitU8((3 << 6) | ((2 & 7) << 3) | (dst & 7)); + _emitU32((u32)imm); + } + void ADC_qi32(GPR64 dst, s32 imm) + { + _emitU8(0x48 | ((dst & 8) >> 3)); + _emitU8(0x81); + _emitU8((3 << 6) | ((2 & 7) << 3) | (dst & 7)); + _emitU32((u32)imm); + } + void ADC_di32_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((memReg & 8)) + _emitU8(0x40 | ((memReg & 8) >> 1)); + } + _emitU8(0x81); + _emitU8((mod << 6) | ((2 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU32((u32)imm); + } + void ADC_qi32_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x81); + _emitU8((mod << 6) | ((2 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU32((u32)imm); + } + void SBB_di32(GPR32 dst, s32 imm) + { + if (((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3)); + } + _emitU8(0x81); + _emitU8((3 << 6) | ((3 & 7) << 3) | (dst & 7)); + _emitU32((u32)imm); + } + void SBB_qi32(GPR64 dst, s32 imm) + { + _emitU8(0x48 | ((dst & 8) >> 3)); + _emitU8(0x81); + _emitU8((3 << 6) | ((3 & 7) << 3) | (dst & 7)); + _emitU32((u32)imm); + } + void SBB_di32_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((memReg & 8)) + _emitU8(0x40 | ((memReg & 8) >> 1)); + } + _emitU8(0x81); + _emitU8((mod << 6) | ((3 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU32((u32)imm); + } + void SBB_qi32_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x81); + _emitU8((mod << 6) | ((3 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU32((u32)imm); + } + void AND_di32(GPR32 dst, s32 imm) + { + if (((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3)); + } + _emitU8(0x81); + _emitU8((3 << 6) | ((4 & 7) << 3) | (dst & 7)); + _emitU32((u32)imm); + } + void AND_qi32(GPR64 dst, s32 imm) + { + _emitU8(0x48 | ((dst & 8) >> 3)); + _emitU8(0x81); + _emitU8((3 << 6) | ((4 & 7) << 3) | (dst & 7)); + _emitU32((u32)imm); + } + void AND_di32_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((memReg & 8)) + _emitU8(0x40 | ((memReg & 8) >> 1)); + } + _emitU8(0x81); + _emitU8((mod << 6) | ((4 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU32((u32)imm); + } + void AND_qi32_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x81); + _emitU8((mod << 6) | ((4 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU32((u32)imm); + } + void SUB_di32(GPR32 dst, s32 imm) + { + if (((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3)); + } + _emitU8(0x81); + _emitU8((3 << 6) | ((5 & 7) << 3) | (dst & 7)); + _emitU32((u32)imm); + } + void SUB_qi32(GPR64 dst, s32 imm) + { + _emitU8(0x48 | ((dst & 8) >> 3)); + _emitU8(0x81); + _emitU8((3 << 6) | ((5 & 7) << 3) | (dst & 7)); + _emitU32((u32)imm); + } + void SUB_di32_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((memReg & 8)) + _emitU8(0x40 | ((memReg & 8) >> 1)); + } + _emitU8(0x81); + _emitU8((mod << 6) | ((5 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU32((u32)imm); + } + void SUB_qi32_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x81); + _emitU8((mod << 6) | ((5 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU32((u32)imm); + } + void XOR_di32(GPR32 dst, s32 imm) + { + if (((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3)); + } + _emitU8(0x81); + _emitU8((3 << 6) | ((6 & 7) << 3) | (dst & 7)); + _emitU32((u32)imm); + } + void XOR_qi32(GPR64 dst, s32 imm) + { + _emitU8(0x48 | ((dst & 8) >> 3)); + _emitU8(0x81); + _emitU8((3 << 6) | ((6 & 7) << 3) | (dst & 7)); + _emitU32((u32)imm); + } + void XOR_di32_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((memReg & 8)) + _emitU8(0x40 | ((memReg & 8) >> 1)); + } + _emitU8(0x81); + _emitU8((mod << 6) | ((6 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU32((u32)imm); + } + void XOR_qi32_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x81); + _emitU8((mod << 6) | ((6 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU32((u32)imm); + } + void CMP_di32(GPR32 dst, s32 imm) + { + if (((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3)); + } + _emitU8(0x81); + _emitU8((3 << 6) | ((7 & 7) << 3) | (dst & 7)); + _emitU32((u32)imm); + } + void CMP_qi32(GPR64 dst, s32 imm) + { + _emitU8(0x48 | ((dst & 8) >> 3)); + _emitU8(0x81); + _emitU8((3 << 6) | ((7 & 7) << 3) | (dst & 7)); + _emitU32((u32)imm); + } + void CMP_di32_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((memReg & 8)) + _emitU8(0x40 | ((memReg & 8) >> 1)); + } + _emitU8(0x81); + _emitU8((mod << 6) | ((7 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU32((u32)imm); + } + void CMP_qi32_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x81); + _emitU8((mod << 6) | ((7 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU32((u32)imm); + } + void ADD_di8(GPR32 dst, s8 imm) + { + if (((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3)); + } + _emitU8(0x83); + _emitU8((3 << 6) | ((0 & 7) << 3) | (dst & 7)); + _emitU8((u8)imm); + } + void ADD_qi8(GPR64 dst, s8 imm) + { + _emitU8(0x48 | ((dst & 8) >> 3)); + _emitU8(0x83); + _emitU8((3 << 6) | ((0 & 7) << 3) | (dst & 7)); + _emitU8((u8)imm); + } + void ADD_di8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((memReg & 8)) + _emitU8(0x40 | ((memReg & 8) >> 1)); + } + _emitU8(0x83); + _emitU8((mod << 6) | ((0 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU8((u8)imm); + } + void ADD_qi8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x83); + _emitU8((mod << 6) | ((0 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU8((u8)imm); + } + void OR_di8(GPR32 dst, s8 imm) + { + if (((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3)); + } + _emitU8(0x83); + _emitU8((3 << 6) | ((1 & 7) << 3) | (dst & 7)); + _emitU8((u8)imm); + } + void OR_qi8(GPR64 dst, s8 imm) + { + _emitU8(0x48 | ((dst & 8) >> 3)); + _emitU8(0x83); + _emitU8((3 << 6) | ((1 & 7) << 3) | (dst & 7)); + _emitU8((u8)imm); + } + void OR_di8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((memReg & 8)) + _emitU8(0x40 | ((memReg & 8) >> 1)); + } + _emitU8(0x83); + _emitU8((mod << 6) | ((1 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU8((u8)imm); + } + void OR_qi8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x83); + _emitU8((mod << 6) | ((1 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU8((u8)imm); + } + void ADC_di8(GPR32 dst, s8 imm) + { + if (((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3)); + } + _emitU8(0x83); + _emitU8((3 << 6) | ((2 & 7) << 3) | (dst & 7)); + _emitU8((u8)imm); + } + void ADC_qi8(GPR64 dst, s8 imm) + { + _emitU8(0x48 | ((dst & 8) >> 3)); + _emitU8(0x83); + _emitU8((3 << 6) | ((2 & 7) << 3) | (dst & 7)); + _emitU8((u8)imm); + } + void ADC_di8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((memReg & 8)) + _emitU8(0x40 | ((memReg & 8) >> 1)); + } + _emitU8(0x83); + _emitU8((mod << 6) | ((2 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU8((u8)imm); + } + void ADC_qi8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x83); + _emitU8((mod << 6) | ((2 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU8((u8)imm); + } + void SBB_di8(GPR32 dst, s8 imm) + { + if (((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3)); + } + _emitU8(0x83); + _emitU8((3 << 6) | ((3 & 7) << 3) | (dst & 7)); + _emitU8((u8)imm); + } + void SBB_qi8(GPR64 dst, s8 imm) + { + _emitU8(0x48 | ((dst & 8) >> 3)); + _emitU8(0x83); + _emitU8((3 << 6) | ((3 & 7) << 3) | (dst & 7)); + _emitU8((u8)imm); + } + void SBB_di8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((memReg & 8)) + _emitU8(0x40 | ((memReg & 8) >> 1)); + } + _emitU8(0x83); + _emitU8((mod << 6) | ((3 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU8((u8)imm); + } + void SBB_qi8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x83); + _emitU8((mod << 6) | ((3 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU8((u8)imm); + } + void AND_di8(GPR32 dst, s8 imm) + { + if (((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3)); + } + _emitU8(0x83); + _emitU8((3 << 6) | ((4 & 7) << 3) | (dst & 7)); + _emitU8((u8)imm); + } + void AND_qi8(GPR64 dst, s8 imm) + { + _emitU8(0x48 | ((dst & 8) >> 3)); + _emitU8(0x83); + _emitU8((3 << 6) | ((4 & 7) << 3) | (dst & 7)); + _emitU8((u8)imm); + } + void AND_di8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((memReg & 8)) + _emitU8(0x40 | ((memReg & 8) >> 1)); + } + _emitU8(0x83); + _emitU8((mod << 6) | ((4 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU8((u8)imm); + } + void AND_qi8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x83); + _emitU8((mod << 6) | ((4 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU8((u8)imm); + } + void SUB_di8(GPR32 dst, s8 imm) + { + if (((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3)); + } + _emitU8(0x83); + _emitU8((3 << 6) | ((5 & 7) << 3) | (dst & 7)); + _emitU8((u8)imm); + } + void SUB_qi8(GPR64 dst, s8 imm) + { + _emitU8(0x48 | ((dst & 8) >> 3)); + _emitU8(0x83); + _emitU8((3 << 6) | ((5 & 7) << 3) | (dst & 7)); + _emitU8((u8)imm); + } + void SUB_di8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((memReg & 8)) + _emitU8(0x40 | ((memReg & 8) >> 1)); + } + _emitU8(0x83); + _emitU8((mod << 6) | ((5 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU8((u8)imm); + } + void SUB_qi8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x83); + _emitU8((mod << 6) | ((5 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU8((u8)imm); + } + void XOR_di8(GPR32 dst, s8 imm) + { + if (((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3)); + } + _emitU8(0x83); + _emitU8((3 << 6) | ((6 & 7) << 3) | (dst & 7)); + _emitU8((u8)imm); + } + void XOR_qi8(GPR64 dst, s8 imm) + { + _emitU8(0x48 | ((dst & 8) >> 3)); + _emitU8(0x83); + _emitU8((3 << 6) | ((6 & 7) << 3) | (dst & 7)); + _emitU8((u8)imm); + } + void XOR_di8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((memReg & 8)) + _emitU8(0x40 | ((memReg & 8) >> 1)); + } + _emitU8(0x83); + _emitU8((mod << 6) | ((6 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU8((u8)imm); + } + void XOR_qi8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x83); + _emitU8((mod << 6) | ((6 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU8((u8)imm); + } + void CMP_di8(GPR32 dst, s8 imm) + { + if (((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3)); + } + _emitU8(0x83); + _emitU8((3 << 6) | ((7 & 7) << 3) | (dst & 7)); + _emitU8((u8)imm); + } + void CMP_qi8(GPR64 dst, s8 imm) + { + _emitU8(0x48 | ((dst & 8) >> 3)); + _emitU8(0x83); + _emitU8((3 << 6) | ((7 & 7) << 3) | (dst & 7)); + _emitU8((u8)imm); + } + void CMP_di8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((memReg & 8)) + _emitU8(0x40 | ((memReg & 8) >> 1)); + } + _emitU8(0x83); + _emitU8((mod << 6) | ((7 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU8((u8)imm); + } + void CMP_qi8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x83); + _emitU8((mod << 6) | ((7 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU8((u8)imm); + } + void TEST_bb(GPR8_REX dst, GPR8_REX src) + { + if ((src >= 4) || (dst >= 4)) + { + _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); + } + _emitU8(0x84); + _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); + } + void TEST_bb_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR8_REX src) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((src >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((src >= 4) || (memReg & 8)) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x84); + _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void TEST_dd(GPR32 dst, GPR32 src) + { + if (((src & 8) != 0) || ((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); + } + _emitU8(0x85); + _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); + } + void TEST_qq(GPR64 dst, GPR64 src) + { + _emitU8(0x48 | ((dst & 8) >> 3) | ((src & 8) >> 1)); + _emitU8(0x85); + _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); + } + void TEST_dd_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR32 src) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((src & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((src & 8) || (memReg & 8)) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x85); + _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void TEST_qq_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR64 src) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x85); + _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void XCHG_bb(GPR8_REX dst, GPR8_REX src) + { + if ((dst >= 4) || (src >= 4)) + { + _emitU8(0x40 | ((src & 8) >> 3) | ((dst & 8) >> 1)); + } + _emitU8(0x86); + _emitU8((3 << 6) | ((dst & 7) << 3) | (src & 7)); + } + void XCHG_bb_r(GPR8_REX dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((dst >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((dst >= 4) || (memReg & 8)) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x86); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void XCHG_dd(GPR32 dst, GPR32 src) + { + if (((dst & 8) != 0) || ((src & 8) != 0)) + { + _emitU8(0x40 | ((src & 8) >> 3) | ((dst & 8) >> 1)); + } + _emitU8(0x87); + _emitU8((3 << 6) | ((dst & 7) << 3) | (src & 7)); + } + void XCHG_qq(GPR64 dst, GPR64 src) + { + _emitU8(0x48 | ((src & 8) >> 3) | ((dst & 8) >> 1)); + _emitU8(0x87); + _emitU8((3 << 6) | ((dst & 7) << 3) | (src & 7)); + } + void XCHG_dd_r(GPR32 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((dst & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((dst & 8) || (memReg & 8)) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x87); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void XCHG_qq_r(GPR64 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x87); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void MOV_bb(GPR8_REX dst, GPR8_REX src) + { + if ((src >= 4) || (dst >= 4)) + { + _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); + } + _emitU8(0x88); + _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); + } + void MOV_bb_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR8_REX src) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((src >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((src >= 4) || (memReg & 8)) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x88); + _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void MOV_bb_r(GPR8_REX dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((dst >= 4) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((dst >= 4) || (memReg & 8)) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x8a); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void MOV_dd(GPR32 dst, GPR32 src) + { + if (((src & 8) != 0) || ((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); + } + _emitU8(0x89); + _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); + } + void MOV_qq(GPR64 dst, GPR64 src) + { + _emitU8(0x48 | ((dst & 8) >> 3) | ((src & 8) >> 1)); + _emitU8(0x89); + _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); + } + void MOV_dd_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR32 src) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((src & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((src & 8) || (memReg & 8)) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x89); + _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void MOV_qq_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR64 src) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x89); + _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void MOV_dd_r(GPR32 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((dst & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((dst & 8) || (memReg & 8)) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x8b); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void MOV_qq_r(GPR64 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x8b); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void MOV_di32(GPR32 dst, s32 imm) + { + if (((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3)); + } + _emitU8(0xb8 | ((dst) & 7)); + _emitU32((u32)imm); + } + void MOV_qi64(GPR64 dst, s64 imm) + { + _emitU8(0x48 | ((dst & 8) >> 3)); + _emitU8(0xb8 | ((dst) & 7)); + _emitU64((u64)imm); + } + void CALL_q(GPR64 dst) + { + if (((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3)); + } + _emitU8(0xff); + _emitU8((3 << 6) | ((2 & 7) << 3) | (dst & 7)); + } + void CALL_q_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((memReg & 8)) + _emitU8(0x40 | ((memReg & 8) >> 1)); + } + _emitU8(0xff); + _emitU8((mod << 6) | ((2 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void IMUL_ddi32(GPR32 dst, GPR32 src, s32 imm) + { + if (((dst & 8) != 0) || ((src & 8) != 0)) + { + _emitU8(0x40 | ((src & 8) >> 3) | ((dst & 8) >> 1)); + } + _emitU8(0x69); + _emitU8((3 << 6) | ((dst & 7) << 3) | (src & 7)); + _emitU32((u32)imm); + } + void IMUL_qqi32(GPR64 dst, GPR64 src, s32 imm) + { + _emitU8(0x48 | ((src & 8) >> 3) | ((dst & 8) >> 1)); + _emitU8(0x69); + _emitU8((3 << 6) | ((dst & 7) << 3) | (src & 7)); + _emitU32((u32)imm); + } + void IMUL_ddi32_r(GPR32 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((dst & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((dst & 8) || (memReg & 8)) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x69); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU32((u32)imm); + } + void IMUL_qqi32_r(GPR64 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s32 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x69); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU32((u32)imm); + } + void IMUL_ddi8(GPR32 dst, GPR32 src, s8 imm) + { + if (((dst & 8) != 0) || ((src & 8) != 0)) + { + _emitU8(0x40 | ((src & 8) >> 3) | ((dst & 8) >> 1)); + } + _emitU8(0x6b); + _emitU8((3 << 6) | ((dst & 7) << 3) | (src & 7)); + _emitU8((u8)imm); + } + void IMUL_qqi8(GPR64 dst, GPR64 src, s8 imm) + { + _emitU8(0x48 | ((src & 8) >> 3) | ((dst & 8) >> 1)); + _emitU8(0x6b); + _emitU8((3 << 6) | ((dst & 7) << 3) | (src & 7)); + _emitU8((u8)imm); + } + void IMUL_ddi8_r(GPR32 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((dst & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((dst & 8) || (memReg & 8)) + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x6b); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU8((u8)imm); + } + void IMUL_qqi8_r(GPR64 dst, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, s8 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((dst & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x6b); + _emitU8((mod << 6) | ((dst & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU8((u8)imm); + } + void SHL_b_CL(GPR8_REX dst) + { + if ((dst >= 4)) + { + _emitU8(0x40 | ((dst & 8) >> 3)); + } + _emitU8(0xd2); + _emitU8((3 << 6) | ((4 & 7) << 3) | (dst & 7)); + } + void SHL_b_CL_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((memReg & 8)) + _emitU8(0x40 | ((memReg & 8) >> 1)); + } + _emitU8(0xd2); + _emitU8((mod << 6) | ((4 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void SHR_b_CL(GPR8_REX dst) + { + if ((dst >= 4)) + { + _emitU8(0x40 | ((dst & 8) >> 3)); + } + _emitU8(0xd2); + _emitU8((3 << 6) | ((5 & 7) << 3) | (dst & 7)); + } + void SHR_b_CL_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((memReg & 8)) + _emitU8(0x40 | ((memReg & 8) >> 1)); + } + _emitU8(0xd2); + _emitU8((mod << 6) | ((5 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void SAR_b_CL(GPR8_REX dst) + { + if ((dst >= 4)) + { + _emitU8(0x40 | ((dst & 8) >> 3)); + } + _emitU8(0xd2); + _emitU8((3 << 6) | ((7 & 7) << 3) | (dst & 7)); + } + void SAR_b_CL_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((memReg & 8)) + _emitU8(0x40 | ((memReg & 8) >> 1)); + } + _emitU8(0xd2); + _emitU8((mod << 6) | ((7 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void SHL_d_CL(GPR32 dst) + { + if (((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3)); + } + _emitU8(0xd3); + _emitU8((3 << 6) | ((4 & 7) << 3) | (dst & 7)); + } + void SHL_q_CL(GPR64 dst) + { + _emitU8(0x48 | ((dst & 8) >> 3)); + _emitU8(0xd3); + _emitU8((3 << 6) | ((4 & 7) << 3) | (dst & 7)); + } + void SHL_d_CL_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((memReg & 8)) + _emitU8(0x40 | ((memReg & 8) >> 1)); + } + _emitU8(0xd3); + _emitU8((mod << 6) | ((4 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void SHL_q_CL_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0xd3); + _emitU8((mod << 6) | ((4 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void SHR_d_CL(GPR32 dst) + { + if (((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3)); + } + _emitU8(0xd3); + _emitU8((3 << 6) | ((5 & 7) << 3) | (dst & 7)); + } + void SHR_q_CL(GPR64 dst) + { + _emitU8(0x48 | ((dst & 8) >> 3)); + _emitU8(0xd3); + _emitU8((3 << 6) | ((5 & 7) << 3) | (dst & 7)); + } + void SHR_d_CL_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((memReg & 8)) + _emitU8(0x40 | ((memReg & 8) >> 1)); + } + _emitU8(0xd3); + _emitU8((mod << 6) | ((5 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void SHR_q_CL_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0xd3); + _emitU8((mod << 6) | ((5 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void SAR_d_CL(GPR32 dst) + { + if (((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3)); + } + _emitU8(0xd3); + _emitU8((3 << 6) | ((7 & 7) << 3) | (dst & 7)); + } + void SAR_q_CL(GPR64 dst) + { + _emitU8(0x48 | ((dst & 8) >> 3)); + _emitU8(0xd3); + _emitU8((3 << 6) | ((7 & 7) << 3) | (dst & 7)); + } + void SAR_d_CL_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((memReg & 8)) + _emitU8(0x40 | ((memReg & 8) >> 1)); + } + _emitU8(0xd3); + _emitU8((mod << 6) | ((7 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void SAR_q_CL_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0xd3); + _emitU8((mod << 6) | ((7 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void JMP_j32(s32 imm) + { + _emitU8(0xe9); + _emitU32((u32)imm); + } + void Jcc_j32(X86Cond cond, s32 imm) + { + _emitU8(0x0f); + _emitU8(0x80 | (u8)cond); + _emitU32((u32)imm); + } + void SETcc_b(X86Cond cond, GPR8_REX dst) + { + if ((dst >= 4)) + { + _emitU8(0x40 | ((dst & 8) >> 3)); + } + _emitU8(0x0f); + _emitU8(0x90 | (u8)cond); + _emitU8((3 << 6) | (dst & 7)); + } + void SETcc_b_l(X86Cond cond, GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((memReg & 8)) + _emitU8(0x40 | ((memReg & 8) >> 1)); + } + _emitU8(0x0f); + _emitU8(0x90); + _emitU8((mod << 6) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void CMPXCHG_dd(GPR32 dst, GPR32 src) + { + if (((src & 8) != 0) || ((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3) | ((src & 8) >> 1)); + } + _emitU8(0x0f); + _emitU8(0xb1); + _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); + } + void CMPXCHG_qq(GPR64 dst, GPR64 src) + { + _emitU8(0x48 | ((dst & 8) >> 3) | ((src & 8) >> 1)); + _emitU8(0x0f); + _emitU8(0xb1); + _emitU8((3 << 6) | ((src & 7) << 3) | (dst & 7)); + } + void CMPXCHG_dd_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR32 src) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((src & 8) || (memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((src & 8) || (memReg & 8)) + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1)); + } + _emitU8(0x0f); + _emitU8(0xb1); + _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void CMPXCHG_qq_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, GPR64 src) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((src & 8) >> 1) | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x0f); + _emitU8(0xb1); + _emitU8((mod << 6) | ((src & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + } + void BSWAP_d(GPR32 dst) + { + if (((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3)); + } + _emitU8(0x0f); + _emitU8(0xc8 | ((dst) & 7)); + } + void BSWAP_q(GPR64 dst) + { + _emitU8(0x48 | ((dst & 8) >> 3)); + _emitU8(0x0f); + _emitU8(0xc8 | ((dst) & 7)); + } + void BT_du8(GPR32 dst, u8 imm) + { + if (((dst & 8) != 0)) + { + _emitU8(0x40 | ((dst & 8) >> 3)); + } + _emitU8(0x0f); + _emitU8(0xba); + _emitU8((3 << 6) | ((4 & 7) << 3) | (dst & 7)); + _emitU8((u8)imm); + } + void BT_qu8(GPR64 dst, u8 imm) + { + _emitU8(0x48 | ((dst & 8) >> 3)); + _emitU8(0x0f); + _emitU8(0xba); + _emitU8((3 << 6) | ((4 & 7) << 3) | (dst & 7)); + _emitU8((u8)imm); + } + void BT_du8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, u8 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + if ((memReg & 8) || ((index != X86_REG_NONE) && (index & 8))) + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2)); + } + else + { + if ((memReg & 8)) + _emitU8(0x40 | ((memReg & 8) >> 1)); + } + _emitU8(0x0f); + _emitU8(0xba); + _emitU8((mod << 6) | ((4 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU8((u8)imm); + } + void BT_qu8_l(GPR64 memReg, sint32 offset, GPR64 index, uint8 scaler, u8 imm) + { + uint8 mod; + if (offset == 0 && (memReg & 7) != 5) mod = 0; + else if (offset == (s32)(s8)offset) mod = 1; + else mod = 2; + bool sib_use = (scaler != 0 && index != X86_REG_NONE); + if ((memReg & 7) == 4) + { + cemu_assert_debug(index == X86_REG_NONE); + index = memReg; + sib_use = true; + } + if (sib_use) + { + _emitU8(0x40 | ((memReg & 8) >> 3) | ((index & 8) >> 2) | 0x08); + } + else + { + _emitU8(0x40 | ((memReg & 8) >> 1) | 0x08); + } + _emitU8(0x0f); + _emitU8(0xba); + _emitU8((mod << 6) | ((4 & 7) << 3) | (sib_use ? 4 : (memReg & 7))); + if (sib_use) + { + _emitU8((0 << 6) | ((memReg & 7)) | ((index & 7) << 3)); + } + if (mod == 1) _emitU8((u8)offset); + else if (mod == 2) _emitU32((u32)offset); + _emitU8((u8)imm); + } +}; diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IML.h b/src/Cafe/HW/Espresso/Recompiler/IML/IML.h new file mode 100644 index 00000000..bc0c27c5 --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IML.h @@ -0,0 +1,16 @@ +#pragma once + +#include "IMLInstruction.h" +#include "IMLSegment.h" + +// optimizer passes +void IMLOptimizer_OptimizeDirectFloatCopies(struct ppcImlGenContext_t* ppcImlGenContext); +void IMLOptimizer_OptimizeDirectIntegerCopies(struct ppcImlGenContext_t* ppcImlGenContext); +void PPCRecompiler_optimizePSQLoadAndStore(struct ppcImlGenContext_t* ppcImlGenContext); + +void IMLOptimizer_StandardOptimizationPass(ppcImlGenContext_t& ppcImlGenContext); + +// debug +void IMLDebug_DisassembleInstruction(const IMLInstruction& inst, std::string& disassemblyLineOut); +void IMLDebug_DumpSegment(struct ppcImlGenContext_t* ctx, IMLSegment* imlSegment, bool printLivenessRangeInfo = false); +void IMLDebug_Dump(struct ppcImlGenContext_t* ppcImlGenContext, bool printLivenessRangeInfo = false); diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IMLAnalyzer.cpp b/src/Cafe/HW/Espresso/Recompiler/IML/IMLAnalyzer.cpp new file mode 100644 index 00000000..6ae4b591 --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IMLAnalyzer.cpp @@ -0,0 +1,5 @@ +#include "IML.h" +//#include "PPCRecompilerIml.h" +#include "util/helpers/fixedSizeList.h" + +#include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IMLDebug.cpp b/src/Cafe/HW/Espresso/Recompiler/IML/IMLDebug.cpp new file mode 100644 index 00000000..07fd4002 --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IMLDebug.cpp @@ -0,0 +1,528 @@ +#include "IML.h" +#include "IMLInstruction.h" +#include "IMLSegment.h" +#include "IMLRegisterAllocatorRanges.h" +#include "util/helpers/StringBuf.h" + +#include "../PPCRecompiler.h" + +const char* IMLDebug_GetOpcodeName(const IMLInstruction* iml) +{ + static char _tempOpcodename[32]; + uint32 op = iml->operation; + if (op == PPCREC_IML_OP_ASSIGN) + return "MOV"; + else if (op == PPCREC_IML_OP_ADD) + return "ADD"; + else if (op == PPCREC_IML_OP_ADD_WITH_CARRY) + return "ADC"; + else if (op == PPCREC_IML_OP_SUB) + return "SUB"; + else if (op == PPCREC_IML_OP_OR) + return "OR"; + else if (op == PPCREC_IML_OP_AND) + return "AND"; + else if (op == PPCREC_IML_OP_XOR) + return "XOR"; + else if (op == PPCREC_IML_OP_LEFT_SHIFT) + return "LSH"; + else if (op == PPCREC_IML_OP_RIGHT_SHIFT_U) + return "RSH"; + else if (op == PPCREC_IML_OP_RIGHT_SHIFT_S) + return "ARSH"; + else if (op == PPCREC_IML_OP_LEFT_ROTATE) + return "LROT"; + else if (op == PPCREC_IML_OP_MULTIPLY_SIGNED) + return "MULS"; + else if (op == PPCREC_IML_OP_DIVIDE_SIGNED) + return "DIVS"; + + sprintf(_tempOpcodename, "OP0%02x_T%d", iml->operation, iml->type); + return _tempOpcodename; +} + +std::string IMLDebug_GetRegName(IMLReg r) +{ + std::string regName; + uint32 regId = r.GetRegID(); + switch (r.GetRegFormat()) + { + case IMLRegFormat::F32: + regName.append("f"); + break; + case IMLRegFormat::F64: + regName.append("fd"); + break; + case IMLRegFormat::I32: + regName.append("i"); + break; + case IMLRegFormat::I64: + regName.append("r"); + break; + default: + DEBUG_BREAK; + } + regName.append(fmt::format("{}", regId)); + return regName; +} + +void IMLDebug_AppendRegisterParam(StringBuf& strOutput, IMLReg virtualRegister, bool isLast = false) +{ + strOutput.add(IMLDebug_GetRegName(virtualRegister)); + if (!isLast) + strOutput.add(", "); +} + +void IMLDebug_AppendS32Param(StringBuf& strOutput, sint32 val, bool isLast = false) +{ + if (val < 0) + { + strOutput.add("-"); + val = -val; + } + strOutput.addFmt("0x{:08x}", val); + if (!isLast) + strOutput.add(", "); +} + +void IMLDebug_PrintLivenessRangeInfo(StringBuf& currentLineText, IMLSegment* imlSegment, sint32 offset) +{ + // pad to 70 characters + sint32 index = currentLineText.getLen(); + while (index < 70) + { + currentLineText.add(" "); + index++; + } + raLivenessRange* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; + while (subrangeItr) + { + if (subrangeItr->interval.start.GetInstructionIndexEx() == offset) + { + if(subrangeItr->interval.start.IsInstructionIndex() && !subrangeItr->interval.start.IsOnInputEdge()) + currentLineText.add("."); + else + currentLineText.add("|"); + + currentLineText.addFmt("{:<4}", subrangeItr->GetVirtualRegister()); + } + else if (subrangeItr->interval.end.GetInstructionIndexEx() == offset) + { + if(subrangeItr->interval.end.IsInstructionIndex() && !subrangeItr->interval.end.IsOnOutputEdge()) + currentLineText.add("* "); + else + currentLineText.add("| "); + } + else if (subrangeItr->interval.ContainsInstructionIndexEx(offset)) + { + currentLineText.add("| "); + } + else + { + currentLineText.add(" "); + } + index += 5; + // next + subrangeItr = subrangeItr->link_allSegmentRanges.next; + } +} + +std::string IMLDebug_GetSegmentName(ppcImlGenContext_t* ctx, IMLSegment* seg) +{ + if (!ctx) + { + return "<NoNameWithoutCtx>"; + } + // find segment index + for (size_t i = 0; i < ctx->segmentList2.size(); i++) + { + if (ctx->segmentList2[i] == seg) + { + return fmt::format("Seg{:04x}", i); + } + } + return "<SegmentNotInCtx>"; +} + +std::string IMLDebug_GetConditionName(IMLCondition cond) +{ + switch (cond) + { + case IMLCondition::EQ: + return "EQ"; + case IMLCondition::NEQ: + return "NEQ"; + case IMLCondition::UNSIGNED_GT: + return "UGT"; + case IMLCondition::UNSIGNED_LT: + return "ULT"; + case IMLCondition::SIGNED_GT: + return "SGT"; + case IMLCondition::SIGNED_LT: + return "SLT"; + default: + cemu_assert_unimplemented(); + } + return "ukn"; +} + +void IMLDebug_DisassembleInstruction(const IMLInstruction& inst, std::string& disassemblyLineOut) +{ + const sint32 lineOffsetParameters = 10;//18; + + StringBuf strOutput(1024); + strOutput.reset(); + if (inst.type == PPCREC_IML_TYPE_R_NAME || inst.type == PPCREC_IML_TYPE_NAME_R) + { + if (inst.type == PPCREC_IML_TYPE_R_NAME) + strOutput.add("R_NAME"); + else + strOutput.add("NAME_R"); + while ((sint32)strOutput.getLen() < lineOffsetParameters) + strOutput.add(" "); + + if(inst.type == PPCREC_IML_TYPE_R_NAME) + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_name.regR); + + strOutput.add("name_"); + if (inst.op_r_name.name >= PPCREC_NAME_R0 && inst.op_r_name.name < (PPCREC_NAME_R0 + 999)) + { + strOutput.addFmt("r{}", inst.op_r_name.name - PPCREC_NAME_R0); + } + else if (inst.op_r_name.name >= PPCREC_NAME_FPR0 && inst.op_r_name.name < (PPCREC_NAME_FPR0 + 999)) + { + strOutput.addFmt("f{}", inst.op_r_name.name - PPCREC_NAME_FPR0); + } + else if (inst.op_r_name.name >= PPCREC_NAME_SPR0 && inst.op_r_name.name < (PPCREC_NAME_SPR0 + 999)) + { + strOutput.addFmt("spr{}", inst.op_r_name.name - PPCREC_NAME_SPR0); + } + else if (inst.op_r_name.name >= PPCREC_NAME_CR && inst.op_r_name.name <= PPCREC_NAME_CR_LAST) + strOutput.addFmt("cr{}", inst.op_r_name.name - PPCREC_NAME_CR); + else if (inst.op_r_name.name == PPCREC_NAME_XER_CA) + strOutput.add("xer.ca"); + else if (inst.op_r_name.name == PPCREC_NAME_XER_SO) + strOutput.add("xer.so"); + else if (inst.op_r_name.name == PPCREC_NAME_XER_OV) + strOutput.add("xer.ov"); + else if (inst.op_r_name.name == PPCREC_NAME_CPU_MEMRES_EA) + strOutput.add("cpuReservation.ea"); + else if (inst.op_r_name.name == PPCREC_NAME_CPU_MEMRES_VAL) + strOutput.add("cpuReservation.value"); + else + { + strOutput.addFmt("name_ukn{}", inst.op_r_name.name); + } + if (inst.type != PPCREC_IML_TYPE_R_NAME) + { + strOutput.add(", "); + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_name.regR, true); + } + + } + else if (inst.type == PPCREC_IML_TYPE_R_R) + { + strOutput.addFmt("{}", IMLDebug_GetOpcodeName(&inst)); + while ((sint32)strOutput.getLen() < lineOffsetParameters) + strOutput.add(" "); + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r.regR); + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r.regA, true); + } + else if (inst.type == PPCREC_IML_TYPE_R_R_R) + { + strOutput.addFmt("{}", IMLDebug_GetOpcodeName(&inst)); + while ((sint32)strOutput.getLen() < lineOffsetParameters) + strOutput.add(" "); + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r.regR); + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r.regA); + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r.regB, true); + } + else if (inst.type == PPCREC_IML_TYPE_R_R_R_CARRY) + { + strOutput.addFmt("{}", IMLDebug_GetOpcodeName(&inst)); + while ((sint32)strOutput.getLen() < lineOffsetParameters) + strOutput.add(" "); + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r_carry.regR); + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r_carry.regA); + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r_carry.regB); + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_r_carry.regCarry, true); + } + else if (inst.type == PPCREC_IML_TYPE_COMPARE) + { + strOutput.add("CMP "); + while ((sint32)strOutput.getLen() < lineOffsetParameters) + strOutput.add(" "); + IMLDebug_AppendRegisterParam(strOutput, inst.op_compare.regA); + IMLDebug_AppendRegisterParam(strOutput, inst.op_compare.regB); + strOutput.addFmt("{}", IMLDebug_GetConditionName(inst.op_compare.cond)); + strOutput.add(" -> "); + IMLDebug_AppendRegisterParam(strOutput, inst.op_compare.regR, true); + } + else if (inst.type == PPCREC_IML_TYPE_COMPARE_S32) + { + strOutput.add("CMP "); + while ((sint32)strOutput.getLen() < lineOffsetParameters) + strOutput.add(" "); + IMLDebug_AppendRegisterParam(strOutput, inst.op_compare_s32.regA); + strOutput.addFmt("{}", inst.op_compare_s32.immS32); + strOutput.addFmt(", {}", IMLDebug_GetConditionName(inst.op_compare_s32.cond)); + strOutput.add(" -> "); + IMLDebug_AppendRegisterParam(strOutput, inst.op_compare_s32.regR, true); + } + else if (inst.type == PPCREC_IML_TYPE_CONDITIONAL_JUMP) + { + strOutput.add("CJUMP "); + while ((sint32)strOutput.getLen() < lineOffsetParameters) + strOutput.add(" "); + IMLDebug_AppendRegisterParam(strOutput, inst.op_conditional_jump.registerBool, true); + if (!inst.op_conditional_jump.mustBeTrue) + strOutput.add("(inverted)"); + } + else if (inst.type == PPCREC_IML_TYPE_JUMP) + { + strOutput.add("JUMP"); + } + else if (inst.type == PPCREC_IML_TYPE_R_R_S32) + { + strOutput.addFmt("{}", IMLDebug_GetOpcodeName(&inst)); + while ((sint32)strOutput.getLen() < lineOffsetParameters) + strOutput.add(" "); + + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_s32.regR); + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_s32.regA); + IMLDebug_AppendS32Param(strOutput, inst.op_r_r_s32.immS32, true); + } + else if (inst.type == PPCREC_IML_TYPE_R_R_S32_CARRY) + { + strOutput.addFmt("{}", IMLDebug_GetOpcodeName(&inst)); + while ((sint32)strOutput.getLen() < lineOffsetParameters) + strOutput.add(" "); + + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_s32_carry.regR); + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_s32_carry.regA); + IMLDebug_AppendS32Param(strOutput, inst.op_r_r_s32_carry.immS32); + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_r_s32_carry.regCarry, true); + } + else if (inst.type == PPCREC_IML_TYPE_R_S32) + { + strOutput.addFmt("{}", IMLDebug_GetOpcodeName(&inst)); + while ((sint32)strOutput.getLen() < lineOffsetParameters) + strOutput.add(" "); + + IMLDebug_AppendRegisterParam(strOutput, inst.op_r_immS32.regR); + IMLDebug_AppendS32Param(strOutput, inst.op_r_immS32.immS32, true); + } + else if (inst.type == PPCREC_IML_TYPE_LOAD || inst.type == PPCREC_IML_TYPE_STORE || + inst.type == PPCREC_IML_TYPE_LOAD_INDEXED || inst.type == PPCREC_IML_TYPE_STORE_INDEXED) + { + if (inst.type == PPCREC_IML_TYPE_LOAD || inst.type == PPCREC_IML_TYPE_LOAD_INDEXED) + strOutput.add("LD_"); + else + strOutput.add("ST_"); + + if (inst.op_storeLoad.flags2.signExtend) + strOutput.add("S"); + else + strOutput.add("U"); + strOutput.addFmt("{}", inst.op_storeLoad.copyWidth); + + while ((sint32)strOutput.getLen() < lineOffsetParameters) + strOutput.add(" "); + + IMLDebug_AppendRegisterParam(strOutput, inst.op_storeLoad.registerData); + + if (inst.type == PPCREC_IML_TYPE_LOAD_INDEXED || inst.type == PPCREC_IML_TYPE_STORE_INDEXED) + strOutput.addFmt("[{}+{}]", IMLDebug_GetRegName(inst.op_storeLoad.registerMem), IMLDebug_GetRegName(inst.op_storeLoad.registerMem2)); + else + strOutput.addFmt("[{}+{}]", IMLDebug_GetRegName(inst.op_storeLoad.registerMem), inst.op_storeLoad.immS32); + } + else if (inst.type == PPCREC_IML_TYPE_ATOMIC_CMP_STORE) + { + strOutput.add("ATOMIC_ST_U32"); + + while ((sint32)strOutput.getLen() < lineOffsetParameters) + strOutput.add(" "); + + IMLDebug_AppendRegisterParam(strOutput, inst.op_atomic_compare_store.regEA); + IMLDebug_AppendRegisterParam(strOutput, inst.op_atomic_compare_store.regCompareValue); + IMLDebug_AppendRegisterParam(strOutput, inst.op_atomic_compare_store.regWriteValue); + IMLDebug_AppendRegisterParam(strOutput, inst.op_atomic_compare_store.regBoolOut, true); + } + else if (inst.type == PPCREC_IML_TYPE_NO_OP) + { + strOutput.add("NOP"); + } + else if (inst.type == PPCREC_IML_TYPE_MACRO) + { + if (inst.operation == PPCREC_IML_MACRO_B_TO_REG) + { + strOutput.addFmt("MACRO B_TO_REG {}", IMLDebug_GetRegName(inst.op_macro.paramReg)); + } + else if (inst.operation == PPCREC_IML_MACRO_BL) + { + strOutput.addFmt("MACRO BL 0x{:08x} -> 0x{:08x} cycles (depr): {}", inst.op_macro.param, inst.op_macro.param2, (sint32)inst.op_macro.paramU16); + } + else if (inst.operation == PPCREC_IML_MACRO_B_FAR) + { + strOutput.addFmt("MACRO B_FAR 0x{:08x} -> 0x{:08x} cycles (depr): {}", inst.op_macro.param, inst.op_macro.param2, (sint32)inst.op_macro.paramU16); + } + else if (inst.operation == PPCREC_IML_MACRO_LEAVE) + { + strOutput.addFmt("MACRO LEAVE ppc: 0x{:08x}", inst.op_macro.param); + } + else if (inst.operation == PPCREC_IML_MACRO_HLE) + { + strOutput.addFmt("MACRO HLE ppcAddr: 0x{:08x} funcId: 0x{:08x}", inst.op_macro.param, inst.op_macro.param2); + } + else if (inst.operation == PPCREC_IML_MACRO_COUNT_CYCLES) + { + strOutput.addFmt("MACRO COUNT_CYCLES cycles: {}", inst.op_macro.param); + } + else + { + strOutput.addFmt("MACRO ukn operation {}", inst.operation); + } + } + else if (inst.type == PPCREC_IML_TYPE_FPR_LOAD) + { + strOutput.addFmt("{} = ", IMLDebug_GetRegName(inst.op_storeLoad.registerData)); + if (inst.op_storeLoad.flags2.signExtend) + strOutput.add("S"); + else + strOutput.add("U"); + strOutput.addFmt("{} [{}+{}] mode {}", inst.op_storeLoad.copyWidth / 8, IMLDebug_GetRegName(inst.op_storeLoad.registerMem), inst.op_storeLoad.immS32, inst.op_storeLoad.mode); + if (inst.op_storeLoad.flags2.notExpanded) + { + strOutput.addFmt(" <No expand>"); + } + } + else if (inst.type == PPCREC_IML_TYPE_FPR_STORE) + { + if (inst.op_storeLoad.flags2.signExtend) + strOutput.add("S"); + else + strOutput.add("U"); + strOutput.addFmt("{} [t{}+{}]", inst.op_storeLoad.copyWidth / 8, inst.op_storeLoad.registerMem.GetRegID(), inst.op_storeLoad.immS32); + strOutput.addFmt(" = {} mode {}", IMLDebug_GetRegName(inst.op_storeLoad.registerData), inst.op_storeLoad.mode); + } + else if (inst.type == PPCREC_IML_TYPE_FPR_R_R) + { + strOutput.addFmt("{:>6} ", IMLDebug_GetOpcodeName(&inst)); + strOutput.addFmt("{}, {}", IMLDebug_GetRegName(inst.op_fpr_r_r.regR), IMLDebug_GetRegName(inst.op_fpr_r_r.regA)); + } + else if (inst.type == PPCREC_IML_TYPE_FPR_R_R_R_R) + { + strOutput.addFmt("{:>6} ", IMLDebug_GetOpcodeName(&inst)); + strOutput.addFmt("{}, {}, {}, {}", IMLDebug_GetRegName(inst.op_fpr_r_r_r_r.regR), IMLDebug_GetRegName(inst.op_fpr_r_r_r_r.regA), IMLDebug_GetRegName(inst.op_fpr_r_r_r_r.regB), IMLDebug_GetRegName(inst.op_fpr_r_r_r_r.regC)); + } + else if (inst.type == PPCREC_IML_TYPE_FPR_R_R_R) + { + strOutput.addFmt("{:>6} ", IMLDebug_GetOpcodeName(&inst)); + strOutput.addFmt("{}, {}, {}", IMLDebug_GetRegName(inst.op_fpr_r_r_r.regR), IMLDebug_GetRegName(inst.op_fpr_r_r_r.regA), IMLDebug_GetRegName(inst.op_fpr_r_r_r.regB)); + } + else if (inst.type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK) + { + strOutput.addFmt("CYCLE_CHECK"); + } + else if (inst.type == PPCREC_IML_TYPE_X86_EFLAGS_JCC) + { + strOutput.addFmt("X86_JCC {}", IMLDebug_GetConditionName(inst.op_x86_eflags_jcc.cond)); + } + else + { + strOutput.addFmt("Unknown iml type {}", inst.type); + } + disassemblyLineOut.assign(strOutput.c_str()); +} + +void IMLDebug_DumpSegment(ppcImlGenContext_t* ctx, IMLSegment* imlSegment, bool printLivenessRangeInfo) +{ + StringBuf strOutput(4096); + + strOutput.addFmt("SEGMENT {} | PPC=0x{:08x} Loop-depth {}", IMLDebug_GetSegmentName(ctx, imlSegment), imlSegment->ppcAddress, imlSegment->loopDepth); + if (imlSegment->isEnterable) + { + strOutput.addFmt(" ENTERABLE (0x{:08x})", imlSegment->enterPPCAddress); + } + if (imlSegment->deadCodeEliminationHintSeg) + { + strOutput.addFmt(" InheritOverwrite: {}", IMLDebug_GetSegmentName(ctx, imlSegment->deadCodeEliminationHintSeg)); + } + cemuLog_log(LogType::Force, "{}", strOutput.c_str()); + + if (printLivenessRangeInfo) + { + strOutput.reset(); + IMLDebug_PrintLivenessRangeInfo(strOutput, imlSegment, RA_INTER_RANGE_START); + cemuLog_log(LogType::Force, "{}", strOutput.c_str()); + } + //debug_printf("\n"); + strOutput.reset(); + + std::string disassemblyLine; + for (sint32 i = 0; i < imlSegment->imlList.size(); i++) + { + const IMLInstruction& inst = imlSegment->imlList[i]; + // don't log NOP instructions + if (inst.type == PPCREC_IML_TYPE_NO_OP) + continue; + strOutput.reset(); + strOutput.addFmt("{:02x} ", i); + //cemuLog_log(LogType::Force, "{:02x} ", i); + disassemblyLine.clear(); + IMLDebug_DisassembleInstruction(inst, disassemblyLine); + strOutput.add(disassemblyLine); + if (printLivenessRangeInfo) + { + IMLDebug_PrintLivenessRangeInfo(strOutput, imlSegment, i); + } + cemuLog_log(LogType::Force, "{}", strOutput.c_str()); + } + // all ranges + if (printLivenessRangeInfo) + { + strOutput.reset(); + strOutput.add("Ranges-VirtReg "); + raLivenessRange* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; + while (subrangeItr) + { + strOutput.addFmt("v{:<4}", (uint32)subrangeItr->GetVirtualRegister()); + subrangeItr = subrangeItr->link_allSegmentRanges.next; + } + cemuLog_log(LogType::Force, "{}", strOutput.c_str()); + strOutput.reset(); + strOutput.add("Ranges-PhysReg "); + subrangeItr = imlSegment->raInfo.linkedList_allSubranges; + while (subrangeItr) + { + strOutput.addFmt("p{:<4}", subrangeItr->GetPhysicalRegister()); + subrangeItr = subrangeItr->link_allSegmentRanges.next; + } + cemuLog_log(LogType::Force, "{}", strOutput.c_str()); + } + // branch info + strOutput.reset(); + strOutput.add("Links from: "); + for (sint32 i = 0; i < imlSegment->list_prevSegments.size(); i++) + { + if (i) + strOutput.add(", "); + strOutput.addFmt("{}", IMLDebug_GetSegmentName(ctx, imlSegment->list_prevSegments[i]).c_str()); + } + cemuLog_log(LogType::Force, "{}", strOutput.c_str()); + if (imlSegment->nextSegmentBranchNotTaken) + cemuLog_log(LogType::Force, "BranchNotTaken: {}", IMLDebug_GetSegmentName(ctx, imlSegment->nextSegmentBranchNotTaken).c_str()); + if (imlSegment->nextSegmentBranchTaken) + cemuLog_log(LogType::Force, "BranchTaken: {}", IMLDebug_GetSegmentName(ctx, imlSegment->nextSegmentBranchTaken).c_str()); + if (imlSegment->nextSegmentIsUncertain) + cemuLog_log(LogType::Force, "Dynamic target"); +} + +void IMLDebug_Dump(ppcImlGenContext_t* ppcImlGenContext, bool printLivenessRangeInfo) +{ + for (size_t i = 0; i < ppcImlGenContext->segmentList2.size(); i++) + { + IMLDebug_DumpSegment(ppcImlGenContext, ppcImlGenContext->segmentList2[i], printLivenessRangeInfo); + cemuLog_log(LogType::Force, ""); + } +} diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.cpp b/src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.cpp new file mode 100644 index 00000000..cb481043 --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.cpp @@ -0,0 +1,669 @@ +#include "IMLInstruction.h" +#include "IML.h" + +#include "../PPCRecompiler.h" +#include "../PPCRecompilerIml.h" + +// return true if an instruction has side effects on top of just reading and writing registers +bool IMLInstruction::HasSideEffects() const +{ + bool hasSideEffects = true; + if(type == PPCREC_IML_TYPE_R_R || type == PPCREC_IML_TYPE_R_R_S32 || type == PPCREC_IML_TYPE_COMPARE || type == PPCREC_IML_TYPE_COMPARE_S32) + hasSideEffects = false; + // todo - add more cases + return hasSideEffects; +} + +void IMLInstruction::CheckRegisterUsage(IMLUsedRegisters* registersUsed) const +{ + registersUsed->readGPR1 = IMLREG_INVALID; + registersUsed->readGPR2 = IMLREG_INVALID; + registersUsed->readGPR3 = IMLREG_INVALID; + registersUsed->readGPR4 = IMLREG_INVALID; + registersUsed->writtenGPR1 = IMLREG_INVALID; + registersUsed->writtenGPR2 = IMLREG_INVALID; + if (type == PPCREC_IML_TYPE_R_NAME) + { + registersUsed->writtenGPR1 = op_r_name.regR; + } + else if (type == PPCREC_IML_TYPE_NAME_R) + { + registersUsed->readGPR1 = op_r_name.regR; + } + else if (type == PPCREC_IML_TYPE_R_R) + { + if (operation == PPCREC_IML_OP_X86_CMP) + { + // both operands are read only + registersUsed->readGPR1 = op_r_r.regR; + registersUsed->readGPR2 = op_r_r.regA; + } + else if ( + operation == PPCREC_IML_OP_ASSIGN || + operation == PPCREC_IML_OP_ENDIAN_SWAP || + operation == PPCREC_IML_OP_CNTLZW || + operation == PPCREC_IML_OP_NOT || + operation == PPCREC_IML_OP_NEG || + operation == PPCREC_IML_OP_ASSIGN_S16_TO_S32 || + operation == PPCREC_IML_OP_ASSIGN_S8_TO_S32) + { + // result is written, operand is read + registersUsed->writtenGPR1 = op_r_r.regR; + registersUsed->readGPR1 = op_r_r.regA; + } + else + cemu_assert_unimplemented(); + } + else if (type == PPCREC_IML_TYPE_R_S32) + { + cemu_assert_debug(operation != PPCREC_IML_OP_ADD && + operation != PPCREC_IML_OP_SUB && + operation != PPCREC_IML_OP_AND && + operation != PPCREC_IML_OP_OR && + operation != PPCREC_IML_OP_XOR); // deprecated, use r_r_s32 for these + + if (operation == PPCREC_IML_OP_LEFT_ROTATE) + { + // register operand is read and write + registersUsed->readGPR1 = op_r_immS32.regR; + registersUsed->writtenGPR1 = op_r_immS32.regR; + } + else if (operation == PPCREC_IML_OP_X86_CMP) + { + // register operand is read only + registersUsed->readGPR1 = op_r_immS32.regR; + } + else + { + // register operand is write only + // todo - use explicit lists, avoid default cases + registersUsed->writtenGPR1 = op_r_immS32.regR; + } + } + else if (type == PPCREC_IML_TYPE_R_R_S32) + { + registersUsed->writtenGPR1 = op_r_r_s32.regR; + registersUsed->readGPR1 = op_r_r_s32.regA; + } + else if (type == PPCREC_IML_TYPE_R_R_S32_CARRY) + { + registersUsed->writtenGPR1 = op_r_r_s32_carry.regR; + registersUsed->readGPR1 = op_r_r_s32_carry.regA; + // some operations read carry + switch (operation) + { + case PPCREC_IML_OP_ADD_WITH_CARRY: + registersUsed->readGPR2 = op_r_r_s32_carry.regCarry; + break; + case PPCREC_IML_OP_ADD: + break; + default: + cemu_assert_unimplemented(); + } + // carry is always written + registersUsed->writtenGPR2 = op_r_r_s32_carry.regCarry; + } + else if (type == PPCREC_IML_TYPE_R_R_R) + { + // in all cases result is written and other operands are read only + // with the exception of XOR, where if regA == regB then all bits are zeroed out. So we don't consider it a read + registersUsed->writtenGPR1 = op_r_r_r.regR; + if(!(operation == PPCREC_IML_OP_XOR && op_r_r_r.regA == op_r_r_r.regB)) + { + registersUsed->readGPR1 = op_r_r_r.regA; + registersUsed->readGPR2 = op_r_r_r.regB; + } + } + else if (type == PPCREC_IML_TYPE_R_R_R_CARRY) + { + registersUsed->writtenGPR1 = op_r_r_r_carry.regR; + registersUsed->readGPR1 = op_r_r_r_carry.regA; + registersUsed->readGPR2 = op_r_r_r_carry.regB; + // some operations read carry + switch (operation) + { + case PPCREC_IML_OP_ADD_WITH_CARRY: + registersUsed->readGPR3 = op_r_r_r_carry.regCarry; + break; + case PPCREC_IML_OP_ADD: + break; + default: + cemu_assert_unimplemented(); + } + // carry is always written + registersUsed->writtenGPR2 = op_r_r_r_carry.regCarry; + } + else if (type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK) + { + // no effect on registers + } + else if (type == PPCREC_IML_TYPE_NO_OP) + { + // no effect on registers + } + else if (type == PPCREC_IML_TYPE_MACRO) + { + if (operation == PPCREC_IML_MACRO_BL || operation == PPCREC_IML_MACRO_B_FAR || operation == PPCREC_IML_MACRO_LEAVE || operation == PPCREC_IML_MACRO_DEBUGBREAK || operation == PPCREC_IML_MACRO_COUNT_CYCLES || operation == PPCREC_IML_MACRO_HLE) + { + // no effect on registers + } + else if (operation == PPCREC_IML_MACRO_B_TO_REG) + { + cemu_assert_debug(op_macro.paramReg.IsValid()); + registersUsed->readGPR1 = op_macro.paramReg; + } + else + cemu_assert_unimplemented(); + } + else if (type == PPCREC_IML_TYPE_COMPARE) + { + registersUsed->readGPR1 = op_compare.regA; + registersUsed->readGPR2 = op_compare.regB; + registersUsed->writtenGPR1 = op_compare.regR; + } + else if (type == PPCREC_IML_TYPE_COMPARE_S32) + { + registersUsed->readGPR1 = op_compare_s32.regA; + registersUsed->writtenGPR1 = op_compare_s32.regR; + } + else if (type == PPCREC_IML_TYPE_CONDITIONAL_JUMP) + { + registersUsed->readGPR1 = op_conditional_jump.registerBool; + } + else if (type == PPCREC_IML_TYPE_JUMP) + { + // no registers affected + } + else if (type == PPCREC_IML_TYPE_LOAD) + { + registersUsed->writtenGPR1 = op_storeLoad.registerData; + if (op_storeLoad.registerMem.IsValid()) + registersUsed->readGPR1 = op_storeLoad.registerMem; + } + else if (type == PPCREC_IML_TYPE_LOAD_INDEXED) + { + registersUsed->writtenGPR1 = op_storeLoad.registerData; + if (op_storeLoad.registerMem.IsValid()) + registersUsed->readGPR1 = op_storeLoad.registerMem; + if (op_storeLoad.registerMem2.IsValid()) + registersUsed->readGPR2 = op_storeLoad.registerMem2; + } + else if (type == PPCREC_IML_TYPE_STORE) + { + registersUsed->readGPR1 = op_storeLoad.registerData; + if (op_storeLoad.registerMem.IsValid()) + registersUsed->readGPR2 = op_storeLoad.registerMem; + } + else if (type == PPCREC_IML_TYPE_STORE_INDEXED) + { + registersUsed->readGPR1 = op_storeLoad.registerData; + if (op_storeLoad.registerMem.IsValid()) + registersUsed->readGPR2 = op_storeLoad.registerMem; + if (op_storeLoad.registerMem2.IsValid()) + registersUsed->readGPR3 = op_storeLoad.registerMem2; + } + else if (type == PPCREC_IML_TYPE_ATOMIC_CMP_STORE) + { + registersUsed->readGPR1 = op_atomic_compare_store.regEA; + registersUsed->readGPR2 = op_atomic_compare_store.regCompareValue; + registersUsed->readGPR3 = op_atomic_compare_store.regWriteValue; + registersUsed->writtenGPR1 = op_atomic_compare_store.regBoolOut; + } + else if (type == PPCREC_IML_TYPE_CALL_IMM) + { + if (op_call_imm.regParam0.IsValid()) + registersUsed->readGPR1 = op_call_imm.regParam0; + if (op_call_imm.regParam1.IsValid()) + registersUsed->readGPR2 = op_call_imm.regParam1; + if (op_call_imm.regParam2.IsValid()) + registersUsed->readGPR3 = op_call_imm.regParam2; + registersUsed->writtenGPR1 = op_call_imm.regReturn; + } + else if (type == PPCREC_IML_TYPE_FPR_LOAD) + { + // fpr load operation + registersUsed->writtenGPR1 = op_storeLoad.registerData; + // address is in gpr register + if (op_storeLoad.registerMem.IsValid()) + registersUsed->readGPR1 = op_storeLoad.registerMem; + // determine partially written result + switch (op_storeLoad.mode) + { + case PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0: + case PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1: + cemu_assert_debug(op_storeLoad.registerGQR.IsValid()); + registersUsed->readGPR2 = op_storeLoad.registerGQR; + break; + case PPCREC_FPR_LD_MODE_DOUBLE_INTO_PS0: + // PS1 remains the same + cemu_assert_debug(op_storeLoad.registerGQR.IsInvalid()); + registersUsed->readGPR2 = op_storeLoad.registerData; + break; + case PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1: + case PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0_PS1: + case PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0: + case PPCREC_FPR_LD_MODE_PSQ_S16_PS0: + case PPCREC_FPR_LD_MODE_PSQ_S16_PS0_PS1: + case PPCREC_FPR_LD_MODE_PSQ_U16_PS0_PS1: + case PPCREC_FPR_LD_MODE_PSQ_U16_PS0: + case PPCREC_FPR_LD_MODE_PSQ_S8_PS0_PS1: + case PPCREC_FPR_LD_MODE_PSQ_U8_PS0_PS1: + case PPCREC_FPR_LD_MODE_PSQ_U8_PS0: + case PPCREC_FPR_LD_MODE_PSQ_S8_PS0: + cemu_assert_debug(op_storeLoad.registerGQR.IsInvalid()); + break; + default: + cemu_assert_unimplemented(); + } + } + else if (type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED) + { + // fpr load operation + registersUsed->writtenGPR1 = op_storeLoad.registerData; + // address is in gpr registers + if (op_storeLoad.registerMem.IsValid()) + registersUsed->readGPR1 = op_storeLoad.registerMem; + if (op_storeLoad.registerMem2.IsValid()) + registersUsed->readGPR2 = op_storeLoad.registerMem2; + // determine partially written result + switch (op_storeLoad.mode) + { + case PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0: + case PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1: + cemu_assert_debug(op_storeLoad.registerGQR.IsValid()); + registersUsed->readGPR3 = op_storeLoad.registerGQR; + break; + case PPCREC_FPR_LD_MODE_DOUBLE_INTO_PS0: + // PS1 remains the same + cemu_assert_debug(op_storeLoad.registerGQR.IsInvalid()); + registersUsed->readGPR3 = op_storeLoad.registerData; + break; + case PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1: + case PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0_PS1: + case PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0: + case PPCREC_FPR_LD_MODE_PSQ_S16_PS0: + case PPCREC_FPR_LD_MODE_PSQ_S16_PS0_PS1: + case PPCREC_FPR_LD_MODE_PSQ_U16_PS0_PS1: + case PPCREC_FPR_LD_MODE_PSQ_U16_PS0: + case PPCREC_FPR_LD_MODE_PSQ_S8_PS0_PS1: + case PPCREC_FPR_LD_MODE_PSQ_U8_PS0_PS1: + case PPCREC_FPR_LD_MODE_PSQ_U8_PS0: + cemu_assert_debug(op_storeLoad.registerGQR.IsInvalid()); + break; + default: + cemu_assert_unimplemented(); + } + } + else if (type == PPCREC_IML_TYPE_FPR_STORE) + { + // fpr store operation + registersUsed->readGPR1 = op_storeLoad.registerData; + if (op_storeLoad.registerMem.IsValid()) + registersUsed->readGPR2 = op_storeLoad.registerMem; + // PSQ generic stores also access GQR + switch (op_storeLoad.mode) + { + case PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0: + case PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1: + cemu_assert_debug(op_storeLoad.registerGQR.IsValid()); + registersUsed->readGPR3 = op_storeLoad.registerGQR; + break; + default: + cemu_assert_debug(op_storeLoad.registerGQR.IsInvalid()); + break; + } + } + else if (type == PPCREC_IML_TYPE_FPR_STORE_INDEXED) + { + // fpr store operation + registersUsed->readGPR1 = op_storeLoad.registerData; + // address is in gpr registers + if (op_storeLoad.registerMem.IsValid()) + registersUsed->readGPR2 = op_storeLoad.registerMem; + if (op_storeLoad.registerMem2.IsValid()) + registersUsed->readGPR3 = op_storeLoad.registerMem2; + // PSQ generic stores also access GQR + switch (op_storeLoad.mode) + { + case PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0: + case PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1: + cemu_assert_debug(op_storeLoad.registerGQR.IsValid()); + registersUsed->readGPR4 = op_storeLoad.registerGQR; + break; + default: + cemu_assert_debug(op_storeLoad.registerGQR.IsInvalid()); + break; + } + } + else if (type == PPCREC_IML_TYPE_FPR_R_R) + { + // fpr operation + if (operation == PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP || + operation == PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM_AND_TOP || + operation == PPCREC_IML_OP_FPR_COPY_BOTTOM_AND_TOP_SWAPPED || + operation == PPCREC_IML_OP_ASSIGN || + operation == PPCREC_IML_OP_FPR_NEGATE_PAIR || + operation == PPCREC_IML_OP_FPR_ABS_PAIR || + operation == PPCREC_IML_OP_FPR_FRES_PAIR || + operation == PPCREC_IML_OP_FPR_FRSQRTE_PAIR) + { + // operand read, result written + registersUsed->readGPR1 = op_fpr_r_r.regA; + registersUsed->writtenGPR1 = op_fpr_r_r.regR; + } + else if ( + operation == PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM || + operation == PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_TOP || + operation == PPCREC_IML_OP_FPR_COPY_TOP_TO_TOP || + operation == PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM || + operation == PPCREC_IML_OP_FPR_EXPAND_BOTTOM32_TO_BOTTOM64_AND_TOP64 || + operation == PPCREC_IML_OP_FPR_BOTTOM_FCTIWZ || + operation == PPCREC_IML_OP_FPR_BOTTOM_RECIPROCAL_SQRT + ) + { + // operand read, result read and (partially) written + registersUsed->readGPR1 = op_fpr_r_r.regA; + registersUsed->readGPR2 = op_fpr_r_r.regR; + registersUsed->writtenGPR1 = op_fpr_r_r.regR; + } + else if (operation == PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM || + operation == PPCREC_IML_OP_FPR_MULTIPLY_PAIR || + operation == PPCREC_IML_OP_FPR_DIVIDE_BOTTOM || + operation == PPCREC_IML_OP_FPR_DIVIDE_PAIR || + operation == PPCREC_IML_OP_FPR_ADD_BOTTOM || + operation == PPCREC_IML_OP_FPR_ADD_PAIR || + operation == PPCREC_IML_OP_FPR_SUB_PAIR || + operation == PPCREC_IML_OP_FPR_SUB_BOTTOM) + { + // operand read, result read and written + registersUsed->readGPR1 = op_fpr_r_r.regA; + registersUsed->readGPR2 = op_fpr_r_r.regR; + registersUsed->writtenGPR1 = op_fpr_r_r.regR; + + } + else if (operation == PPCREC_IML_OP_FPR_FCMPU_BOTTOM || + operation == PPCREC_IML_OP_FPR_FCMPU_TOP || + operation == PPCREC_IML_OP_FPR_FCMPO_BOTTOM) + { + // operand read, result read + registersUsed->readGPR1 = op_fpr_r_r.regA; + registersUsed->readGPR2 = op_fpr_r_r.regR; + } + else + cemu_assert_unimplemented(); + } + else if (type == PPCREC_IML_TYPE_FPR_R_R_R) + { + // fpr operation + registersUsed->readGPR1 = op_fpr_r_r_r.regA; + registersUsed->readGPR2 = op_fpr_r_r_r.regB; + registersUsed->writtenGPR1 = op_fpr_r_r_r.regR; + // handle partially written result + switch (operation) + { + case PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM: + case PPCREC_IML_OP_FPR_ADD_BOTTOM: + case PPCREC_IML_OP_FPR_SUB_BOTTOM: + registersUsed->readGPR3 = op_fpr_r_r_r.regR; + break; + case PPCREC_IML_OP_FPR_SUB_PAIR: + break; + default: + cemu_assert_unimplemented(); + } + } + else if (type == PPCREC_IML_TYPE_FPR_R_R_R_R) + { + // fpr operation + registersUsed->readGPR1 = op_fpr_r_r_r_r.regA; + registersUsed->readGPR2 = op_fpr_r_r_r_r.regB; + registersUsed->readGPR3 = op_fpr_r_r_r_r.regC; + registersUsed->writtenGPR1 = op_fpr_r_r_r_r.regR; + // handle partially written result + switch (operation) + { + case PPCREC_IML_OP_FPR_SELECT_BOTTOM: + registersUsed->readGPR4 = op_fpr_r_r_r_r.regR; + break; + case PPCREC_IML_OP_FPR_SUM0: + case PPCREC_IML_OP_FPR_SUM1: + case PPCREC_IML_OP_FPR_SELECT_PAIR: + break; + default: + cemu_assert_unimplemented(); + } + } + else if (type == PPCREC_IML_TYPE_FPR_R) + { + // fpr operation + if (operation == PPCREC_IML_OP_FPR_NEGATE_BOTTOM || + operation == PPCREC_IML_OP_FPR_ABS_BOTTOM || + operation == PPCREC_IML_OP_FPR_NEGATIVE_ABS_BOTTOM || + operation == PPCREC_IML_OP_FPR_EXPAND_BOTTOM32_TO_BOTTOM64_AND_TOP64 || + operation == PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_BOTTOM || + operation == PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_PAIR) + { + registersUsed->readGPR1 = op_fpr_r.regR; + registersUsed->writtenGPR1 = op_fpr_r.regR; + } + else + cemu_assert_unimplemented(); + } + else if (type == PPCREC_IML_TYPE_FPR_COMPARE) + { + registersUsed->writtenGPR1 = op_fpr_compare.regR; + registersUsed->readGPR1 = op_fpr_compare.regA; + registersUsed->readGPR2 = op_fpr_compare.regB; + } + else if (type == PPCREC_IML_TYPE_X86_EFLAGS_JCC) + { + // no registers read or written (except for the implicit eflags) + } + else + { + cemu_assert_unimplemented(); + } +} + +IMLReg replaceRegisterIdMultiple(IMLReg reg, const std::unordered_map<IMLRegID, IMLRegID>& translationTable) +{ + if (reg.IsInvalid()) + return reg; + const auto& it = translationTable.find(reg.GetRegID()); + cemu_assert_debug(it != translationTable.cend()); + IMLReg alteredReg = reg; + alteredReg.SetRegID(it->second); + return alteredReg; +} + +void IMLInstruction::RewriteGPR(const std::unordered_map<IMLRegID, IMLRegID>& translationTable) +{ + if (type == PPCREC_IML_TYPE_R_NAME) + { + op_r_name.regR = replaceRegisterIdMultiple(op_r_name.regR, translationTable); + } + else if (type == PPCREC_IML_TYPE_NAME_R) + { + op_r_name.regR = replaceRegisterIdMultiple(op_r_name.regR, translationTable); + } + else if (type == PPCREC_IML_TYPE_R_R) + { + op_r_r.regR = replaceRegisterIdMultiple(op_r_r.regR, translationTable); + op_r_r.regA = replaceRegisterIdMultiple(op_r_r.regA, translationTable); + } + else if (type == PPCREC_IML_TYPE_R_S32) + { + op_r_immS32.regR = replaceRegisterIdMultiple(op_r_immS32.regR, translationTable); + } + else if (type == PPCREC_IML_TYPE_R_R_S32) + { + op_r_r_s32.regR = replaceRegisterIdMultiple(op_r_r_s32.regR, translationTable); + op_r_r_s32.regA = replaceRegisterIdMultiple(op_r_r_s32.regA, translationTable); + } + else if (type == PPCREC_IML_TYPE_R_R_S32_CARRY) + { + op_r_r_s32_carry.regR = replaceRegisterIdMultiple(op_r_r_s32_carry.regR, translationTable); + op_r_r_s32_carry.regA = replaceRegisterIdMultiple(op_r_r_s32_carry.regA, translationTable); + op_r_r_s32_carry.regCarry = replaceRegisterIdMultiple(op_r_r_s32_carry.regCarry, translationTable); + } + else if (type == PPCREC_IML_TYPE_R_R_R) + { + op_r_r_r.regR = replaceRegisterIdMultiple(op_r_r_r.regR, translationTable); + op_r_r_r.regA = replaceRegisterIdMultiple(op_r_r_r.regA, translationTable); + op_r_r_r.regB = replaceRegisterIdMultiple(op_r_r_r.regB, translationTable); + } + else if (type == PPCREC_IML_TYPE_R_R_R_CARRY) + { + op_r_r_r_carry.regR = replaceRegisterIdMultiple(op_r_r_r_carry.regR, translationTable); + op_r_r_r_carry.regA = replaceRegisterIdMultiple(op_r_r_r_carry.regA, translationTable); + op_r_r_r_carry.regB = replaceRegisterIdMultiple(op_r_r_r_carry.regB, translationTable); + op_r_r_r_carry.regCarry = replaceRegisterIdMultiple(op_r_r_r_carry.regCarry, translationTable); + } + else if (type == PPCREC_IML_TYPE_COMPARE) + { + op_compare.regR = replaceRegisterIdMultiple(op_compare.regR, translationTable); + op_compare.regA = replaceRegisterIdMultiple(op_compare.regA, translationTable); + op_compare.regB = replaceRegisterIdMultiple(op_compare.regB, translationTable); + } + else if (type == PPCREC_IML_TYPE_COMPARE_S32) + { + op_compare_s32.regR = replaceRegisterIdMultiple(op_compare_s32.regR, translationTable); + op_compare_s32.regA = replaceRegisterIdMultiple(op_compare_s32.regA, translationTable); + } + else if (type == PPCREC_IML_TYPE_CONDITIONAL_JUMP) + { + op_conditional_jump.registerBool = replaceRegisterIdMultiple(op_conditional_jump.registerBool, translationTable); + } + else if (type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK || type == PPCREC_IML_TYPE_JUMP) + { + // no effect on registers + } + else if (type == PPCREC_IML_TYPE_NO_OP) + { + // no effect on registers + } + else if (type == PPCREC_IML_TYPE_MACRO) + { + if (operation == PPCREC_IML_MACRO_BL || operation == PPCREC_IML_MACRO_B_FAR || operation == PPCREC_IML_MACRO_LEAVE || operation == PPCREC_IML_MACRO_DEBUGBREAK || operation == PPCREC_IML_MACRO_HLE || operation == PPCREC_IML_MACRO_COUNT_CYCLES) + { + // no effect on registers + } + else if (operation == PPCREC_IML_MACRO_B_TO_REG) + { + op_macro.paramReg = replaceRegisterIdMultiple(op_macro.paramReg, translationTable); + } + else + { + cemu_assert_unimplemented(); + } + } + else if (type == PPCREC_IML_TYPE_LOAD) + { + op_storeLoad.registerData = replaceRegisterIdMultiple(op_storeLoad.registerData, translationTable); + if (op_storeLoad.registerMem.IsValid()) + { + op_storeLoad.registerMem = replaceRegisterIdMultiple(op_storeLoad.registerMem, translationTable); + } + } + else if (type == PPCREC_IML_TYPE_LOAD_INDEXED) + { + op_storeLoad.registerData = replaceRegisterIdMultiple(op_storeLoad.registerData, translationTable); + if (op_storeLoad.registerMem.IsValid()) + op_storeLoad.registerMem = replaceRegisterIdMultiple(op_storeLoad.registerMem, translationTable); + if (op_storeLoad.registerMem2.IsValid()) + op_storeLoad.registerMem2 = replaceRegisterIdMultiple(op_storeLoad.registerMem2, translationTable); + } + else if (type == PPCREC_IML_TYPE_STORE) + { + op_storeLoad.registerData = replaceRegisterIdMultiple(op_storeLoad.registerData, translationTable); + if (op_storeLoad.registerMem.IsValid()) + op_storeLoad.registerMem = replaceRegisterIdMultiple(op_storeLoad.registerMem, translationTable); + } + else if (type == PPCREC_IML_TYPE_STORE_INDEXED) + { + op_storeLoad.registerData = replaceRegisterIdMultiple(op_storeLoad.registerData, translationTable); + if (op_storeLoad.registerMem.IsValid()) + op_storeLoad.registerMem = replaceRegisterIdMultiple(op_storeLoad.registerMem, translationTable); + if (op_storeLoad.registerMem2.IsValid()) + op_storeLoad.registerMem2 = replaceRegisterIdMultiple(op_storeLoad.registerMem2, translationTable); + } + else if (type == PPCREC_IML_TYPE_ATOMIC_CMP_STORE) + { + op_atomic_compare_store.regEA = replaceRegisterIdMultiple(op_atomic_compare_store.regEA, translationTable); + op_atomic_compare_store.regCompareValue = replaceRegisterIdMultiple(op_atomic_compare_store.regCompareValue, translationTable); + op_atomic_compare_store.regWriteValue = replaceRegisterIdMultiple(op_atomic_compare_store.regWriteValue, translationTable); + op_atomic_compare_store.regBoolOut = replaceRegisterIdMultiple(op_atomic_compare_store.regBoolOut, translationTable); + } + else if (type == PPCREC_IML_TYPE_CALL_IMM) + { + op_call_imm.regReturn = replaceRegisterIdMultiple(op_call_imm.regReturn, translationTable); + if (op_call_imm.regParam0.IsValid()) + op_call_imm.regParam0 = replaceRegisterIdMultiple(op_call_imm.regParam0, translationTable); + if (op_call_imm.regParam1.IsValid()) + op_call_imm.regParam1 = replaceRegisterIdMultiple(op_call_imm.regParam1, translationTable); + if (op_call_imm.regParam2.IsValid()) + op_call_imm.regParam2 = replaceRegisterIdMultiple(op_call_imm.regParam2, translationTable); + } + else if (type == PPCREC_IML_TYPE_FPR_LOAD) + { + op_storeLoad.registerData = replaceRegisterIdMultiple(op_storeLoad.registerData, translationTable); + op_storeLoad.registerMem = replaceRegisterIdMultiple(op_storeLoad.registerMem, translationTable); + op_storeLoad.registerGQR = replaceRegisterIdMultiple(op_storeLoad.registerGQR, translationTable); + } + else if (type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED) + { + op_storeLoad.registerData = replaceRegisterIdMultiple(op_storeLoad.registerData, translationTable); + op_storeLoad.registerMem = replaceRegisterIdMultiple(op_storeLoad.registerMem, translationTable); + op_storeLoad.registerMem2 = replaceRegisterIdMultiple(op_storeLoad.registerMem2, translationTable); + op_storeLoad.registerGQR = replaceRegisterIdMultiple(op_storeLoad.registerGQR, translationTable); + } + else if (type == PPCREC_IML_TYPE_FPR_STORE) + { + op_storeLoad.registerData = replaceRegisterIdMultiple(op_storeLoad.registerData, translationTable); + op_storeLoad.registerMem = replaceRegisterIdMultiple(op_storeLoad.registerMem, translationTable); + op_storeLoad.registerGQR = replaceRegisterIdMultiple(op_storeLoad.registerGQR, translationTable); + } + else if (type == PPCREC_IML_TYPE_FPR_STORE_INDEXED) + { + op_storeLoad.registerData = replaceRegisterIdMultiple(op_storeLoad.registerData, translationTable); + op_storeLoad.registerMem = replaceRegisterIdMultiple(op_storeLoad.registerMem, translationTable); + op_storeLoad.registerMem2 = replaceRegisterIdMultiple(op_storeLoad.registerMem2, translationTable); + op_storeLoad.registerGQR = replaceRegisterIdMultiple(op_storeLoad.registerGQR, translationTable); + } + else if (type == PPCREC_IML_TYPE_FPR_R) + { + op_fpr_r.regR = replaceRegisterIdMultiple(op_fpr_r.regR, translationTable); + } + else if (type == PPCREC_IML_TYPE_FPR_R_R) + { + op_fpr_r_r.regR = replaceRegisterIdMultiple(op_fpr_r_r.regR, translationTable); + op_fpr_r_r.regA = replaceRegisterIdMultiple(op_fpr_r_r.regA, translationTable); + } + else if (type == PPCREC_IML_TYPE_FPR_R_R_R) + { + op_fpr_r_r_r.regR = replaceRegisterIdMultiple(op_fpr_r_r_r.regR, translationTable); + op_fpr_r_r_r.regA = replaceRegisterIdMultiple(op_fpr_r_r_r.regA, translationTable); + op_fpr_r_r_r.regB = replaceRegisterIdMultiple(op_fpr_r_r_r.regB, translationTable); + } + else if (type == PPCREC_IML_TYPE_FPR_R_R_R_R) + { + op_fpr_r_r_r_r.regR = replaceRegisterIdMultiple(op_fpr_r_r_r_r.regR, translationTable); + op_fpr_r_r_r_r.regA = replaceRegisterIdMultiple(op_fpr_r_r_r_r.regA, translationTable); + op_fpr_r_r_r_r.regB = replaceRegisterIdMultiple(op_fpr_r_r_r_r.regB, translationTable); + op_fpr_r_r_r_r.regC = replaceRegisterIdMultiple(op_fpr_r_r_r_r.regC, translationTable); + } + else if (type == PPCREC_IML_TYPE_FPR_COMPARE) + { + op_fpr_compare.regA = replaceRegisterIdMultiple(op_fpr_compare.regA, translationTable); + op_fpr_compare.regB = replaceRegisterIdMultiple(op_fpr_compare.regB, translationTable); + op_fpr_compare.regR = replaceRegisterIdMultiple(op_fpr_compare.regR, translationTable); + } + else if (type == PPCREC_IML_TYPE_X86_EFLAGS_JCC) + { + // no registers read or written (except for the implicit eflags) + } + else + { + cemu_assert_unimplemented(); + } +} diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.h b/src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.h new file mode 100644 index 00000000..3ba0a1af --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.h @@ -0,0 +1,785 @@ +#pragma once + +using IMLRegID = uint16; // 16 bit ID +using IMLPhysReg = sint32; // arbitrary value that is up to the architecture backend, usually this will be the register index. A value of -1 is reserved and means not assigned + +// format of IMLReg: +// 0-15 (16 bit) IMLRegID +// 19-23 (5 bit) Offset In elements, for SIMD registers +// 24-27 (4 bit) IMLRegFormat RegFormat +// 28-31 (4 bit) IMLRegFormat BaseFormat + +enum class IMLRegFormat : uint8 +{ + INVALID_FORMAT, + I64, + I32, + I16, + I8, + // I1 ? + F64, + F32, + TYPE_COUNT, +}; + +class IMLReg +{ +public: + IMLReg() + { + m_raw = 0; // 0 is invalid + } + + IMLReg(IMLRegFormat baseRegFormat, IMLRegFormat regFormat, uint8 viewOffset, IMLRegID regId) + { + m_raw = 0; + m_raw |= ((uint8)baseRegFormat << 28); + m_raw |= ((uint8)regFormat << 24); + m_raw |= (uint32)regId; + } + + IMLReg(IMLReg&& baseReg, IMLRegFormat viewFormat, uint8 viewOffset, IMLRegID regId) + { + DEBUG_BREAK; + //m_raw = 0; + //m_raw |= ((uint8)baseRegFormat << 28); + //m_raw |= ((uint8)viewFormat << 24); + //m_raw |= (uint32)regId; + } + + IMLReg(const IMLReg& other) : m_raw(other.m_raw) {} + + IMLRegFormat GetBaseFormat() const + { + return (IMLRegFormat)((m_raw >> 28) & 0xF); + } + + IMLRegFormat GetRegFormat() const + { + return (IMLRegFormat)((m_raw >> 24) & 0xF); + } + + IMLRegID GetRegID() const + { + cemu_assert_debug(GetBaseFormat() != IMLRegFormat::INVALID_FORMAT); + cemu_assert_debug(GetRegFormat() != IMLRegFormat::INVALID_FORMAT); + return (IMLRegID)(m_raw & 0xFFFF); + } + + void SetRegID(IMLRegID regId) + { + cemu_assert_debug(regId <= 0xFFFF); + m_raw &= ~0xFFFF; + m_raw |= (uint32)regId; + } + + bool IsInvalid() const + { + return GetBaseFormat() == IMLRegFormat::INVALID_FORMAT; + } + + bool IsValid() const + { + return GetBaseFormat() != IMLRegFormat::INVALID_FORMAT; + } + + bool IsValidAndSameRegID(IMLRegID regId) const + { + return IsValid() && GetRegID() == regId; + } + + // compare all fields + bool operator==(const IMLReg& other) const + { + return m_raw == other.m_raw; + } + +private: + uint32 m_raw; +}; + +static const IMLReg IMLREG_INVALID(IMLRegFormat::INVALID_FORMAT, IMLRegFormat::INVALID_FORMAT, 0, 0); +static const IMLRegID IMLRegID_INVALID(0xFFFF); + +using IMLName = uint32; + +enum +{ + PPCREC_IML_OP_ASSIGN, // '=' operator + PPCREC_IML_OP_ENDIAN_SWAP, // '=' operator with 32bit endian swap + PPCREC_IML_OP_MULTIPLY_SIGNED, // '*' operator (signed multiply) + PPCREC_IML_OP_MULTIPLY_HIGH_UNSIGNED, // unsigned 64bit multiply, store only high 32bit-word of result + PPCREC_IML_OP_MULTIPLY_HIGH_SIGNED, // signed 64bit multiply, store only high 32bit-word of result + PPCREC_IML_OP_DIVIDE_SIGNED, // '/' operator (signed divide) + PPCREC_IML_OP_DIVIDE_UNSIGNED, // '/' operator (unsigned divide) + + // binary operation + PPCREC_IML_OP_OR, // '|' operator + PPCREC_IML_OP_AND, // '&' operator + PPCREC_IML_OP_XOR, // '^' operator + PPCREC_IML_OP_LEFT_ROTATE, // left rotate operator + PPCREC_IML_OP_LEFT_SHIFT, // shift left operator + PPCREC_IML_OP_RIGHT_SHIFT_U, // right shift operator (unsigned) + PPCREC_IML_OP_RIGHT_SHIFT_S, // right shift operator (signed) + // ppc + PPCREC_IML_OP_SLW, // SLW (shift based on register by up to 63 bits) + PPCREC_IML_OP_SRW, // SRW (shift based on register by up to 63 bits) + PPCREC_IML_OP_CNTLZW, + // FPU + PPCREC_IML_OP_FPR_ADD_BOTTOM, + PPCREC_IML_OP_FPR_ADD_PAIR, + PPCREC_IML_OP_FPR_SUB_PAIR, + PPCREC_IML_OP_FPR_SUB_BOTTOM, + PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM, + PPCREC_IML_OP_FPR_MULTIPLY_PAIR, + PPCREC_IML_OP_FPR_DIVIDE_BOTTOM, + PPCREC_IML_OP_FPR_DIVIDE_PAIR, + PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, + PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM_AND_TOP, + PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, + PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_TOP, // leave bottom of destination untouched + PPCREC_IML_OP_FPR_COPY_TOP_TO_TOP, // leave bottom of destination untouched + PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM, // leave top of destination untouched + PPCREC_IML_OP_FPR_COPY_BOTTOM_AND_TOP_SWAPPED, + PPCREC_IML_OP_FPR_EXPAND_BOTTOM32_TO_BOTTOM64_AND_TOP64, // expand bottom f32 to f64 in bottom and top half + PPCREC_IML_OP_FPR_FCMPO_BOTTOM, // deprecated + PPCREC_IML_OP_FPR_FCMPU_BOTTOM, // deprecated + PPCREC_IML_OP_FPR_FCMPU_TOP, // deprecated + PPCREC_IML_OP_FPR_NEGATE_BOTTOM, + PPCREC_IML_OP_FPR_NEGATE_PAIR, + PPCREC_IML_OP_FPR_ABS_BOTTOM, // abs(fp0) + PPCREC_IML_OP_FPR_ABS_PAIR, + PPCREC_IML_OP_FPR_FRES_PAIR, // 1.0/fp approx (Espresso accuracy) + PPCREC_IML_OP_FPR_FRSQRTE_PAIR, // 1.0/sqrt(fp) approx (Espresso accuracy) + PPCREC_IML_OP_FPR_NEGATIVE_ABS_BOTTOM, // -abs(fp0) + PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_BOTTOM, // round 64bit double to 64bit double with 32bit float precision (in bottom half of xmm register) + PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_PAIR, // round two 64bit doubles to 64bit double with 32bit float precision + PPCREC_IML_OP_FPR_BOTTOM_RECIPROCAL_SQRT, + PPCREC_IML_OP_FPR_BOTTOM_FCTIWZ, + PPCREC_IML_OP_FPR_SELECT_BOTTOM, // selectively copy bottom value from operand B or C based on value in operand A + PPCREC_IML_OP_FPR_SELECT_PAIR, // selectively copy top/bottom from operand B or C based on value in top/bottom of operand A + // PS + PPCREC_IML_OP_FPR_SUM0, + PPCREC_IML_OP_FPR_SUM1, + + + // R_R_R only + + // R_R_S32 only + + // R_R_R + R_R_S32 + PPCREC_IML_OP_ADD, // also R_R_R_CARRY + PPCREC_IML_OP_SUB, + + // R_R only + PPCREC_IML_OP_NOT, + PPCREC_IML_OP_NEG, + PPCREC_IML_OP_ASSIGN_S16_TO_S32, + PPCREC_IML_OP_ASSIGN_S8_TO_S32, + + // R_R_R_carry + PPCREC_IML_OP_ADD_WITH_CARRY, // similar to ADD but also adds carry bit (0 or 1) + + // X86 extension + PPCREC_IML_OP_X86_CMP, // R_R and R_S32 + + PPCREC_IML_OP_INVALID +}; + +#define PPCREC_IML_OP_FPR_COPY_PAIR (PPCREC_IML_OP_ASSIGN) + +enum +{ + PPCREC_IML_MACRO_B_TO_REG, // branch to PPC address in register (used for BCCTR, BCLR) + + PPCREC_IML_MACRO_BL, // call to different function (can be within same function) + PPCREC_IML_MACRO_B_FAR, // branch to different function + PPCREC_IML_MACRO_COUNT_CYCLES, // decrease current remaining thread cycles by a certain amount + PPCREC_IML_MACRO_HLE, // HLE function call + PPCREC_IML_MACRO_LEAVE, // leaves recompiler and switches to interpeter + // debugging + PPCREC_IML_MACRO_DEBUGBREAK, // throws a debugbreak +}; + +enum class IMLCondition : uint8 +{ + EQ, + NEQ, + SIGNED_GT, + SIGNED_LT, + UNSIGNED_GT, + UNSIGNED_LT, + + // floating point conditions + UNORDERED_GT, // a > b, false if either is NaN + UNORDERED_LT, // a < b, false if either is NaN + UNORDERED_EQ, // a == b, false if either is NaN + UNORDERED_U, // unordered (true if either operand is NaN) + + ORDERED_GT, + ORDERED_LT, + ORDERED_EQ, + ORDERED_U +}; + +enum +{ + PPCREC_IML_TYPE_NONE, + PPCREC_IML_TYPE_NO_OP, // no-op instruction + PPCREC_IML_TYPE_R_R, // r* = (op) *r (can also be r* (op) *r) + PPCREC_IML_TYPE_R_R_R, // r* = r* (op) r* + PPCREC_IML_TYPE_R_R_R_CARRY, // r* = r* (op) r* (reads and/or updates carry) + PPCREC_IML_TYPE_R_R_S32, // r* = r* (op) s32* + PPCREC_IML_TYPE_R_R_S32_CARRY, // r* = r* (op) s32* (reads and/or updates carry) + PPCREC_IML_TYPE_LOAD, // r* = [r*+s32*] + PPCREC_IML_TYPE_LOAD_INDEXED, // r* = [r*+r*] + PPCREC_IML_TYPE_STORE, // [r*+s32*] = r* + PPCREC_IML_TYPE_STORE_INDEXED, // [r*+r*] = r* + PPCREC_IML_TYPE_R_NAME, // r* = name + PPCREC_IML_TYPE_NAME_R, // name* = r* + PPCREC_IML_TYPE_R_S32, // r* (op) imm + PPCREC_IML_TYPE_MACRO, + PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK, // jumps only if remaining thread cycles < 0 + + // conditions and branches + PPCREC_IML_TYPE_COMPARE, // r* = r* CMP[cond] r* + PPCREC_IML_TYPE_COMPARE_S32, // r* = r* CMP[cond] imm + PPCREC_IML_TYPE_JUMP, // jump always + PPCREC_IML_TYPE_CONDITIONAL_JUMP, // jump conditionally based on boolean value in register + + // atomic + PPCREC_IML_TYPE_ATOMIC_CMP_STORE, + + // function call + PPCREC_IML_TYPE_CALL_IMM, // call to fixed immediate address + + // FPR + PPCREC_IML_TYPE_FPR_LOAD, // r* = (bitdepth) [r*+s32*] (single or paired single mode) + PPCREC_IML_TYPE_FPR_LOAD_INDEXED, // r* = (bitdepth) [r*+r*] (single or paired single mode) + PPCREC_IML_TYPE_FPR_STORE, // (bitdepth) [r*+s32*] = r* (single or paired single mode) + PPCREC_IML_TYPE_FPR_STORE_INDEXED, // (bitdepth) [r*+r*] = r* (single or paired single mode) + PPCREC_IML_TYPE_FPR_R_R, + PPCREC_IML_TYPE_FPR_R_R_R, + PPCREC_IML_TYPE_FPR_R_R_R_R, + PPCREC_IML_TYPE_FPR_R, + + PPCREC_IML_TYPE_FPR_COMPARE, // r* = r* CMP[cond] r* + + // X86 specific + PPCREC_IML_TYPE_X86_EFLAGS_JCC, +}; + +enum // IMLName +{ + PPCREC_NAME_NONE, + PPCREC_NAME_TEMPORARY = 1000, + PPCREC_NAME_R0 = 2000, + PPCREC_NAME_SPR0 = 3000, + PPCREC_NAME_FPR0 = 4000, + PPCREC_NAME_TEMPORARY_FPR0 = 5000, // 0 to 7 + PPCREC_NAME_XER_CA = 6000, // carry bit from XER + PPCREC_NAME_XER_OV = 6001, // overflow bit from XER + PPCREC_NAME_XER_SO = 6002, // summary overflow bit from XER + PPCREC_NAME_CR = 7000, // CR register bits (31 to 0) + PPCREC_NAME_CR_LAST = PPCREC_NAME_CR+31, + PPCREC_NAME_CPU_MEMRES_EA = 8000, + PPCREC_NAME_CPU_MEMRES_VAL = 8001 +}; + +#define PPC_REC_INVALID_REGISTER 0xFF // deprecated. Use IMLREG_INVALID instead + +enum +{ + // fpr load + PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0, + PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1, + PPCREC_FPR_LD_MODE_DOUBLE_INTO_PS0, + PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0, + PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1, + PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0, + PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0_PS1, + PPCREC_FPR_LD_MODE_PSQ_S16_PS0, + PPCREC_FPR_LD_MODE_PSQ_S16_PS0_PS1, + PPCREC_FPR_LD_MODE_PSQ_U16_PS0, + PPCREC_FPR_LD_MODE_PSQ_U16_PS0_PS1, + PPCREC_FPR_LD_MODE_PSQ_S8_PS0, + PPCREC_FPR_LD_MODE_PSQ_S8_PS0_PS1, + PPCREC_FPR_LD_MODE_PSQ_U8_PS0, + PPCREC_FPR_LD_MODE_PSQ_U8_PS0_PS1, + // fpr store + PPCREC_FPR_ST_MODE_SINGLE_FROM_PS0, // store 1 single precision float from ps0 + PPCREC_FPR_ST_MODE_DOUBLE_FROM_PS0, // store 1 double precision float from ps0 + + PPCREC_FPR_ST_MODE_UI32_FROM_PS0, // store raw low-32bit of PS0 + + PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1, + PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0, + PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0_PS1, + PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0, + PPCREC_FPR_ST_MODE_PSQ_S8_PS0, + PPCREC_FPR_ST_MODE_PSQ_S8_PS0_PS1, + PPCREC_FPR_ST_MODE_PSQ_U8_PS0, + PPCREC_FPR_ST_MODE_PSQ_U8_PS0_PS1, + PPCREC_FPR_ST_MODE_PSQ_U16_PS0, + PPCREC_FPR_ST_MODE_PSQ_U16_PS0_PS1, + PPCREC_FPR_ST_MODE_PSQ_S16_PS0, + PPCREC_FPR_ST_MODE_PSQ_S16_PS0_PS1, +}; + +struct IMLUsedRegisters +{ + IMLUsedRegisters() {}; + + bool IsWrittenByRegId(IMLRegID regId) const + { + if (writtenGPR1.IsValid() && writtenGPR1.GetRegID() == regId) + return true; + if (writtenGPR2.IsValid() && writtenGPR2.GetRegID() == regId) + return true; + return false; + } + + bool IsBaseGPRWritten(IMLReg imlReg) const + { + cemu_assert_debug(imlReg.IsValid()); + auto regId = imlReg.GetRegID(); + return IsWrittenByRegId(regId); + } + + template<typename Fn> + void ForEachWrittenGPR(Fn F) const + { + if (writtenGPR1.IsValid()) + F(writtenGPR1); + if (writtenGPR2.IsValid()) + F(writtenGPR2); + } + + template<typename Fn> + void ForEachReadGPR(Fn F) const + { + if (readGPR1.IsValid()) + F(readGPR1); + if (readGPR2.IsValid()) + F(readGPR2); + if (readGPR3.IsValid()) + F(readGPR3); + if (readGPR4.IsValid()) + F(readGPR4); + } + + template<typename Fn> + void ForEachAccessedGPR(Fn F) const + { + // GPRs + if (readGPR1.IsValid()) + F(readGPR1, false); + if (readGPR2.IsValid()) + F(readGPR2, false); + if (readGPR3.IsValid()) + F(readGPR3, false); + if (readGPR4.IsValid()) + F(readGPR4, false); + if (writtenGPR1.IsValid()) + F(writtenGPR1, true); + if (writtenGPR2.IsValid()) + F(writtenGPR2, true); + } + + IMLReg readGPR1; + IMLReg readGPR2; + IMLReg readGPR3; + IMLReg readGPR4; + IMLReg writtenGPR1; + IMLReg writtenGPR2; +}; + +struct IMLInstruction +{ + IMLInstruction() {} + IMLInstruction(const IMLInstruction& other) + { + memcpy(this, &other, sizeof(IMLInstruction)); + } + + uint8 type; + uint8 operation; + union + { + struct + { + uint8 _padding[7]; + }padding; + struct + { + IMLReg regR; + IMLReg regA; + }op_r_r; + struct + { + IMLReg regR; + IMLReg regA; + IMLReg regB; + }op_r_r_r; + struct + { + IMLReg regR; + IMLReg regA; + IMLReg regB; + IMLReg regCarry; + }op_r_r_r_carry; + struct + { + IMLReg regR; + IMLReg regA; + sint32 immS32; + }op_r_r_s32; + struct + { + IMLReg regR; + IMLReg regA; + IMLReg regCarry; + sint32 immS32; + }op_r_r_s32_carry; + struct + { + IMLReg regR; + IMLName name; + }op_r_name; // alias op_name_r + struct + { + IMLReg regR; + sint32 immS32; + }op_r_immS32; + struct + { + uint32 param; + uint32 param2; + uint16 paramU16; + IMLReg paramReg; + }op_macro; + struct + { + IMLReg registerData; + IMLReg registerMem; + IMLReg registerMem2; + IMLReg registerGQR; + uint8 copyWidth; + struct + { + bool swapEndian : 1; + bool signExtend : 1; + bool notExpanded : 1; // for floats + }flags2; + uint8 mode; // transfer mode (copy width, ps0/ps1 behavior) + sint32 immS32; + }op_storeLoad; + struct + { + uintptr_t callAddress; + IMLReg regParam0; + IMLReg regParam1; + IMLReg regParam2; + IMLReg regReturn; + }op_call_imm; + struct + { + IMLReg regR; + IMLReg regA; + }op_fpr_r_r; + struct + { + IMLReg regR; + IMLReg regA; + IMLReg regB; + }op_fpr_r_r_r; + struct + { + IMLReg regR; + IMLReg regA; + IMLReg regB; + IMLReg regC; + }op_fpr_r_r_r_r; + struct + { + IMLReg regR; + }op_fpr_r; + struct + { + IMLReg regR; // stores the boolean result of the comparison + IMLReg regA; + IMLReg regB; + IMLCondition cond; + }op_fpr_compare; + struct + { + IMLReg regR; // stores the boolean result of the comparison + IMLReg regA; + IMLReg regB; + IMLCondition cond; + }op_compare; + struct + { + IMLReg regR; // stores the boolean result of the comparison + IMLReg regA; + sint32 immS32; + IMLCondition cond; + }op_compare_s32; + struct + { + IMLReg registerBool; + bool mustBeTrue; + }op_conditional_jump; + struct + { + IMLReg regEA; + IMLReg regCompareValue; + IMLReg regWriteValue; + IMLReg regBoolOut; + }op_atomic_compare_store; + // conditional operations (emitted if supported by target platform) + struct + { + // r_s32 + IMLReg regR; + sint32 immS32; + // condition + uint8 crRegisterIndex; + uint8 crBitIndex; + bool bitMustBeSet; + }op_conditional_r_s32; + // X86 specific + struct + { + IMLCondition cond; + bool invertedCondition; + }op_x86_eflags_jcc; + }; + + bool IsSuffixInstruction() const + { + if (type == PPCREC_IML_TYPE_MACRO && operation == PPCREC_IML_MACRO_BL || + type == PPCREC_IML_TYPE_MACRO && operation == PPCREC_IML_MACRO_B_FAR || + type == PPCREC_IML_TYPE_MACRO && operation == PPCREC_IML_MACRO_B_TO_REG || + type == PPCREC_IML_TYPE_MACRO && operation == PPCREC_IML_MACRO_LEAVE || + type == PPCREC_IML_TYPE_MACRO && operation == PPCREC_IML_MACRO_HLE || + type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK || + type == PPCREC_IML_TYPE_JUMP || + type == PPCREC_IML_TYPE_CONDITIONAL_JUMP || + type == PPCREC_IML_TYPE_X86_EFLAGS_JCC) + return true; + return false; + } + + // instruction setters + void make_no_op() + { + type = PPCREC_IML_TYPE_NO_OP; + operation = 0; + } + + void make_r_name(IMLReg regR, IMLName name) + { + cemu_assert_debug(regR.GetBaseFormat() == regR.GetRegFormat()); // for name load/store instructions the register must match the base format + type = PPCREC_IML_TYPE_R_NAME; + operation = PPCREC_IML_OP_ASSIGN; + op_r_name.regR = regR; + op_r_name.name = name; + } + + void make_name_r(IMLName name, IMLReg regR) + { + cemu_assert_debug(regR.GetBaseFormat() == regR.GetRegFormat()); // for name load/store instructions the register must match the base format + type = PPCREC_IML_TYPE_NAME_R; + operation = PPCREC_IML_OP_ASSIGN; + op_r_name.regR = regR; + op_r_name.name = name; + } + + void make_debugbreak(uint32 currentPPCAddress = 0) + { + make_macro(PPCREC_IML_MACRO_DEBUGBREAK, 0, currentPPCAddress, 0, IMLREG_INVALID); + } + + void make_macro(uint32 macroId, uint32 param, uint32 param2, uint16 paramU16, IMLReg regParam) + { + this->type = PPCREC_IML_TYPE_MACRO; + this->operation = macroId; + this->op_macro.param = param; + this->op_macro.param2 = param2; + this->op_macro.paramU16 = paramU16; + this->op_macro.paramReg = regParam; + } + + void make_cjump_cycle_check() + { + this->type = PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK; + this->operation = 0; + } + + void make_r_r(uint32 operation, IMLReg regR, IMLReg regA) + { + this->type = PPCREC_IML_TYPE_R_R; + this->operation = operation; + this->op_r_r.regR = regR; + this->op_r_r.regA = regA; + } + + void make_r_s32(uint32 operation, IMLReg regR, sint32 immS32) + { + this->type = PPCREC_IML_TYPE_R_S32; + this->operation = operation; + this->op_r_immS32.regR = regR; + this->op_r_immS32.immS32 = immS32; + } + + void make_r_r_r(uint32 operation, IMLReg regR, IMLReg regA, IMLReg regB) + { + this->type = PPCREC_IML_TYPE_R_R_R; + this->operation = operation; + this->op_r_r_r.regR = regR; + this->op_r_r_r.regA = regA; + this->op_r_r_r.regB = regB; + } + + void make_r_r_r_carry(uint32 operation, IMLReg regR, IMLReg regA, IMLReg regB, IMLReg regCarry) + { + this->type = PPCREC_IML_TYPE_R_R_R_CARRY; + this->operation = operation; + this->op_r_r_r_carry.regR = regR; + this->op_r_r_r_carry.regA = regA; + this->op_r_r_r_carry.regB = regB; + this->op_r_r_r_carry.regCarry = regCarry; + } + + void make_r_r_s32(uint32 operation, IMLReg regR, IMLReg regA, sint32 immS32) + { + this->type = PPCREC_IML_TYPE_R_R_S32; + this->operation = operation; + this->op_r_r_s32.regR = regR; + this->op_r_r_s32.regA = regA; + this->op_r_r_s32.immS32 = immS32; + } + + void make_r_r_s32_carry(uint32 operation, IMLReg regR, IMLReg regA, sint32 immS32, IMLReg regCarry) + { + this->type = PPCREC_IML_TYPE_R_R_S32_CARRY; + this->operation = operation; + this->op_r_r_s32_carry.regR = regR; + this->op_r_r_s32_carry.regA = regA; + this->op_r_r_s32_carry.immS32 = immS32; + this->op_r_r_s32_carry.regCarry = regCarry; + } + + void make_compare(IMLReg regA, IMLReg regB, IMLReg regR, IMLCondition cond) + { + this->type = PPCREC_IML_TYPE_COMPARE; + this->operation = PPCREC_IML_OP_INVALID; + this->op_compare.regR = regR; + this->op_compare.regA = regA; + this->op_compare.regB = regB; + this->op_compare.cond = cond; + } + + void make_compare_s32(IMLReg regA, sint32 immS32, IMLReg regR, IMLCondition cond) + { + this->type = PPCREC_IML_TYPE_COMPARE_S32; + this->operation = PPCREC_IML_OP_INVALID; + this->op_compare_s32.regR = regR; + this->op_compare_s32.regA = regA; + this->op_compare_s32.immS32 = immS32; + this->op_compare_s32.cond = cond; + } + + void make_conditional_jump(IMLReg regBool, bool mustBeTrue) + { + this->type = PPCREC_IML_TYPE_CONDITIONAL_JUMP; + this->operation = PPCREC_IML_OP_INVALID; + this->op_conditional_jump.registerBool = regBool; + this->op_conditional_jump.mustBeTrue = mustBeTrue; + } + + void make_jump() + { + this->type = PPCREC_IML_TYPE_JUMP; + this->operation = PPCREC_IML_OP_INVALID; + } + + // load from memory + void make_r_memory(IMLReg regD, IMLReg regMem, sint32 immS32, uint32 copyWidth, bool signExtend, bool switchEndian) + { + this->type = PPCREC_IML_TYPE_LOAD; + this->operation = 0; + this->op_storeLoad.registerData = regD; + this->op_storeLoad.registerMem = regMem; + this->op_storeLoad.immS32 = immS32; + this->op_storeLoad.copyWidth = copyWidth; + this->op_storeLoad.flags2.swapEndian = switchEndian; + this->op_storeLoad.flags2.signExtend = signExtend; + } + + // store to memory + void make_memory_r(IMLReg regS, IMLReg regMem, sint32 immS32, uint32 copyWidth, bool switchEndian) + { + this->type = PPCREC_IML_TYPE_STORE; + this->operation = 0; + this->op_storeLoad.registerData = regS; + this->op_storeLoad.registerMem = regMem; + this->op_storeLoad.immS32 = immS32; + this->op_storeLoad.copyWidth = copyWidth; + this->op_storeLoad.flags2.swapEndian = switchEndian; + this->op_storeLoad.flags2.signExtend = false; + } + + void make_atomic_cmp_store(IMLReg regEA, IMLReg regCompareValue, IMLReg regWriteValue, IMLReg regSuccessOutput) + { + this->type = PPCREC_IML_TYPE_ATOMIC_CMP_STORE; + this->operation = 0; + this->op_atomic_compare_store.regEA = regEA; + this->op_atomic_compare_store.regCompareValue = regCompareValue; + this->op_atomic_compare_store.regWriteValue = regWriteValue; + this->op_atomic_compare_store.regBoolOut = regSuccessOutput; + } + + void make_call_imm(uintptr_t callAddress, IMLReg param0, IMLReg param1, IMLReg param2, IMLReg regReturn) + { + this->type = PPCREC_IML_TYPE_CALL_IMM; + this->operation = 0; + this->op_call_imm.callAddress = callAddress; + this->op_call_imm.regParam0 = param0; + this->op_call_imm.regParam1 = param1; + this->op_call_imm.regParam2 = param2; + this->op_call_imm.regReturn = regReturn; + } + + void make_fpr_compare(IMLReg regA, IMLReg regB, IMLReg regR, IMLCondition cond) + { + this->type = PPCREC_IML_TYPE_FPR_COMPARE; + this->operation = -999; + this->op_fpr_compare.regR = regR; + this->op_fpr_compare.regA = regA; + this->op_fpr_compare.regB = regB; + this->op_fpr_compare.cond = cond; + } + + /* X86 specific */ + void make_x86_eflags_jcc(IMLCondition cond, bool invertedCondition) + { + this->type = PPCREC_IML_TYPE_X86_EFLAGS_JCC; + this->operation = -999; + this->op_x86_eflags_jcc.cond = cond; + this->op_x86_eflags_jcc.invertedCondition = invertedCondition; + } + + void CheckRegisterUsage(IMLUsedRegisters* registersUsed) const; + bool HasSideEffects() const; // returns true if the instruction has side effects beyond just reading and writing registers. Dead code elimination uses this to know if an instruction can be dropped when the regular register outputs are not used + + void RewriteGPR(const std::unordered_map<IMLRegID, IMLRegID>& translationTable); +}; + +// architecture specific constants +namespace IMLArchX86 +{ + static constexpr int PHYSREG_GPR_BASE = 0; + static constexpr int PHYSREG_FPR_BASE = 16; +}; \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IMLOptimizer.cpp b/src/Cafe/HW/Espresso/Recompiler/IML/IMLOptimizer.cpp new file mode 100644 index 00000000..f2cf173a --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IMLOptimizer.cpp @@ -0,0 +1,794 @@ +#include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" +#include "Cafe/HW/Espresso/Recompiler/IML/IML.h" +#include "Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.h" + +#include "../PPCRecompiler.h" +#include "../PPCRecompilerIml.h" +#include "../BackendX64/BackendX64.h" + +#include "Common/FileStream.h" + +#include <boost/container/static_vector.hpp> +#include <boost/container/small_vector.hpp> + +IMLReg _FPRRegFromID(IMLRegID regId) +{ + return IMLReg(IMLRegFormat::F64, IMLRegFormat::F64, 0, regId); +} + +void PPCRecompiler_optimizeDirectFloatCopiesScanForward(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* imlSegment, sint32 imlIndexLoad, IMLReg fprReg) +{ + IMLRegID fprIndex = fprReg.GetRegID(); + + IMLInstruction* imlInstructionLoad = imlSegment->imlList.data() + imlIndexLoad; + if (imlInstructionLoad->op_storeLoad.flags2.notExpanded) + return; + + IMLUsedRegisters registersUsed; + sint32 scanRangeEnd = std::min<sint32>(imlIndexLoad + 25, imlSegment->imlList.size()); // don't scan too far (saves performance and also the chances we can merge the load+store become low at high distances) + bool foundMatch = false; + sint32 lastStore = -1; + for (sint32 i = imlIndexLoad + 1; i < scanRangeEnd; i++) + { + IMLInstruction* imlInstruction = imlSegment->imlList.data() + i; + if (imlInstruction->IsSuffixInstruction()) + break; + // check if FPR is stored + if ((imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE && imlInstruction->op_storeLoad.mode == PPCREC_FPR_ST_MODE_SINGLE_FROM_PS0) || + (imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE_INDEXED && imlInstruction->op_storeLoad.mode == PPCREC_FPR_ST_MODE_SINGLE_FROM_PS0)) + { + if (imlInstruction->op_storeLoad.registerData.GetRegID() == fprIndex) + { + if (foundMatch == false) + { + // flag the load-single instruction as "don't expand" (leave single value as-is) + imlInstructionLoad->op_storeLoad.flags2.notExpanded = true; + } + // also set the flag for the store instruction + IMLInstruction* imlInstructionStore = imlInstruction; + imlInstructionStore->op_storeLoad.flags2.notExpanded = true; + + foundMatch = true; + lastStore = i + 1; + + continue; + } + } + + // check if FPR is overwritten (we can actually ignore read operations?) + imlInstruction->CheckRegisterUsage(®istersUsed); + if (registersUsed.writtenGPR1.IsValidAndSameRegID(fprIndex) || registersUsed.writtenGPR2.IsValidAndSameRegID(fprIndex)) + break; + if (registersUsed.readGPR1.IsValidAndSameRegID(fprIndex)) + break; + if (registersUsed.readGPR2.IsValidAndSameRegID(fprIndex)) + break; + if (registersUsed.readGPR3.IsValidAndSameRegID(fprIndex)) + break; + if (registersUsed.readGPR4.IsValidAndSameRegID(fprIndex)) + break; + } + + if (foundMatch) + { + // insert expand instruction after store + IMLInstruction* newExpand = PPCRecompiler_insertInstruction(imlSegment, lastStore); + PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext, newExpand, PPCREC_IML_OP_FPR_EXPAND_BOTTOM32_TO_BOTTOM64_AND_TOP64, _FPRRegFromID(fprIndex)); + } +} + +/* +* Scans for patterns: +* <Load sp float into register f> +* <Random unrelated instructions> +* <Store sp float from register f> +* For these patterns the store and load is modified to work with un-extended values (float remains as float, no double conversion) +* The float->double extension is then executed later +* Advantages: +* Keeps denormals and other special float values intact +* Slightly improves performance +*/ +void IMLOptimizer_OptimizeDirectFloatCopies(ppcImlGenContext_t* ppcImlGenContext) +{ + for (IMLSegment* segIt : ppcImlGenContext->segmentList2) + { + for (sint32 i = 0; i < segIt->imlList.size(); i++) + { + IMLInstruction* imlInstruction = segIt->imlList.data() + i; + if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD && imlInstruction->op_storeLoad.mode == PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1) + { + PPCRecompiler_optimizeDirectFloatCopiesScanForward(ppcImlGenContext, segIt, i, imlInstruction->op_storeLoad.registerData); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED && imlInstruction->op_storeLoad.mode == PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1) + { + PPCRecompiler_optimizeDirectFloatCopiesScanForward(ppcImlGenContext, segIt, i, imlInstruction->op_storeLoad.registerData); + } + } + } +} + +void PPCRecompiler_optimizeDirectIntegerCopiesScanForward(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* imlSegment, sint32 imlIndexLoad, IMLReg gprReg) +{ + cemu_assert_debug(gprReg.GetBaseFormat() == IMLRegFormat::I64); // todo - proper handling required for non-standard sizes + cemu_assert_debug(gprReg.GetRegFormat() == IMLRegFormat::I32); + + IMLRegID gprIndex = gprReg.GetRegID(); + IMLInstruction* imlInstructionLoad = imlSegment->imlList.data() + imlIndexLoad; + if ( imlInstructionLoad->op_storeLoad.flags2.swapEndian == false ) + return; + bool foundMatch = false; + IMLUsedRegisters registersUsed; + sint32 scanRangeEnd = std::min<sint32>(imlIndexLoad + 25, imlSegment->imlList.size()); // don't scan too far (saves performance and also the chances we can merge the load+store become low at high distances) + sint32 i = imlIndexLoad + 1; + for (; i < scanRangeEnd; i++) + { + IMLInstruction* imlInstruction = imlSegment->imlList.data() + i; + if (imlInstruction->IsSuffixInstruction()) + break; + // check if GPR is stored + if ((imlInstruction->type == PPCREC_IML_TYPE_STORE && imlInstruction->op_storeLoad.copyWidth == 32 ) ) + { + if (imlInstruction->op_storeLoad.registerMem.GetRegID() == gprIndex) + break; + if (imlInstruction->op_storeLoad.registerData.GetRegID() == gprIndex) + { + IMLInstruction* imlInstructionStore = imlInstruction; + if (foundMatch == false) + { + // switch the endian swap flag for the load instruction + imlInstructionLoad->op_storeLoad.flags2.swapEndian = !imlInstructionLoad->op_storeLoad.flags2.swapEndian; + foundMatch = true; + } + // switch the endian swap flag for the store instruction + imlInstructionStore->op_storeLoad.flags2.swapEndian = !imlInstructionStore->op_storeLoad.flags2.swapEndian; + // keep scanning + continue; + } + } + // check if GPR is accessed + imlInstruction->CheckRegisterUsage(®istersUsed); + if (registersUsed.readGPR1.IsValidAndSameRegID(gprIndex) || + registersUsed.readGPR2.IsValidAndSameRegID(gprIndex) || + registersUsed.readGPR3.IsValidAndSameRegID(gprIndex)) + { + break; + } + if (registersUsed.IsBaseGPRWritten(gprReg)) + return; // GPR overwritten, we don't need to byte swap anymore + } + if (foundMatch) + { + PPCRecompiler_insertInstruction(imlSegment, i)->make_r_r(PPCREC_IML_OP_ENDIAN_SWAP, gprReg, gprReg); + } +} + +/* +* Scans for patterns: +* <Load sp integer into register r> +* <Random unrelated instructions> +* <Store sp integer from register r> +* For these patterns the store and load is modified to work with non-swapped values +* The big_endian->little_endian conversion is then executed later +* Advantages: +* Slightly improves performance +*/ +void IMLOptimizer_OptimizeDirectIntegerCopies(ppcImlGenContext_t* ppcImlGenContext) +{ + for (IMLSegment* segIt : ppcImlGenContext->segmentList2) + { + for (sint32 i = 0; i < segIt->imlList.size(); i++) + { + IMLInstruction* imlInstruction = segIt->imlList.data() + i; + if (imlInstruction->type == PPCREC_IML_TYPE_LOAD && imlInstruction->op_storeLoad.copyWidth == 32 && imlInstruction->op_storeLoad.flags2.swapEndian ) + { + PPCRecompiler_optimizeDirectIntegerCopiesScanForward(ppcImlGenContext, segIt, i, imlInstruction->op_storeLoad.registerData); + } + } + } +} + +IMLName PPCRecompilerImlGen_GetRegName(ppcImlGenContext_t* ppcImlGenContext, IMLReg reg); + +sint32 _getGQRIndexFromRegister(ppcImlGenContext_t* ppcImlGenContext, IMLReg gqrReg) +{ + if (gqrReg.IsInvalid()) + return -1; + sint32 namedReg = PPCRecompilerImlGen_GetRegName(ppcImlGenContext, gqrReg); + if (namedReg >= (PPCREC_NAME_SPR0 + SPR_UGQR0) && namedReg <= (PPCREC_NAME_SPR0 + SPR_UGQR7)) + { + return namedReg - (PPCREC_NAME_SPR0 + SPR_UGQR0); + } + else + { + cemu_assert_suspicious(); + } + return -1; +} + +bool PPCRecompiler_isUGQRValueKnown(ppcImlGenContext_t* ppcImlGenContext, sint32 gqrIndex, uint32& gqrValue) +{ + // UGQR 2 to 7 are initialized by the OS and we assume that games won't ever permanently touch those + // todo - hack - replace with more accurate solution + if (gqrIndex == 2) + gqrValue = 0x00040004; + else if (gqrIndex == 3) + gqrValue = 0x00050005; + else if (gqrIndex == 4) + gqrValue = 0x00060006; + else if (gqrIndex == 5) + gqrValue = 0x00070007; + else + return false; + return true; +} + +/* + * If value of GQR can be predicted for a given PSQ load or store instruction then replace it with an optimized version + */ +void PPCRecompiler_optimizePSQLoadAndStore(ppcImlGenContext_t* ppcImlGenContext) +{ + for (IMLSegment* segIt : ppcImlGenContext->segmentList2) + { + for(IMLInstruction& instIt : segIt->imlList) + { + if (instIt.type == PPCREC_IML_TYPE_FPR_LOAD || instIt.type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED) + { + if(instIt.op_storeLoad.mode != PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0 && + instIt.op_storeLoad.mode != PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1 ) + continue; + // get GQR value + cemu_assert_debug(instIt.op_storeLoad.registerGQR.IsValid()); + sint32 gqrIndex = _getGQRIndexFromRegister(ppcImlGenContext, instIt.op_storeLoad.registerGQR); + cemu_assert(gqrIndex >= 0); + if (ppcImlGenContext->tracking.modifiesGQR[gqrIndex]) + continue; + uint32 gqrValue; + if (!PPCRecompiler_isUGQRValueKnown(ppcImlGenContext, gqrIndex, gqrValue)) + continue; + + uint32 formatType = (gqrValue >> 16) & 7; + uint32 scale = (gqrValue >> 24) & 0x3F; + if (scale != 0) + continue; // only generic handler supports scale + if (instIt.op_storeLoad.mode == PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0) + { + if (formatType == 0) + instIt.op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0; + else if (formatType == 4) + instIt.op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_U8_PS0; + else if (formatType == 5) + instIt.op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_U16_PS0; + else if (formatType == 6) + instIt.op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_S8_PS0; + else if (formatType == 7) + instIt.op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_S16_PS0; + if (instIt.op_storeLoad.mode != PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0) + instIt.op_storeLoad.registerGQR = IMLREG_INVALID; + } + else if (instIt.op_storeLoad.mode == PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1) + { + if (formatType == 0) + instIt.op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0_PS1; + else if (formatType == 4) + instIt.op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_U8_PS0_PS1; + else if (formatType == 5) + instIt.op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_U16_PS0_PS1; + else if (formatType == 6) + instIt.op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_S8_PS0_PS1; + else if (formatType == 7) + instIt.op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_S16_PS0_PS1; + if (instIt.op_storeLoad.mode != PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1) + instIt.op_storeLoad.registerGQR = IMLREG_INVALID; + } + } + else if (instIt.type == PPCREC_IML_TYPE_FPR_STORE || instIt.type == PPCREC_IML_TYPE_FPR_STORE_INDEXED) + { + if(instIt.op_storeLoad.mode != PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0 && + instIt.op_storeLoad.mode != PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1) + continue; + // get GQR value + cemu_assert_debug(instIt.op_storeLoad.registerGQR.IsValid()); + sint32 gqrIndex = _getGQRIndexFromRegister(ppcImlGenContext, instIt.op_storeLoad.registerGQR); + cemu_assert(gqrIndex >= 0 && gqrIndex < 8); + if (ppcImlGenContext->tracking.modifiesGQR[gqrIndex]) + continue; + uint32 gqrValue; + if(!PPCRecompiler_isUGQRValueKnown(ppcImlGenContext, gqrIndex, gqrValue)) + continue; + uint32 formatType = (gqrValue >> 16) & 7; + uint32 scale = (gqrValue >> 24) & 0x3F; + if (scale != 0) + continue; // only generic handler supports scale + if (instIt.op_storeLoad.mode == PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0) + { + if (formatType == 0) + instIt.op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0; + else if (formatType == 4) + instIt.op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_U8_PS0; + else if (formatType == 5) + instIt.op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_U16_PS0; + else if (formatType == 6) + instIt.op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_S8_PS0; + else if (formatType == 7) + instIt.op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_S16_PS0; + if (instIt.op_storeLoad.mode != PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0) + instIt.op_storeLoad.registerGQR = IMLREG_INVALID; + } + else if (instIt.op_storeLoad.mode == PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1) + { + if (formatType == 0) + instIt.op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0_PS1; + else if (formatType == 4) + instIt.op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_U8_PS0_PS1; + else if (formatType == 5) + instIt.op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_U16_PS0_PS1; + else if (formatType == 6) + instIt.op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_S8_PS0_PS1; + else if (formatType == 7) + instIt.op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_S16_PS0_PS1; + if (instIt.op_storeLoad.mode != PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1) + instIt.op_storeLoad.registerGQR = IMLREG_INVALID; + } + } + } + } +} + +// analyses register dependencies across the entire function +// per segment this will generate information about which registers need to be preserved and which ones don't (e.g. are overwritten) +class IMLOptimizerRegIOAnalysis +{ + public: + // constructor with segment pointer list as span + IMLOptimizerRegIOAnalysis(std::span<IMLSegment*> segmentList, uint32 maxRegId) : m_segmentList(segmentList), m_maxRegId(maxRegId) + { + m_segRegisterInOutList.resize(segmentList.size()); + } + + struct IMLSegmentRegisterInOut + { + // todo - since our register ID range is usually pretty small (<64) we could use integer bitmasks to accelerate this? There is a helper class used in RA code already + std::unordered_set<IMLRegID> regWritten; // registers which are modified in this segment + std::unordered_set<IMLRegID> regImported; // registers which are read in this segment before they are written (importing value from previous segments) + std::unordered_set<IMLRegID> regForward; // registers which are not read or written in this segment, but are imported into a later segment (propagated info) + }; + + // calculate which registers are imported (read-before-written) and forwarded (read-before-written by a later segment) per segment + // then in a second step propagate the dependencies across linked segments + void ComputeDepedencies() + { + std::vector<IMLSegmentRegisterInOut>& segRegisterInOutList = m_segRegisterInOutList; + IMLSegmentRegisterInOut* segIO = segRegisterInOutList.data(); + uint32 index = 0; + for(auto& seg : m_segmentList) + { + seg->momentaryIndex = index; + index++; + for(auto& instr : seg->imlList) + { + IMLUsedRegisters registerUsage; + instr.CheckRegisterUsage(®isterUsage); + // registers are considered imported if they are read before being written in this seg + registerUsage.ForEachReadGPR([&](IMLReg gprReg) { + IMLRegID gprId = gprReg.GetRegID(); + if (!segIO->regWritten.contains(gprId)) + { + segIO->regImported.insert(gprId); + } + }); + registerUsage.ForEachWrittenGPR([&](IMLReg gprReg) { + IMLRegID gprId = gprReg.GetRegID(); + segIO->regWritten.insert(gprId); + }); + } + segIO++; + } + // for every exit segment, import all registers + for(auto& seg : m_segmentList) + { + if (!seg->nextSegmentIsUncertain) + continue; + if(seg->deadCodeEliminationHintSeg) + continue; + IMLSegmentRegisterInOut& segIO = segRegisterInOutList[seg->momentaryIndex]; + for(uint32 i=0; i<=m_maxRegId; i++) + { + segIO.regImported.insert((IMLRegID)i); + } + } + // broadcast dependencies across segment chains + std::unordered_set<uint32> segIdsWhichNeedUpdate; + for (uint32 i = 0; i < m_segmentList.size(); i++) + { + segIdsWhichNeedUpdate.insert(i); + } + while(!segIdsWhichNeedUpdate.empty()) + { + auto firstIt = segIdsWhichNeedUpdate.begin(); + uint32 segId = *firstIt; + segIdsWhichNeedUpdate.erase(firstIt); + // forward regImported and regForward to earlier segments into their regForward, unless the register is written + auto& curSeg = m_segmentList[segId]; + IMLSegmentRegisterInOut& curSegIO = segRegisterInOutList[segId]; + for(auto& prevSeg : curSeg->list_prevSegments) + { + IMLSegmentRegisterInOut& prevSegIO = segRegisterInOutList[prevSeg->momentaryIndex]; + bool prevSegChanged = false; + for(auto& regId : curSegIO.regImported) + { + if (!prevSegIO.regWritten.contains(regId)) + prevSegChanged |= prevSegIO.regForward.insert(regId).second; + } + for(auto& regId : curSegIO.regForward) + { + if (!prevSegIO.regWritten.contains(regId)) + prevSegChanged |= prevSegIO.regForward.insert(regId).second; + } + if(prevSegChanged) + segIdsWhichNeedUpdate.insert(prevSeg->momentaryIndex); + } + // same for hint links + for(auto& prevSeg : curSeg->list_deadCodeHintBy) + { + IMLSegmentRegisterInOut& prevSegIO = segRegisterInOutList[prevSeg->momentaryIndex]; + bool prevSegChanged = false; + for(auto& regId : curSegIO.regImported) + { + if (!prevSegIO.regWritten.contains(regId)) + prevSegChanged |= prevSegIO.regForward.insert(regId).second; + } + for(auto& regId : curSegIO.regForward) + { + if (!prevSegIO.regWritten.contains(regId)) + prevSegChanged |= prevSegIO.regForward.insert(regId).second; + } + if(prevSegChanged) + segIdsWhichNeedUpdate.insert(prevSeg->momentaryIndex); + } + } + } + + std::unordered_set<IMLRegID> GetRegistersNeededAtEndOfSegment(IMLSegment& seg) + { + std::unordered_set<IMLRegID> regsNeeded; + if(seg.nextSegmentIsUncertain) + { + if(seg.deadCodeEliminationHintSeg) + { + auto& nextSegIO = m_segRegisterInOutList[seg.deadCodeEliminationHintSeg->momentaryIndex]; + regsNeeded.insert(nextSegIO.regImported.begin(), nextSegIO.regImported.end()); + regsNeeded.insert(nextSegIO.regForward.begin(), nextSegIO.regForward.end()); + } + else + { + // add all regs + for(uint32 i = 0; i <= m_maxRegId; i++) + regsNeeded.insert(i); + } + return regsNeeded; + } + if(seg.nextSegmentBranchTaken) + { + auto& nextSegIO = m_segRegisterInOutList[seg.nextSegmentBranchTaken->momentaryIndex]; + regsNeeded.insert(nextSegIO.regImported.begin(), nextSegIO.regImported.end()); + regsNeeded.insert(nextSegIO.regForward.begin(), nextSegIO.regForward.end()); + } + if(seg.nextSegmentBranchNotTaken) + { + auto& nextSegIO = m_segRegisterInOutList[seg.nextSegmentBranchNotTaken->momentaryIndex]; + regsNeeded.insert(nextSegIO.regImported.begin(), nextSegIO.regImported.end()); + regsNeeded.insert(nextSegIO.regForward.begin(), nextSegIO.regForward.end()); + } + return regsNeeded; + } + + bool IsRegisterNeededAtEndOfSegment(IMLSegment& seg, IMLRegID regId) + { + if(seg.nextSegmentIsUncertain) + { + if(!seg.deadCodeEliminationHintSeg) + return true; + auto& nextSegIO = m_segRegisterInOutList[seg.deadCodeEliminationHintSeg->momentaryIndex]; + if(nextSegIO.regImported.contains(regId)) + return true; + if(nextSegIO.regForward.contains(regId)) + return true; + return false; + } + if(seg.nextSegmentBranchTaken) + { + auto& nextSegIO = m_segRegisterInOutList[seg.nextSegmentBranchTaken->momentaryIndex]; + if(nextSegIO.regImported.contains(regId)) + return true; + if(nextSegIO.regForward.contains(regId)) + return true; + } + if(seg.nextSegmentBranchNotTaken) + { + auto& nextSegIO = m_segRegisterInOutList[seg.nextSegmentBranchNotTaken->momentaryIndex]; + if(nextSegIO.regImported.contains(regId)) + return true; + if(nextSegIO.regForward.contains(regId)) + return true; + } + return false; + } + + private: + std::span<IMLSegment*> m_segmentList; + uint32 m_maxRegId; + + std::vector<IMLSegmentRegisterInOut> m_segRegisterInOutList; + +}; + +// scan backwards starting from index and return the index of the first found instruction which writes to the given register (by id) +sint32 IMLUtil_FindInstructionWhichWritesRegister(IMLSegment& seg, sint32 startIndex, IMLReg reg, sint32 maxScanDistance = -1) +{ + sint32 endIndex = std::max<sint32>(startIndex - maxScanDistance, 0); + for (sint32 i = startIndex; i >= endIndex; i--) + { + IMLInstruction& imlInstruction = seg.imlList[i]; + IMLUsedRegisters registersUsed; + imlInstruction.CheckRegisterUsage(®istersUsed); + if (registersUsed.IsBaseGPRWritten(reg)) + return i; + } + return -1; +} + +// returns true if the instruction can safely be moved while keeping ordering constraints and data dependencies intact +// initialIndex is inclusive, targetIndex is exclusive +bool IMLUtil_CanMoveInstructionTo(IMLSegment& seg, sint32 initialIndex, sint32 targetIndex) +{ + boost::container::static_vector<IMLRegID, 8> regsWritten; + boost::container::static_vector<IMLRegID, 8> regsRead; + // get list of read and written registers + IMLUsedRegisters registersUsed; + seg.imlList[initialIndex].CheckRegisterUsage(®istersUsed); + registersUsed.ForEachAccessedGPR([&](IMLReg reg, bool isWritten) { + if (isWritten) + regsWritten.push_back(reg.GetRegID()); + else + regsRead.push_back(reg.GetRegID()); + }); + // check all the instructions inbetween + if(initialIndex < targetIndex) + { + sint32 scanStartIndex = initialIndex+1; // +1 to skip the moving instruction itself + sint32 scanEndIndex = targetIndex; + for (sint32 i = scanStartIndex; i < scanEndIndex; i++) + { + IMLUsedRegisters registersUsed; + seg.imlList[i].CheckRegisterUsage(®istersUsed); + // in order to be able to move an instruction past another instruction, any of the read registers must not be modified (written) + // and any of it's written registers must not be read + bool canMove = true; + registersUsed.ForEachAccessedGPR([&](IMLReg reg, bool isWritten) { + IMLRegID regId = reg.GetRegID(); + if (!isWritten) + canMove = canMove && std::find(regsWritten.begin(), regsWritten.end(), regId) == regsWritten.end(); + else + canMove = canMove && std::find(regsRead.begin(), regsRead.end(), regId) == regsRead.end(); + }); + if(!canMove) + return false; + } + } + else + { + cemu_assert_unimplemented(); // backwards scan is todo + return false; + } + return true; +} + +sint32 IMLUtil_CountRegisterReadsInRange(IMLSegment& seg, sint32 scanStartIndex, sint32 scanEndIndex, IMLRegID regId) +{ + cemu_assert_debug(scanStartIndex <= scanEndIndex); + cemu_assert_debug(scanEndIndex < seg.imlList.size()); + sint32 count = 0; + for (sint32 i = scanStartIndex; i <= scanEndIndex; i++) + { + IMLUsedRegisters registersUsed; + seg.imlList[i].CheckRegisterUsage(®istersUsed); + registersUsed.ForEachReadGPR([&](IMLReg reg) { + if (reg.GetRegID() == regId) + count++; + }); + } + return count; +} + +// move instruction from one index to another +// instruction will be inserted before the instruction at targetIndex +// returns the new instruction index of the moved instruction +sint32 IMLUtil_MoveInstructionTo(IMLSegment& seg, sint32 initialIndex, sint32 targetIndex) +{ + cemu_assert_debug(initialIndex != targetIndex); + IMLInstruction temp = seg.imlList[initialIndex]; + if (initialIndex < targetIndex) + { + cemu_assert_debug(targetIndex > 0); + targetIndex--; + for(size_t i=initialIndex; i<targetIndex; i++) + seg.imlList[i] = seg.imlList[i+1]; + seg.imlList[targetIndex] = temp; + return targetIndex; + } + else + { + cemu_assert_unimplemented(); // testing needed + std::copy(seg.imlList.begin() + targetIndex, seg.imlList.begin() + initialIndex, seg.imlList.begin() + targetIndex + 1); + seg.imlList[targetIndex] = temp; + return targetIndex; + } +} + +// x86 specific +bool IMLOptimizerX86_ModifiesEFlags(IMLInstruction& inst) +{ + // this is a very conservative implementation. There are more cases but this is good enough for now + if(inst.type == PPCREC_IML_TYPE_NAME_R || inst.type == PPCREC_IML_TYPE_R_NAME) + return false; + if((inst.type == PPCREC_IML_TYPE_R_R || inst.type == PPCREC_IML_TYPE_R_S32) && inst.operation == PPCREC_IML_OP_ASSIGN) + return false; + return true; // if we dont know for sure, assume it does +} + +void IMLOptimizer_DebugPrintSeg(ppcImlGenContext_t& ppcImlGenContext, IMLSegment& seg) +{ + printf("----------------\n"); + IMLDebug_DumpSegment(&ppcImlGenContext, &seg); + fflush(stdout); +} + +void IMLOptimizer_RemoveDeadCodeFromSegment(IMLOptimizerRegIOAnalysis& regIoAnalysis, IMLSegment& seg) +{ + // algorithm works like this: + // Calculate which registers need to be preserved at the end of each segment + // Then for each segment: + // - Iterate instructions backwards + // - Maintain a list of registers which are read at a later point (initially this is the list from the first step) + // - If an instruction only modifies registers which are not in the read list and has no side effects, then it is dead code and can be replaced with a no-op + + std::unordered_set<IMLRegID> regsNeeded = regIoAnalysis.GetRegistersNeededAtEndOfSegment(seg); + + // start with suffix instruction + if(seg.HasSuffixInstruction()) + { + IMLInstruction& imlInstruction = seg.imlList[seg.GetSuffixInstructionIndex()]; + IMLUsedRegisters registersUsed; + imlInstruction.CheckRegisterUsage(®istersUsed); + registersUsed.ForEachWrittenGPR([&](IMLReg reg) { + regsNeeded.erase(reg.GetRegID()); + }); + registersUsed.ForEachReadGPR([&](IMLReg reg) { + regsNeeded.insert(reg.GetRegID()); + }); + } + // iterate instructions backwards + for (sint32 i = seg.imlList.size() - (seg.HasSuffixInstruction() ? 2:1); i >= 0; i--) + { + IMLInstruction& imlInstruction = seg.imlList[i]; + IMLUsedRegisters registersUsed; + imlInstruction.CheckRegisterUsage(®istersUsed); + // register read -> remove from overwritten list + // register written -> add to overwritten list + + // check if this instruction only writes registers which will never be read + bool onlyWritesRedundantRegisters = true; + registersUsed.ForEachWrittenGPR([&](IMLReg reg) { + if (regsNeeded.contains(reg.GetRegID())) + onlyWritesRedundantRegisters = false; + }); + // check if any of the written registers are read after this point + registersUsed.ForEachWrittenGPR([&](IMLReg reg) { + regsNeeded.erase(reg.GetRegID()); + }); + registersUsed.ForEachReadGPR([&](IMLReg reg) { + regsNeeded.insert(reg.GetRegID()); + }); + if(!imlInstruction.HasSideEffects() && onlyWritesRedundantRegisters) + { + imlInstruction.make_no_op(); + } + } +} + +void IMLOptimizerX86_SubstituteCJumpForEflagsJump(IMLOptimizerRegIOAnalysis& regIoAnalysis, IMLSegment& seg) +{ + // convert and optimize bool condition jumps to eflags condition jumps + // - Moves eflag setter (e.g. cmp) closer to eflags consumer (conditional jump) if necessary. If not possible but required then exit early + // - Since we only rely on eflags, the boolean register can be optimized out if DCE considers it unused + // - Further detect and optimize patterns like DEC + CMP + JCC into fused ops (todo) + + // check if this segment ends with a conditional jump + if(!seg.HasSuffixInstruction()) + return; + sint32 cjmpInstIndex = seg.GetSuffixInstructionIndex(); + if(cjmpInstIndex < 0) + return; + IMLInstruction& cjumpInstr = seg.imlList[cjmpInstIndex]; + if( cjumpInstr.type != PPCREC_IML_TYPE_CONDITIONAL_JUMP ) + return; + IMLReg regCondBool = cjumpInstr.op_conditional_jump.registerBool; + bool invertedCondition = !cjumpInstr.op_conditional_jump.mustBeTrue; + // find the instruction which sets the bool + sint32 cmpInstrIndex = IMLUtil_FindInstructionWhichWritesRegister(seg, cjmpInstIndex-1, regCondBool, 20); + if(cmpInstrIndex < 0) + return; + // check if its an instruction combo which can be optimized (currently only cmp + cjump) and get the condition + IMLInstruction& condSetterInstr = seg.imlList[cmpInstrIndex]; + IMLCondition cond; + if(condSetterInstr.type == PPCREC_IML_TYPE_COMPARE) + cond = condSetterInstr.op_compare.cond; + else if(condSetterInstr.type == PPCREC_IML_TYPE_COMPARE_S32) + cond = condSetterInstr.op_compare_s32.cond; + else + return; + // check if instructions inbetween modify eflags + sint32 indexEflagsSafeStart = -1; // index of the first instruction which does not modify eflags up to cjump + for(sint32 i = cjmpInstIndex-1; i > cmpInstrIndex; i--) + { + if(IMLOptimizerX86_ModifiesEFlags(seg.imlList[i])) + { + indexEflagsSafeStart = i+1; + break; + } + } + if(indexEflagsSafeStart >= 0) + { + cemu_assert(indexEflagsSafeStart > 0); + // there are eflags-modifying instructions inbetween the bool setter and cjump + // try to move the eflags setter close enough to the cjump (to indexEflagsSafeStart) + bool canMove = IMLUtil_CanMoveInstructionTo(seg, cmpInstrIndex, indexEflagsSafeStart); + if(!canMove) + { + return; + } + else + { + cmpInstrIndex = IMLUtil_MoveInstructionTo(seg, cmpInstrIndex, indexEflagsSafeStart); + } + } + // we can turn the jump into an eflags jump + cjumpInstr.make_x86_eflags_jcc(cond, invertedCondition); + + if (IMLUtil_CountRegisterReadsInRange(seg, cmpInstrIndex, cjmpInstIndex, regCondBool.GetRegID()) > 1 || regIoAnalysis.IsRegisterNeededAtEndOfSegment(seg, regCondBool.GetRegID())) + return; // bool register is used beyond the CMP, we can't drop it + + auto& cmpInstr = seg.imlList[cmpInstrIndex]; + cemu_assert_debug(cmpInstr.type == PPCREC_IML_TYPE_COMPARE || cmpInstr.type == PPCREC_IML_TYPE_COMPARE_S32); + if(cmpInstr.type == PPCREC_IML_TYPE_COMPARE) + { + IMLReg regA = cmpInstr.op_compare.regA; + IMLReg regB = cmpInstr.op_compare.regB; + seg.imlList[cmpInstrIndex].make_r_r(PPCREC_IML_OP_X86_CMP, regA, regB); + } + else + { + IMLReg regA = cmpInstr.op_compare_s32.regA; + sint32 val = cmpInstr.op_compare_s32.immS32; + seg.imlList[cmpInstrIndex].make_r_s32(PPCREC_IML_OP_X86_CMP, regA, val); + } + +} + +void IMLOptimizer_StandardOptimizationPassForSegment(IMLOptimizerRegIOAnalysis& regIoAnalysis, IMLSegment& seg) +{ + IMLOptimizer_RemoveDeadCodeFromSegment(regIoAnalysis, seg); + + // x86 specific optimizations + IMLOptimizerX86_SubstituteCJumpForEflagsJump(regIoAnalysis, seg); // this pass should be applied late since it creates invisible eflags dependencies (which would break further register dependency analysis) +} + +void IMLOptimizer_StandardOptimizationPass(ppcImlGenContext_t& ppcImlGenContext) +{ + IMLOptimizerRegIOAnalysis regIoAnalysis(ppcImlGenContext.segmentList2, ppcImlGenContext.GetMaxRegId()); + regIoAnalysis.ComputeDepedencies(); + for (IMLSegment* segIt : ppcImlGenContext.segmentList2) + { + IMLOptimizer_StandardOptimizationPassForSegment(regIoAnalysis, *segIt); + } +} diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocator.cpp b/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocator.cpp new file mode 100644 index 00000000..d411be14 --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocator.cpp @@ -0,0 +1,2199 @@ +#include "IML.h" + +#include "../PPCRecompiler.h" +#include "../PPCRecompilerIml.h" +#include "IMLRegisterAllocator.h" +#include "IMLRegisterAllocatorRanges.h" + +#include "../BackendX64/BackendX64.h" + +#include <boost/container/static_vector.hpp> +#include <boost/container/small_vector.hpp> + +#include "Common/cpu_features.h" + +#define DEBUG_RA_EXTRA_VALIDATION 0 // if set to non-zero, additional expensive validation checks will be performed +#define DEBUG_RA_INSTRUCTION_GEN 0 + +struct IMLRARegAbstractLiveness // preliminary liveness info. One entry per register and segment +{ + IMLRARegAbstractLiveness(IMLRegFormat regBaseFormat, sint32 usageStart, sint32 usageEnd) + : regBaseFormat(regBaseFormat), usageStart(usageStart), usageEnd(usageEnd) {}; + + void TrackInstruction(sint32 index) + { + usageStart = std::min<sint32>(usageStart, index); + usageEnd = std::max<sint32>(usageEnd, index + 1); // exclusive index + } + + sint32 usageStart; + sint32 usageEnd; + bool isProcessed{false}; + IMLRegFormat regBaseFormat; +}; + +struct IMLRegisterAllocatorContext +{ + IMLRegisterAllocatorParameters* raParam; + ppcImlGenContext_t* deprGenContext; // deprecated. Try to decouple IMLRA from other parts of IML/PPCRec + + std::unordered_map<IMLRegID, IMLRegFormat> regIdToBaseFormat; + // first pass + std::vector<std::unordered_map<IMLRegID, IMLRARegAbstractLiveness>> perSegmentAbstractRanges; + + // helper methods + inline std::unordered_map<IMLRegID, IMLRARegAbstractLiveness>& GetSegmentAbstractRangeMap(IMLSegment* imlSegment) + { + return perSegmentAbstractRanges[imlSegment->momentaryIndex]; + } + + inline IMLRegFormat GetBaseFormatByRegId(IMLRegID regId) const + { + auto it = regIdToBaseFormat.find(regId); + cemu_assert_debug(it != regIdToBaseFormat.cend()); + return it->second; + } +}; + +struct IMLFixedRegisters +{ + struct Entry + { + Entry(IMLReg reg, IMLPhysRegisterSet physRegSet) + : reg(reg), physRegSet(physRegSet) {} + + IMLReg reg; + IMLPhysRegisterSet physRegSet; + }; + boost::container::small_vector<Entry, 4> listInput; // fixed register requirements for instruction input edge + boost::container::small_vector<Entry, 4> listOutput; // fixed register requirements for instruction output edge +}; + +static void SetupCallingConvention(const IMLInstruction* instruction, IMLFixedRegisters& fixedRegs, const IMLPhysReg intParamToPhysReg[3], const IMLPhysReg floatParamToPhysReg[3], const IMLPhysReg intReturnPhysReg, const IMLPhysReg floatReturnPhysReg, IMLPhysRegisterSet volatileRegisters) +{ + sint32 numIntParams = 0, numFloatParams = 0; + + auto AddParameterMapping = [&](IMLReg reg) { + if (!reg.IsValid()) + return; + if (reg.GetBaseFormat() == IMLRegFormat::I64) + { + IMLPhysRegisterSet ps; + ps.SetAvailable(intParamToPhysReg[numIntParams]); + fixedRegs.listInput.emplace_back(reg, ps); + numIntParams++; + } + else if (reg.GetBaseFormat() == IMLRegFormat::F64) + { + IMLPhysRegisterSet ps; + ps.SetAvailable(floatParamToPhysReg[numFloatParams]); + fixedRegs.listInput.emplace_back(reg, ps); + numFloatParams++; + } + else + { + cemu_assert_suspicious(); + } + }; + AddParameterMapping(instruction->op_call_imm.regParam0); + AddParameterMapping(instruction->op_call_imm.regParam1); + AddParameterMapping(instruction->op_call_imm.regParam2); + // return value + if (instruction->op_call_imm.regReturn.IsValid()) + { + IMLRegFormat returnFormat = instruction->op_call_imm.regReturn.GetBaseFormat(); + bool isIntegerFormat = returnFormat == IMLRegFormat::I64 || returnFormat == IMLRegFormat::I32 || returnFormat == IMLRegFormat::I16 || returnFormat == IMLRegFormat::I8; + IMLPhysRegisterSet ps; + if (isIntegerFormat) + { + ps.SetAvailable(intReturnPhysReg); + volatileRegisters.SetReserved(intReturnPhysReg); + } + else + { + ps.SetAvailable(floatReturnPhysReg); + volatileRegisters.SetReserved(floatReturnPhysReg); + } + fixedRegs.listOutput.emplace_back(instruction->op_call_imm.regReturn, ps); + } + // block volatile registers from being used on the output edge, this makes the register allocator store them during the call + fixedRegs.listOutput.emplace_back(IMLREG_INVALID, volatileRegisters); +} + +#if defined(__aarch64__) +// aarch64 +static void GetInstructionFixedRegisters(IMLInstruction* instruction, IMLFixedRegisters& fixedRegs) +{ + fixedRegs.listInput.clear(); + fixedRegs.listOutput.clear(); + + // code below for aarch64 has not been tested + // The purpose of GetInstructionFixedRegisters() is to constraint virtual registers to specific physical registers for instructions which need it + // on x86 this is used for instructions like SHL <reg>, CL where the CL register is hardwired. On aarch it's probably only necessary for setting up the calling convention + cemu_assert_unimplemented(); +#ifdef 0 + if (instruction->type == PPCREC_IML_TYPE_CALL_IMM) + { + const IMLPhysReg intParamToPhysReg[3] = {IMLArchAArch64::PHYSREG_GPR_BASE + 0, IMLArchAArch64::PHYSREG_GPR_BASE + 1, IMLArchAArch64::PHYSREG_GPR_BASE + 2}; + const IMLPhysReg floatParamToPhysReg[3] = {IMLArchAArch64::PHYSREG_FPR_BASE + 0, IMLArchAArch64::PHYSREG_FPR_BASE + 1, IMLArchAArch64::PHYSREG_FPR_BASE + 2}; + IMLPhysRegisterSet volatileRegs; + for (int i=0; i<19; i++) // x0 to x18 are volatile + volatileRegs.SetAvailable(IMLArchAArch64::PHYSREG_GPR_BASE + i); + for (int i = 0; i <= 31; i++) // which float registers are volatile? + volatileRegs.SetAvailable(IMLArchAArch64::PHYSREG_FPR_BASE + i); + SetupCallingConvention(instruction, fixedRegs, intParamToPhysReg, floatParamToPhysReg, IMLArchAArch64::PHYSREG_GPR_BASE + 0, IMLArchAArch64::PHYSREG_FPR_BASE + 0, volatileRegs); + } +#endif +} +#else +// x86-64 +static void GetInstructionFixedRegisters(IMLInstruction* instruction, IMLFixedRegisters& fixedRegs) +{ + fixedRegs.listInput.clear(); + fixedRegs.listOutput.clear(); + + if (instruction->type == PPCREC_IML_TYPE_R_R_R) + { + if (instruction->operation == PPCREC_IML_OP_LEFT_SHIFT || instruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_S || instruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_U) + { + if(!g_CPUFeatures.x86.bmi2) + { + IMLPhysRegisterSet ps; + ps.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_ECX); + fixedRegs.listInput.emplace_back(instruction->op_r_r_r.regB, ps); + } + } + } + else if (instruction->type == PPCREC_IML_TYPE_ATOMIC_CMP_STORE) + { + IMLPhysRegisterSet ps; + ps.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_EAX); + fixedRegs.listInput.emplace_back(IMLREG_INVALID, ps); // none of the inputs may use EAX + fixedRegs.listOutput.emplace_back(instruction->op_atomic_compare_store.regBoolOut, ps); // but we output to EAX + } + else if (instruction->type == PPCREC_IML_TYPE_CALL_IMM) + { + const IMLPhysReg intParamToPhysReg[3] = {IMLArchX86::PHYSREG_GPR_BASE + X86_REG_RCX, IMLArchX86::PHYSREG_GPR_BASE + X86_REG_RDX, IMLArchX86::PHYSREG_GPR_BASE + X86_REG_R8}; + const IMLPhysReg floatParamToPhysReg[3] = {IMLArchX86::PHYSREG_FPR_BASE + 0, IMLArchX86::PHYSREG_FPR_BASE + 1, IMLArchX86::PHYSREG_FPR_BASE + 2}; + IMLPhysRegisterSet volatileRegs; + volatileRegs.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_RAX); + volatileRegs.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_RCX); + volatileRegs.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_RDX); + volatileRegs.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_R8); + volatileRegs.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_R9); + volatileRegs.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_R10); + volatileRegs.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_R11); + // YMM0-YMM5 are volatile + for (int i = 0; i <= 5; i++) + volatileRegs.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + i); + // for YMM6-YMM15 only the upper 128 bits are volatile which we dont use + SetupCallingConvention(instruction, fixedRegs, intParamToPhysReg, floatParamToPhysReg, IMLArchX86::PHYSREG_GPR_BASE + X86_REG_EAX, IMLArchX86::PHYSREG_FPR_BASE + 0, volatileRegs); + } +} +#endif + +uint32 IMLRA_GetNextIterationIndex() +{ + static uint32 recRACurrentIterationIndex = 0; + recRACurrentIterationIndex++; + return recRACurrentIterationIndex; +} + +bool _detectLoop(IMLSegment* currentSegment, sint32 depth, uint32 iterationIndex, IMLSegment* imlSegmentLoopBase) +{ + if (currentSegment == imlSegmentLoopBase) + return true; + if (currentSegment->raInfo.lastIterationIndex == iterationIndex) + return currentSegment->raInfo.isPartOfProcessedLoop; + if (depth >= 9) + return false; + currentSegment->raInfo.lastIterationIndex = iterationIndex; + currentSegment->raInfo.isPartOfProcessedLoop = false; + + if (currentSegment->nextSegmentIsUncertain) + return false; + if (currentSegment->nextSegmentBranchNotTaken) + { + if (currentSegment->nextSegmentBranchNotTaken->momentaryIndex > currentSegment->momentaryIndex) + { + currentSegment->raInfo.isPartOfProcessedLoop |= _detectLoop(currentSegment->nextSegmentBranchNotTaken, depth + 1, iterationIndex, imlSegmentLoopBase); + } + } + if (currentSegment->nextSegmentBranchTaken) + { + if (currentSegment->nextSegmentBranchTaken->momentaryIndex > currentSegment->momentaryIndex) + { + currentSegment->raInfo.isPartOfProcessedLoop |= _detectLoop(currentSegment->nextSegmentBranchTaken, depth + 1, iterationIndex, imlSegmentLoopBase); + } + } + if (currentSegment->raInfo.isPartOfProcessedLoop) + currentSegment->loopDepth++; + return currentSegment->raInfo.isPartOfProcessedLoop; +} + +void IMLRA_DetectLoop(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* imlSegmentLoopBase) +{ + uint32 iterationIndex = IMLRA_GetNextIterationIndex(); + imlSegmentLoopBase->raInfo.lastIterationIndex = iterationIndex; + if (_detectLoop(imlSegmentLoopBase->nextSegmentBranchTaken, 0, iterationIndex, imlSegmentLoopBase)) + { + imlSegmentLoopBase->loopDepth++; + } +} + +void IMLRA_IdentifyLoop(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* imlSegment) +{ + if (imlSegment->nextSegmentIsUncertain) + return; + // check if this segment has a branch that links to itself (tight loop) + if (imlSegment->nextSegmentBranchTaken == imlSegment) + { + // segment loops over itself + imlSegment->loopDepth++; + return; + } + // check if this segment has a branch that goes backwards (potential complex loop) + if (imlSegment->nextSegmentBranchTaken && imlSegment->nextSegmentBranchTaken->momentaryIndex < imlSegment->momentaryIndex) + { + IMLRA_DetectLoop(ppcImlGenContext, imlSegment); + } +} + +#define SUBRANGE_LIST_SIZE (128) + +sint32 IMLRA_CountDistanceUntilNextUse(raLivenessRange* subrange, raInstructionEdge startPosition) +{ + for (sint32 i = 0; i < subrange->list_accessLocations.size(); i++) + { + if (subrange->list_accessLocations[i].pos >= startPosition) + { + auto& it = subrange->list_accessLocations[i]; + cemu_assert_debug(it.IsRead() != it.IsWrite()); // an access location can be either read or write + cemu_assert_debug(!startPosition.ConnectsToPreviousSegment() && !startPosition.ConnectsToNextSegment()); + return it.pos.GetRaw() - startPosition.GetRaw(); + } + } + cemu_assert_debug(subrange->imlSegment->imlList.size() < 10000); + return 10001 * 2; +} + +// returns -1 if there is no fixed register requirement on or after startPosition +sint32 IMLRA_CountDistanceUntilFixedRegUsageInRange(IMLSegment* imlSegment, raLivenessRange* range, raInstructionEdge startPosition, sint32 physRegister, bool& hasFixedAccess) +{ + hasFixedAccess = false; + cemu_assert_debug(startPosition.IsInstructionIndex()); + for (auto& fixedReqEntry : range->list_fixedRegRequirements) + { + if (fixedReqEntry.pos < startPosition) + continue; + if (fixedReqEntry.allowedReg.IsAvailable(physRegister)) + { + hasFixedAccess = true; + return fixedReqEntry.pos.GetRaw() - startPosition.GetRaw(); + } + } + cemu_assert_debug(range->interval.end.IsInstructionIndex()); + return range->interval.end.GetRaw() - startPosition.GetRaw(); +} + +sint32 IMLRA_CountDistanceUntilFixedRegUsage(IMLSegment* imlSegment, raInstructionEdge startPosition, sint32 maxDistance, IMLRegID ourRegId, sint32 physRegister) +{ + cemu_assert_debug(startPosition.IsInstructionIndex()); + raInstructionEdge lastPos2; + lastPos2.Set(imlSegment->imlList.size(), false); + + raInstructionEdge endPos; + endPos = startPosition + maxDistance; + if (endPos > lastPos2) + endPos = lastPos2; + IMLFixedRegisters fixedRegs; + if (startPosition.IsOnOutputEdge()) + GetInstructionFixedRegisters(imlSegment->imlList.data() + startPosition.GetInstructionIndex(), fixedRegs); + for (raInstructionEdge currentPos = startPosition; currentPos <= endPos; ++currentPos) + { + if (currentPos.IsOnInputEdge()) + { + GetInstructionFixedRegisters(imlSegment->imlList.data() + currentPos.GetInstructionIndex(), fixedRegs); + } + auto& fixedRegAccess = currentPos.IsOnInputEdge() ? fixedRegs.listInput : fixedRegs.listOutput; + for (auto& fixedRegLoc : fixedRegAccess) + { + if (fixedRegLoc.reg.IsInvalid() || fixedRegLoc.reg.GetRegID() != ourRegId) + { + cemu_assert_debug(fixedRegLoc.reg.IsInvalid() || fixedRegLoc.physRegSet.HasExactlyOneAvailable()); // this whole function only makes sense when there is only one fixed register, otherwise there are extra permutations to consider. Except for IMLREG_INVALID which is used to indicate reserved registers + if (fixedRegLoc.physRegSet.IsAvailable(physRegister)) + return currentPos.GetRaw() - startPosition.GetRaw(); + } + } + } + return endPos.GetRaw() - startPosition.GetRaw(); +} + +// count how many instructions there are until physRegister is used by any subrange or reserved for any fixed register requirement (returns 0 if register is in use at startIndex) +sint32 PPCRecRA_countDistanceUntilNextLocalPhysRegisterUse(IMLSegment* imlSegment, raInstructionEdge startPosition, sint32 physRegister) +{ + cemu_assert_debug(startPosition.IsInstructionIndex()); + sint32 minDistance = (sint32)imlSegment->imlList.size() * 2 - startPosition.GetRaw(); + // next + raLivenessRange* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; + while (subrangeItr) + { + if (subrangeItr->GetPhysicalRegister() != physRegister) + { + subrangeItr = subrangeItr->link_allSegmentRanges.next; + continue; + } + if (subrangeItr->interval.ContainsEdge(startPosition)) + return 0; + if (subrangeItr->interval.end < startPosition) + { + subrangeItr = subrangeItr->link_allSegmentRanges.next; + continue; + } + cemu_assert_debug(startPosition <= subrangeItr->interval.start); + sint32 currentDist = subrangeItr->interval.start.GetRaw() - startPosition.GetRaw(); + minDistance = std::min(minDistance, currentDist); + subrangeItr = subrangeItr->link_allSegmentRanges.next; + } + return minDistance; +} + +struct IMLRALivenessTimeline +{ + IMLRALivenessTimeline() + { + } + + // manually add an active range + void AddActiveRange(raLivenessRange* subrange) + { + activeRanges.emplace_back(subrange); + } + + void ExpireRanges(raInstructionEdge expireUpTo) + { + expiredRanges.clear(); + size_t count = activeRanges.size(); + for (size_t f = 0; f < count; f++) + { + raLivenessRange* liverange = activeRanges[f]; + if (liverange->interval.end < expireUpTo) // this was <= but since end is not inclusive we need to use < + { +#ifdef CEMU_DEBUG_ASSERT + if (!expireUpTo.ConnectsToNextSegment() && (liverange->subrangeBranchTaken || liverange->subrangeBranchNotTaken)) + assert_dbg(); // infinite subranges should not expire +#endif + expiredRanges.emplace_back(liverange); + // remove entry + activeRanges[f] = activeRanges[count - 1]; + f--; + count--; + } + } + if (count != activeRanges.size()) + activeRanges.resize(count); + } + + std::span<raLivenessRange*> GetExpiredRanges() + { + return {expiredRanges.data(), expiredRanges.size()}; + } + + std::span<raLivenessRange*> GetActiveRanges() + { + return {activeRanges.data(), activeRanges.size()}; + } + + raLivenessRange* GetActiveRangeByVirtualRegId(IMLRegID regId) + { + for (auto& it : activeRanges) + if (it->virtualRegister == regId) + return it; + return nullptr; + } + + raLivenessRange* GetActiveRangeByPhysicalReg(sint32 physReg) + { + cemu_assert_debug(physReg >= 0); + for (auto& it : activeRanges) + if (it->physicalRegister == physReg) + return it; + return nullptr; + } + + boost::container::small_vector<raLivenessRange*, 64> activeRanges; + + private: + boost::container::small_vector<raLivenessRange*, 16> expiredRanges; +}; + +// mark occupied registers by any overlapping range as unavailable in physRegSet +void PPCRecRA_MaskOverlappingPhysRegForGlobalRange(raLivenessRange* range2, IMLPhysRegisterSet& physRegSet) +{ + auto clusterRanges = range2->GetAllSubrangesInCluster(); + for (auto& subrange : clusterRanges) + { + IMLSegment* imlSegment = subrange->imlSegment; + raLivenessRange* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; + while (subrangeItr) + { + if (subrange == subrangeItr) + { + // next + subrangeItr = subrangeItr->link_allSegmentRanges.next; + continue; + } + if (subrange->interval.IsOverlapping(subrangeItr->interval)) + { + if (subrangeItr->GetPhysicalRegister() >= 0) + physRegSet.SetReserved(subrangeItr->GetPhysicalRegister()); + } + // next + subrangeItr = subrangeItr->link_allSegmentRanges.next; + } + } +} + +bool _livenessRangeStartCompare(raLivenessRange* lhs, raLivenessRange* rhs) +{ + return lhs->interval.start < rhs->interval.start; +} + +void _sortSegmentAllSubrangesLinkedList(IMLSegment* imlSegment) +{ + raLivenessRange* subrangeList[4096 + 1]; + sint32 count = 0; + // disassemble linked list + raLivenessRange* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; + while (subrangeItr) + { + cemu_assert(count < 4096); + subrangeList[count] = subrangeItr; + count++; + // next + subrangeItr = subrangeItr->link_allSegmentRanges.next; + } + if (count == 0) + { + imlSegment->raInfo.linkedList_allSubranges = nullptr; + return; + } + // sort + std::sort(subrangeList, subrangeList + count, _livenessRangeStartCompare); + // reassemble linked list + subrangeList[count] = nullptr; + imlSegment->raInfo.linkedList_allSubranges = subrangeList[0]; + subrangeList[0]->link_allSegmentRanges.prev = nullptr; + subrangeList[0]->link_allSegmentRanges.next = subrangeList[1]; + for (sint32 i = 1; i < count; i++) + { + subrangeList[i]->link_allSegmentRanges.prev = subrangeList[i - 1]; + subrangeList[i]->link_allSegmentRanges.next = subrangeList[i + 1]; + } + // validate list +#if DEBUG_RA_EXTRA_VALIDATION + sint32 count2 = 0; + subrangeItr = imlSegment->raInfo.linkedList_allSubranges; + raInstructionEdge currentStartPosition; + currentStartPosition.SetRaw(RA_INTER_RANGE_START); + while (subrangeItr) + { + count2++; + if (subrangeItr->interval2.start < currentStartPosition) + assert_dbg(); + currentStartPosition = subrangeItr->interval2.start; + // next + subrangeItr = subrangeItr->link_allSegmentRanges.next; + } + if (count != count2) + assert_dbg(); +#endif +} + +std::unordered_map<IMLRegID, raLivenessRange*>& IMLRA_GetSubrangeMap(IMLSegment* imlSegment) +{ + return imlSegment->raInfo.linkedList_perVirtualRegister; +} + +raLivenessRange* IMLRA_GetSubrange(IMLSegment* imlSegment, IMLRegID regId) +{ + auto it = imlSegment->raInfo.linkedList_perVirtualRegister.find(regId); + if (it == imlSegment->raInfo.linkedList_perVirtualRegister.end()) + return nullptr; + return it->second; +} + +struct raFixedRegRequirementWithVGPR +{ + raFixedRegRequirementWithVGPR(raInstructionEdge pos, IMLPhysRegisterSet allowedReg, IMLRegID regId) + : pos(pos), allowedReg(allowedReg), regId(regId) {} + + raInstructionEdge pos; + IMLPhysRegisterSet allowedReg; + IMLRegID regId; +}; + +std::vector<raFixedRegRequirementWithVGPR> IMLRA_BuildSegmentInstructionFixedRegList(IMLSegment* imlSegment) +{ + std::vector<raFixedRegRequirementWithVGPR> frrList; + size_t index = 0; + while (index < imlSegment->imlList.size()) + { + IMLFixedRegisters fixedRegs; + GetInstructionFixedRegisters(&imlSegment->imlList[index], fixedRegs); + raInstructionEdge pos; + pos.Set(index, true); + for (auto& fixedRegAccess : fixedRegs.listInput) + { + frrList.emplace_back(pos, fixedRegAccess.physRegSet, fixedRegAccess.reg.IsValid() ? fixedRegAccess.reg.GetRegID() : IMLRegID_INVALID); + } + pos = pos + 1; + for (auto& fixedRegAccess : fixedRegs.listOutput) + { + frrList.emplace_back(pos, fixedRegAccess.physRegSet, fixedRegAccess.reg.IsValid() ? fixedRegAccess.reg.GetRegID() : IMLRegID_INVALID); + } + index++; + } + return frrList; +} + +boost::container::small_vector<raLivenessRange*, 8> IMLRA_GetRangeWithFixedRegReservationOverlappingPos(IMLSegment* imlSegment, raInstructionEdge pos, IMLPhysReg physReg) +{ + boost::container::small_vector<raLivenessRange*, 8> rangeList; + for (raLivenessRange* currentRange = imlSegment->raInfo.linkedList_allSubranges; currentRange; currentRange = currentRange->link_allSegmentRanges.next) + { + if (!currentRange->interval.ContainsEdge(pos)) + continue; + IMLPhysRegisterSet allowedRegs; + if (!currentRange->GetAllowedRegistersEx(allowedRegs)) + continue; + if (allowedRegs.IsAvailable(physReg)) + rangeList.emplace_back(currentRange); + } + return rangeList; +} + +void IMLRA_HandleFixedRegisters(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* imlSegment) +{ + // first pass - iterate over all ranges with fixed register requirements and split them if they cross the segment border + // todo - this pass currently creates suboptimal results by splitting all ranges that cross the segment border if they have any fixed register requirement. This can be avoided in some cases + for (raLivenessRange* currentRange = imlSegment->raInfo.linkedList_allSubranges; currentRange;) + { + IMLPhysRegisterSet allowedRegs; + if(currentRange->list_fixedRegRequirements.empty()) + { + currentRange = currentRange->link_allSegmentRanges.next; + continue; // since we run this pass for every segment we dont need to do global checks here for clusters which may not even have fixed register requirements + } + if (!currentRange->GetAllowedRegistersEx(allowedRegs)) + { + currentRange = currentRange->link_allSegmentRanges.next; + continue; + } + if (currentRange->interval.ExtendsPreviousSegment() || currentRange->interval.ExtendsIntoNextSegment()) + { + raLivenessRange* nextRange = currentRange->link_allSegmentRanges.next; + IMLRA_ExplodeRangeCluster(ppcImlGenContext, currentRange); + currentRange = nextRange; + continue; + } + currentRange = currentRange->link_allSegmentRanges.next; + } + // second pass - look for ranges with conflicting fixed register requirements and split these too (locally) + for (raLivenessRange* currentRange = imlSegment->raInfo.linkedList_allSubranges; currentRange; currentRange = currentRange->link_allSegmentRanges.next) + { + IMLPhysRegisterSet allowedRegs; + if (currentRange->list_fixedRegRequirements.empty()) + continue; // we dont need to check whole clusters because the pass above guarantees that there are no ranges with fixed register requirements that extend outside of this segment + if (!currentRange->GetAllowedRegistersEx(allowedRegs)) + continue; + if (allowedRegs.HasAnyAvailable()) + continue; + cemu_assert_unimplemented(); + } + // third pass - assign fixed registers, split ranges if needed + std::vector<raFixedRegRequirementWithVGPR> frr = IMLRA_BuildSegmentInstructionFixedRegList(imlSegment); + std::unordered_map<IMLPhysReg, IMLRegID> lastVGPR; + for (size_t i = 0; i < frr.size(); i++) + { + raFixedRegRequirementWithVGPR& entry = frr[i]; + // we currently only handle fixed register requirements with a single register + // with one exception: When regId is IMLRegID_INVALID then the entry acts as a list of reserved registers + cemu_assert_debug(entry.regId == IMLRegID_INVALID || entry.allowedReg.HasExactlyOneAvailable()); + for (IMLPhysReg physReg = entry.allowedReg.GetFirstAvailableReg(); physReg >= 0; physReg = entry.allowedReg.GetNextAvailableReg(physReg + 1)) + { + // check if the assigned vGPR has changed + bool vgprHasChanged = false; + auto it = lastVGPR.find(physReg); + if (it != lastVGPR.end()) + vgprHasChanged = it->second != entry.regId; + else + vgprHasChanged = true; + lastVGPR[physReg] = entry.regId; + + if (!vgprHasChanged) + continue; + + boost::container::small_vector<raLivenessRange*, 8> overlappingRanges = IMLRA_GetRangeWithFixedRegReservationOverlappingPos(imlSegment, entry.pos, physReg); + if (entry.regId != IMLRegID_INVALID) + cemu_assert_debug(!overlappingRanges.empty()); // there should always be at least one range that overlaps corresponding to the fixed register requirement, except for IMLRegID_INVALID which is used to indicate reserved registers + + for (auto& range : overlappingRanges) + { + if (range->interval.start < entry.pos) + { + IMLRA_SplitRange(ppcImlGenContext, range, entry.pos, true); + } + } + } + } + // finally iterate ranges and assign fixed registers + for (raLivenessRange* currentRange = imlSegment->raInfo.linkedList_allSubranges; currentRange; currentRange = currentRange->link_allSegmentRanges.next) + { + IMLPhysRegisterSet allowedRegs; + if (currentRange->list_fixedRegRequirements.empty()) + continue; // we dont need to check whole clusters because the pass above guarantees that there are no ranges with fixed register requirements that extend outside of this segment + if (!currentRange->GetAllowedRegistersEx(allowedRegs)) + { + cemu_assert_debug(currentRange->list_fixedRegRequirements.empty()); + continue; + } + cemu_assert_debug(allowedRegs.HasExactlyOneAvailable()); + currentRange->SetPhysicalRegister(allowedRegs.GetFirstAvailableReg()); + } + // DEBUG - check for collisions and make sure all ranges with fixed register requirements got their physical register assigned +#if DEBUG_RA_EXTRA_VALIDATION + for (raLivenessRange* currentRange = imlSegment->raInfo.linkedList_allSubranges; currentRange; currentRange = currentRange->link_allSegmentRanges.next) + { + IMLPhysRegisterSet allowedRegs; + if (!currentRange->HasPhysicalRegister()) + continue; + for (raLivenessRange* currentRange2 = imlSegment->raInfo.linkedList_allSubranges; currentRange2; currentRange2 = currentRange2->link_allSegmentRanges.next) + { + if (currentRange == currentRange2) + continue; + if (currentRange->interval2.IsOverlapping(currentRange2->interval2)) + { + cemu_assert_debug(currentRange->GetPhysicalRegister() != currentRange2->GetPhysicalRegister()); + } + } + } +#endif +} + +// we should not split ranges on instructions with tied registers (i.e. where a register encoded as a single parameter is both input and output) +// otherwise the RA algorithm has to assign both ranges the same physical register (not supported yet) and the point of splitting to fit another range is nullified +void IMLRA_MakeSafeSplitPosition(IMLSegment* imlSegment, raInstructionEdge& pos) +{ + // we ignore the instruction for now and just always make it a safe split position + cemu_assert_debug(pos.IsInstructionIndex()); + if (pos.IsOnOutputEdge()) + pos = pos - 1; +} + +// convenience wrapper for IMLRA_MakeSafeSplitPosition +void IMLRA_MakeSafeSplitDistance(IMLSegment* imlSegment, raInstructionEdge startPos, sint32& distance) +{ + cemu_assert_debug(startPos.IsInstructionIndex()); + cemu_assert_debug(distance >= 0); + raInstructionEdge endPos = startPos + distance; + IMLRA_MakeSafeSplitPosition(imlSegment, endPos); + if (endPos < startPos) + { + distance = 0; + return; + } + distance = endPos.GetRaw() - startPos.GetRaw(); +} + +static void DbgVerifyAllRanges(IMLRegisterAllocatorContext& ctx); + +class RASpillStrategy +{ + public: + virtual void Apply(ppcImlGenContext_t* ctx, IMLSegment* imlSegment, raLivenessRange* currentRange) = 0; + + sint32 GetCost() + { + return strategyCost; + } + + protected: + void ResetCost() + { + strategyCost = INT_MAX; + } + + sint32 strategyCost; +}; + +class RASpillStrategy_LocalRangeHoleCutting : public RASpillStrategy +{ + public: + void Reset() + { + localRangeHoleCutting.distance = -1; + localRangeHoleCutting.largestHoleSubrange = nullptr; + ResetCost(); + } + + void Evaluate(IMLSegment* imlSegment, raLivenessRange* currentRange, const IMLRALivenessTimeline& timeline, const IMLPhysRegisterSet& allowedRegs) + { + raInstructionEdge currentRangeStart = currentRange->interval.start; + sint32 requiredSize2 = currentRange->interval.GetPreciseDistance(); + cemu_assert_debug(localRangeHoleCutting.distance == -1); + cemu_assert_debug(strategyCost == INT_MAX); + if (!currentRangeStart.ConnectsToPreviousSegment()) + { + cemu_assert_debug(currentRangeStart.GetRaw() >= 0); + for (auto candidate : timeline.activeRanges) + { + if (candidate->interval.ExtendsIntoNextSegment()) + continue; + // new checks (Oct 2024): + if (candidate == currentRange) + continue; + if (candidate->GetPhysicalRegister() < 0) + continue; + if (!allowedRegs.IsAvailable(candidate->GetPhysicalRegister())) + continue; + + sint32 distance2 = IMLRA_CountDistanceUntilNextUse(candidate, currentRangeStart); + IMLRA_MakeSafeSplitDistance(imlSegment, currentRangeStart, distance2); + if (distance2 < 2) + continue; + cemu_assert_debug(currentRangeStart.IsInstructionIndex()); + distance2 = std::min<sint32>(distance2, imlSegment->imlList.size() * 2 - currentRangeStart.GetRaw()); // limit distance to end of segment + // calculate split cost of candidate + sint32 cost = IMLRA_CalculateAdditionalCostAfterSplit(candidate, currentRangeStart + distance2); + // calculate additional split cost of currentRange if hole is not large enough + if (distance2 < requiredSize2) + { + cost += IMLRA_CalculateAdditionalCostAfterSplit(currentRange, currentRangeStart + distance2); + // we also slightly increase cost in relation to the remaining length (in order to make the algorithm prefer larger holes) + cost += (requiredSize2 - distance2) / 10; + } + // compare cost with previous candidates + if (cost < strategyCost) + { + strategyCost = cost; + localRangeHoleCutting.distance = distance2; + localRangeHoleCutting.largestHoleSubrange = candidate; + } + } + } + } + + void Apply(ppcImlGenContext_t* ctx, IMLSegment* imlSegment, raLivenessRange* currentRange) override + { + cemu_assert_debug(strategyCost != INT_MAX); + sint32 requiredSize2 = currentRange->interval.GetPreciseDistance(); + raInstructionEdge currentRangeStart = currentRange->interval.start; + + raInstructionEdge holeStartPosition = currentRangeStart; + raInstructionEdge holeEndPosition = currentRangeStart + localRangeHoleCutting.distance; + raLivenessRange* collisionRange = localRangeHoleCutting.largestHoleSubrange; + + if (collisionRange->interval.start < holeStartPosition) + { + collisionRange = IMLRA_SplitRange(nullptr, collisionRange, holeStartPosition, true); + cemu_assert_debug(!collisionRange || collisionRange->interval.start >= holeStartPosition); // verify if splitting worked at all, tail must be on or after the split point + cemu_assert_debug(!collisionRange || collisionRange->interval.start >= holeEndPosition); // also verify that the trimmed hole is actually big enough + } + else + { + cemu_assert_unimplemented(); // we still need to trim? + } + // we may also have to cut the current range to fit partially into the hole + if (requiredSize2 > localRangeHoleCutting.distance) + { + raLivenessRange* tailRange = IMLRA_SplitRange(nullptr, currentRange, currentRangeStart + localRangeHoleCutting.distance, true); + if (tailRange) + { + cemu_assert_debug(tailRange->list_fixedRegRequirements.empty()); // we are not allowed to unassign fixed registers + tailRange->UnsetPhysicalRegister(); + } + } + // verify that the hole is large enough + if (collisionRange) + { + cemu_assert_debug(!collisionRange->interval.IsOverlapping(currentRange->interval)); + } + } + + private: + struct + { + sint32 distance; + raLivenessRange* largestHoleSubrange; + } localRangeHoleCutting; +}; + +class RASpillStrategy_AvailableRegisterHole : public RASpillStrategy +{ + // split current range (this is generally only a good choice when the current range is long but has few usages) + public: + void Reset() + { + ResetCost(); + availableRegisterHole.distance = -1; + availableRegisterHole.physRegister = -1; + } + + void Evaluate(IMLSegment* imlSegment, raLivenessRange* currentRange, const IMLRALivenessTimeline& timeline, const IMLPhysRegisterSet& localAvailableRegsMask, const IMLPhysRegisterSet& allowedRegs) + { + sint32 requiredSize2 = currentRange->interval.GetPreciseDistance(); + + raInstructionEdge currentRangeStart = currentRange->interval.start; + cemu_assert_debug(strategyCost == INT_MAX); + availableRegisterHole.distance = -1; + availableRegisterHole.physRegister = -1; + if (currentRangeStart.GetRaw() >= 0) + { + if (localAvailableRegsMask.HasAnyAvailable()) + { + sint32 physRegItr = -1; + while (true) + { + physRegItr = localAvailableRegsMask.GetNextAvailableReg(physRegItr + 1); + if (physRegItr < 0) + break; + if (!allowedRegs.IsAvailable(physRegItr)) + continue; + // get size of potential hole for this register + sint32 distance = PPCRecRA_countDistanceUntilNextLocalPhysRegisterUse(imlSegment, currentRangeStart, physRegItr); + + // some instructions may require the same register for another range, check the distance here + sint32 distUntilFixedReg = IMLRA_CountDistanceUntilFixedRegUsage(imlSegment, currentRangeStart, distance, currentRange->GetVirtualRegister(), physRegItr); + if (distUntilFixedReg < distance) + distance = distUntilFixedReg; + + IMLRA_MakeSafeSplitDistance(imlSegment, currentRangeStart, distance); + if (distance < 2) + continue; + // calculate additional cost due to split + cemu_assert_debug(distance < requiredSize2); // should always be true otherwise previous step would have selected this register? + sint32 cost = IMLRA_CalculateAdditionalCostAfterSplit(currentRange, currentRangeStart + distance); + // add small additional cost for the remaining range (prefer larger holes) + cost += ((requiredSize2 - distance) / 2) / 10; + if (cost < strategyCost) + { + strategyCost = cost; + availableRegisterHole.distance = distance; + availableRegisterHole.physRegister = physRegItr; + } + } + } + } + } + + void Apply(ppcImlGenContext_t* ctx, IMLSegment* imlSegment, raLivenessRange* currentRange) override + { + cemu_assert_debug(strategyCost != INT_MAX); + raInstructionEdge currentRangeStart = currentRange->interval.start; + // use available register + raLivenessRange* tailRange = IMLRA_SplitRange(nullptr, currentRange, currentRangeStart + availableRegisterHole.distance, true); + if (tailRange) + { + cemu_assert_debug(tailRange->list_fixedRegRequirements.empty()); // we are not allowed to unassign fixed registers + tailRange->UnsetPhysicalRegister(); + } + } + + private: + struct + { + sint32 physRegister; + sint32 distance; // size of hole + } availableRegisterHole; +}; + +class RASpillStrategy_ExplodeRange : public RASpillStrategy +{ + public: + void Reset() + { + ResetCost(); + explodeRange.range = nullptr; + explodeRange.distance = -1; + } + + void Evaluate(IMLSegment* imlSegment, raLivenessRange* currentRange, const IMLRALivenessTimeline& timeline, const IMLPhysRegisterSet& allowedRegs) + { + raInstructionEdge currentRangeStart = currentRange->interval.start; + if (currentRangeStart.ConnectsToPreviousSegment()) + currentRangeStart.Set(0, true); + sint32 requiredSize2 = currentRange->interval.GetPreciseDistance(); + cemu_assert_debug(strategyCost == INT_MAX); + explodeRange.range = nullptr; + explodeRange.distance = -1; + for (auto candidate : timeline.activeRanges) + { + if (!candidate->interval.ExtendsIntoNextSegment()) + continue; + // new checks (Oct 2024): + if (candidate == currentRange) + continue; + if (candidate->GetPhysicalRegister() < 0) + continue; + if (!allowedRegs.IsAvailable(candidate->GetPhysicalRegister())) + continue; + + sint32 distance = IMLRA_CountDistanceUntilNextUse(candidate, currentRangeStart); + IMLRA_MakeSafeSplitDistance(imlSegment, currentRangeStart, distance); + if (distance < 2) + continue; + sint32 cost = IMLRA_CalculateAdditionalCostOfRangeExplode(candidate); + // if the hole is not large enough, add cost of splitting current subrange + if (distance < requiredSize2) + { + cost += IMLRA_CalculateAdditionalCostAfterSplit(currentRange, currentRangeStart + distance); + // add small additional cost for the remaining range (prefer larger holes) + cost += ((requiredSize2 - distance) / 2) / 10; + } + // compare with current best candidate for this strategy + if (cost < strategyCost) + { + strategyCost = cost; + explodeRange.distance = distance; + explodeRange.range = candidate; + } + } + } + + void Apply(ppcImlGenContext_t* ctx, IMLSegment* imlSegment, raLivenessRange* currentRange) override + { + raInstructionEdge currentRangeStart = currentRange->interval.start; + if (currentRangeStart.ConnectsToPreviousSegment()) + currentRangeStart.Set(0, true); + sint32 requiredSize2 = currentRange->interval.GetPreciseDistance(); + // explode range + IMLRA_ExplodeRangeCluster(nullptr, explodeRange.range); + // split current subrange if necessary + if (requiredSize2 > explodeRange.distance) + { + raLivenessRange* tailRange = IMLRA_SplitRange(nullptr, currentRange, currentRangeStart + explodeRange.distance, true); + if (tailRange) + { + cemu_assert_debug(tailRange->list_fixedRegRequirements.empty()); // we are not allowed to unassign fixed registers + tailRange->UnsetPhysicalRegister(); + } + } + } + + private: + struct + { + raLivenessRange* range; + sint32 distance; // size of hole + // note: If we explode a range, we still have to check the size of the hole that becomes available, if too small then we need to add cost of splitting local subrange + } explodeRange; +}; + +class RASpillStrategy_ExplodeRangeInter : public RASpillStrategy +{ + public: + void Reset() + { + ResetCost(); + explodeRange.range = nullptr; + explodeRange.distance = -1; + } + + void Evaluate(IMLSegment* imlSegment, raLivenessRange* currentRange, const IMLRALivenessTimeline& timeline, const IMLPhysRegisterSet& allowedRegs) + { + // explode the range with the least cost + cemu_assert_debug(strategyCost == INT_MAX); + cemu_assert_debug(explodeRange.range == nullptr && explodeRange.distance == -1); + for (auto candidate : timeline.activeRanges) + { + if (!candidate->interval.ExtendsIntoNextSegment()) + continue; + // only select candidates that clash with current subrange + if (candidate->GetPhysicalRegister() < 0 && candidate != currentRange) + continue; + // and also filter any that dont meet fixed register requirements + if (!allowedRegs.IsAvailable(candidate->GetPhysicalRegister())) + continue; + sint32 cost; + cost = IMLRA_CalculateAdditionalCostOfRangeExplode(candidate); + // compare with current best candidate for this strategy + if (cost < strategyCost) + { + strategyCost = cost; + explodeRange.distance = INT_MAX; + explodeRange.range = candidate; + } + } + // add current range as a candidate too + sint32 ownCost; + ownCost = IMLRA_CalculateAdditionalCostOfRangeExplode(currentRange); + if (ownCost < strategyCost) + { + strategyCost = ownCost; + explodeRange.distance = INT_MAX; + explodeRange.range = currentRange; + } + } + + void Apply(ppcImlGenContext_t* ctx, IMLSegment* imlSegment, raLivenessRange* currentRange) override + { + cemu_assert_debug(strategyCost != INT_MAX); + IMLRA_ExplodeRangeCluster(ctx, explodeRange.range); + } + + private: + struct + { + raLivenessRange* range; + sint32 distance; // size of hole + // note: If we explode a range, we still have to check the size of the hole that becomes available, if too small then we need to add cost of splitting local subrange + }explodeRange; +}; + +// filter any registers from candidatePhysRegSet which cannot be used by currentRange due to fixed register requirements within the range that it occupies +void IMLRA_FilterReservedFixedRegisterRequirementsForSegment(IMLRegisterAllocatorContext& ctx, raLivenessRange* currentRange, IMLPhysRegisterSet& candidatePhysRegSet) +{ + IMLSegment* seg = currentRange->imlSegment; + if (seg->imlList.empty()) + return; // there can be no fixed register requirements if there are no instructions + + raInstructionEdge firstPos = currentRange->interval.start; + if (currentRange->interval.start.ConnectsToPreviousSegment()) + firstPos.SetRaw(0); + else if (currentRange->interval.start.ConnectsToNextSegment()) + firstPos.Set(seg->imlList.size() - 1, false); + + raInstructionEdge lastPos = currentRange->interval.end; + if (currentRange->interval.end.ConnectsToPreviousSegment()) + lastPos.SetRaw(0); + else if (currentRange->interval.end.ConnectsToNextSegment()) + lastPos.Set(seg->imlList.size() - 1, false); + cemu_assert_debug(firstPos <= lastPos); + + IMLRegID ourRegId = currentRange->GetVirtualRegister(); + + IMLFixedRegisters fixedRegs; + if (firstPos.IsOnOutputEdge()) + GetInstructionFixedRegisters(seg->imlList.data() + firstPos.GetInstructionIndex(), fixedRegs); + for (raInstructionEdge currentPos = firstPos; currentPos <= lastPos; ++currentPos) + { + if (currentPos.IsOnInputEdge()) + { + GetInstructionFixedRegisters(seg->imlList.data() + currentPos.GetInstructionIndex(), fixedRegs); + } + auto& fixedRegAccess = currentPos.IsOnInputEdge() ? fixedRegs.listInput : fixedRegs.listOutput; + for (auto& fixedRegLoc : fixedRegAccess) + { + if (fixedRegLoc.reg.IsInvalid() || fixedRegLoc.reg.GetRegID() != ourRegId) + candidatePhysRegSet.RemoveRegisters(fixedRegLoc.physRegSet); + } + } +} + +// filter out any registers along the range cluster +void IMLRA_FilterReservedFixedRegisterRequirementsForCluster(IMLRegisterAllocatorContext& ctx, IMLSegment* imlSegment, raLivenessRange* currentRange, IMLPhysRegisterSet& candidatePhysRegSet) +{ + cemu_assert_debug(currentRange->imlSegment == imlSegment); + if (currentRange->interval.ExtendsPreviousSegment() || currentRange->interval.ExtendsIntoNextSegment()) + { + auto clusterRanges = currentRange->GetAllSubrangesInCluster(); + for (auto& rangeIt : clusterRanges) + { + IMLRA_FilterReservedFixedRegisterRequirementsForSegment(ctx, rangeIt, candidatePhysRegSet); + if (!candidatePhysRegSet.HasAnyAvailable()) + break; + } + return; + } + IMLRA_FilterReservedFixedRegisterRequirementsForSegment(ctx, currentRange, candidatePhysRegSet); +} + +bool IMLRA_AssignSegmentRegisters(IMLRegisterAllocatorContext& ctx, ppcImlGenContext_t* ppcImlGenContext, IMLSegment* imlSegment) +{ + // sort subranges ascending by start index + _sortSegmentAllSubrangesLinkedList(imlSegment); + + IMLRALivenessTimeline livenessTimeline; + raLivenessRange* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; + raInstructionEdge lastInstructionEdge; + lastInstructionEdge.SetRaw(RA_INTER_RANGE_END); + + struct + { + RASpillStrategy_LocalRangeHoleCutting localRangeHoleCutting; + RASpillStrategy_AvailableRegisterHole availableRegisterHole; + RASpillStrategy_ExplodeRange explodeRange; + // for ranges that connect to follow up segments: + RASpillStrategy_ExplodeRangeInter explodeRangeInter; + } strategy; + + while (subrangeItr) + { + raInstructionEdge currentRangeStart = subrangeItr->interval.start; // used to be currentIndex before refactor + PPCRecRA_debugValidateSubrange(subrangeItr); + + livenessTimeline.ExpireRanges((currentRangeStart > lastInstructionEdge) ? lastInstructionEdge : currentRangeStart); // expire up to currentIndex (inclusive), but exclude infinite ranges + + // if subrange already has register assigned then add it to the active list and continue + if (subrangeItr->GetPhysicalRegister() >= 0) + { + // verify if register is actually available +#if DEBUG_RA_EXTRA_VALIDATION + for (auto& liverangeItr : livenessTimeline.activeRanges) + { + // check for register mismatch + cemu_assert_debug(liverangeItr->GetPhysicalRegister() != subrangeItr->GetPhysicalRegister()); + } +#endif + livenessTimeline.AddActiveRange(subrangeItr); + subrangeItr = subrangeItr->link_allSegmentRanges.next; + continue; + } + // ranges with fixed register requirements should already have a phys register assigned + if (!subrangeItr->list_fixedRegRequirements.empty()) + { + cemu_assert_debug(subrangeItr->HasPhysicalRegister()); + } + // find free register for current subrangeItr and segment + IMLRegFormat regBaseFormat = ctx.GetBaseFormatByRegId(subrangeItr->GetVirtualRegister()); + IMLPhysRegisterSet candidatePhysRegSet = ctx.raParam->GetPhysRegPool(regBaseFormat); + cemu_assert_debug(candidatePhysRegSet.HasAnyAvailable()); // no valid pool provided for this register type + + IMLPhysRegisterSet allowedRegs = subrangeItr->GetAllowedRegisters(candidatePhysRegSet); + cemu_assert_debug(allowedRegs.HasAnyAvailable()); // if zero regs are available, then this range needs to be split to avoid mismatching register requirements (do this in the initial pass to keep the code here simpler) + candidatePhysRegSet &= allowedRegs; + + for (auto& liverangeItr : livenessTimeline.activeRanges) + { + cemu_assert_debug(liverangeItr->GetPhysicalRegister() >= 0); + candidatePhysRegSet.SetReserved(liverangeItr->GetPhysicalRegister()); + } + // check intersections with other ranges and determine allowed registers + IMLPhysRegisterSet localAvailableRegsMask = candidatePhysRegSet; // mask of registers that are currently not used (does not include range checks in other segments) + if (candidatePhysRegSet.HasAnyAvailable()) + { + // check for overlaps on a global scale (subrangeItr can be part of a larger range cluster across multiple segments) + PPCRecRA_MaskOverlappingPhysRegForGlobalRange(subrangeItr, candidatePhysRegSet); + } + // some target instructions may enforce specific registers (e.g. common on X86 where something like SHL <reg>, CL forces CL as the count register) + // we determine the list of allowed registers here + // this really only works if we assume single-register requirements (otherwise its better not to filter out early and instead allow register corrections later but we don't support this yet) + if (candidatePhysRegSet.HasAnyAvailable()) + { + IMLRA_FilterReservedFixedRegisterRequirementsForCluster(ctx, imlSegment, subrangeItr, candidatePhysRegSet); + } + if (candidatePhysRegSet.HasAnyAvailable()) + { + // use free register + subrangeItr->SetPhysicalRegisterForCluster(candidatePhysRegSet.GetFirstAvailableReg()); + livenessTimeline.AddActiveRange(subrangeItr); + subrangeItr = subrangeItr->link_allSegmentRanges.next; // next + continue; + } + // there is no free register for the entire range + // evaluate different strategies of splitting ranges to free up another register or shorten the current range + strategy.localRangeHoleCutting.Reset(); + strategy.availableRegisterHole.Reset(); + strategy.explodeRange.Reset(); + // cant assign register + // there might be registers available, we just can't use them due to range conflicts + RASpillStrategy* selectedStrategy = nullptr; + auto SelectStrategyIfBetter = [&selectedStrategy](RASpillStrategy& newStrategy) { + if (newStrategy.GetCost() == INT_MAX) + return; + if (selectedStrategy == nullptr || newStrategy.GetCost() < selectedStrategy->GetCost()) + selectedStrategy = &newStrategy; + }; + + if (!subrangeItr->interval.ExtendsIntoNextSegment()) + { + // range ends in current segment, use local strategies + // evaluate strategy: Cut hole into local subrange + strategy.localRangeHoleCutting.Evaluate(imlSegment, subrangeItr, livenessTimeline, allowedRegs); + SelectStrategyIfBetter(strategy.localRangeHoleCutting); + // evaluate strategy: Split current range to fit in available holes + // todo - are checks required to avoid splitting on the suffix instruction? + strategy.availableRegisterHole.Evaluate(imlSegment, subrangeItr, livenessTimeline, localAvailableRegsMask, allowedRegs); + SelectStrategyIfBetter(strategy.availableRegisterHole); + // evaluate strategy: Explode inter-segment ranges + strategy.explodeRange.Evaluate(imlSegment, subrangeItr, livenessTimeline, allowedRegs); + SelectStrategyIfBetter(strategy.explodeRange); + } + else // if subrangeItr->interval2.ExtendsIntoNextSegment() + { + strategy.explodeRangeInter.Reset(); + strategy.explodeRangeInter.Evaluate(imlSegment, subrangeItr, livenessTimeline, allowedRegs); + SelectStrategyIfBetter(strategy.explodeRangeInter); + } + // choose strategy + if (selectedStrategy) + { + selectedStrategy->Apply(ppcImlGenContext, imlSegment, subrangeItr); + } + else + { + // none of the evulated strategies can be applied, this should only happen if the segment extends into the next segment(s) for which we have no good strategy + cemu_assert_debug(subrangeItr->interval.ExtendsPreviousSegment()); + // alternative strategy if we have no other choice: explode current range + IMLRA_ExplodeRangeCluster(ppcImlGenContext, subrangeItr); + } + return false; + } + return true; +} + +void IMLRA_AssignRegisters(IMLRegisterAllocatorContext& ctx, ppcImlGenContext_t* ppcImlGenContext) +{ + // start with frequently executed segments first + sint32 maxLoopDepth = 0; + for (IMLSegment* segIt : ppcImlGenContext->segmentList2) + { + maxLoopDepth = std::max(maxLoopDepth, segIt->loopDepth); + } + // assign fixed registers first + for (IMLSegment* segIt : ppcImlGenContext->segmentList2) + IMLRA_HandleFixedRegisters(ppcImlGenContext, segIt); +#if DEBUG_RA_EXTRA_VALIDATION + // fixed registers are currently handled per-segment, but here we validate that they are assigned correctly on a global scope as well + for (IMLSegment* imlSegment : ppcImlGenContext->segmentList2) + { + for (raLivenessRange* currentRange = imlSegment->raInfo.linkedList_allSubranges; currentRange; currentRange = currentRange->link_allSegmentRanges.next) + { + IMLPhysRegisterSet allowedRegs; + if (!currentRange->GetAllowedRegistersEx(allowedRegs)) + { + cemu_assert_debug(currentRange->list_fixedRegRequirements.empty()); + continue; + } + cemu_assert_debug(currentRange->HasPhysicalRegister() && allowedRegs.IsAvailable(currentRange->GetPhysicalRegister())); + } + } +#endif + + while (true) + { + bool done = false; + for (sint32 d = maxLoopDepth; d >= 0; d--) + { + for (IMLSegment* segIt : ppcImlGenContext->segmentList2) + { + if (segIt->loopDepth != d) + continue; + done = IMLRA_AssignSegmentRegisters(ctx, ppcImlGenContext, segIt); + if (done == false) + break; + } + if (done == false) + break; + } + if (done) + break; + } +} + +void IMLRA_ReshapeForRegisterAllocation(ppcImlGenContext_t* ppcImlGenContext) +{ + // insert empty segments after every non-taken branch if the linked segment has more than one input + // this gives the register allocator more room to create efficient spill code + size_t segmentIndex = 0; + while (segmentIndex < ppcImlGenContext->segmentList2.size()) + { + IMLSegment* imlSegment = ppcImlGenContext->segmentList2[segmentIndex]; + if (imlSegment->nextSegmentIsUncertain) + { + segmentIndex++; + continue; + } + if (imlSegment->nextSegmentBranchTaken == nullptr || imlSegment->nextSegmentBranchNotTaken == nullptr) + { + segmentIndex++; + continue; + } + if (imlSegment->nextSegmentBranchNotTaken->list_prevSegments.size() <= 1) + { + segmentIndex++; + continue; + } + if (imlSegment->nextSegmentBranchNotTaken->isEnterable) + { + segmentIndex++; + continue; + } + PPCRecompilerIml_insertSegments(ppcImlGenContext, segmentIndex + 1, 1); + IMLSegment* imlSegmentP0 = ppcImlGenContext->segmentList2[segmentIndex + 0]; + IMLSegment* imlSegmentP1 = ppcImlGenContext->segmentList2[segmentIndex + 1]; + IMLSegment* nextSegment = imlSegment->nextSegmentBranchNotTaken; + IMLSegment_RemoveLink(imlSegmentP0, nextSegment); + IMLSegment_SetLinkBranchNotTaken(imlSegmentP1, nextSegment); + IMLSegment_SetLinkBranchNotTaken(imlSegmentP0, imlSegmentP1); + segmentIndex++; + } + // detect loops + for (size_t s = 0; s < ppcImlGenContext->segmentList2.size(); s++) + { + IMLSegment* imlSegment = ppcImlGenContext->segmentList2[s]; + imlSegment->momentaryIndex = s; + } + for (size_t s = 0; s < ppcImlGenContext->segmentList2.size(); s++) + { + IMLSegment* imlSegment = ppcImlGenContext->segmentList2[s]; + IMLRA_IdentifyLoop(ppcImlGenContext, imlSegment); + } +} + +IMLRARegAbstractLiveness* _GetAbstractRange(IMLRegisterAllocatorContext& ctx, IMLSegment* imlSegment, IMLRegID regId) +{ + auto& segMap = ctx.GetSegmentAbstractRangeMap(imlSegment); + auto it = segMap.find(regId); + return it != segMap.end() ? &it->second : nullptr; +} + +// scan instructions and establish register usage range for segment +void IMLRA_CalculateSegmentMinMaxAbstractRanges(IMLRegisterAllocatorContext& ctx, IMLSegment* imlSegment) +{ + size_t instructionIndex = 0; + IMLUsedRegisters gprTracking; + auto& segDistMap = ctx.GetSegmentAbstractRangeMap(imlSegment); + while (instructionIndex < imlSegment->imlList.size()) + { + imlSegment->imlList[instructionIndex].CheckRegisterUsage(&gprTracking); + gprTracking.ForEachAccessedGPR([&](IMLReg gprReg, bool isWritten) { + IMLRegID gprId = gprReg.GetRegID(); + auto it = segDistMap.find(gprId); + if (it == segDistMap.end()) + { + segDistMap.try_emplace(gprId, gprReg.GetBaseFormat(), (sint32)instructionIndex, (sint32)instructionIndex + 1); + ctx.regIdToBaseFormat.try_emplace(gprId, gprReg.GetBaseFormat()); + } + else + { + it->second.TrackInstruction(instructionIndex); +#ifdef CEMU_DEBUG_ASSERT + cemu_assert_debug(ctx.regIdToBaseFormat[gprId] == gprReg.GetBaseFormat()); // the base type per register always has to be the same +#endif + } + }); + instructionIndex++; + } +} + +void IMLRA_CalculateLivenessRanges(IMLRegisterAllocatorContext& ctx) +{ + // for each register calculate min/max index of usage range within each segment + size_t dbgIndex = 0; + for (IMLSegment* segIt : ctx.deprGenContext->segmentList2) + { + cemu_assert_debug(segIt->momentaryIndex == dbgIndex); + IMLRA_CalculateSegmentMinMaxAbstractRanges(ctx, segIt); + dbgIndex++; + } +} + +raLivenessRange* PPCRecRA_convertToMappedRanges(IMLRegisterAllocatorContext& ctx, IMLSegment* imlSegment, IMLRegID vGPR, IMLName name) +{ + IMLRARegAbstractLiveness* abstractRange = _GetAbstractRange(ctx, imlSegment, vGPR); + if (!abstractRange) + return nullptr; + if (abstractRange->isProcessed) + { + // return already existing segment + raLivenessRange* existingRange = IMLRA_GetSubrange(imlSegment, vGPR); + cemu_assert_debug(existingRange); + return existingRange; + } + abstractRange->isProcessed = true; + // create subrange + cemu_assert_debug(IMLRA_GetSubrange(imlSegment, vGPR) == nullptr); + cemu_assert_debug( + (abstractRange->usageStart == abstractRange->usageEnd && (abstractRange->usageStart == RA_INTER_RANGE_START || abstractRange->usageStart == RA_INTER_RANGE_END)) || + abstractRange->usageStart < abstractRange->usageEnd); // usageEnd is exclusive so it should always be larger + sint32 inclusiveEnd = abstractRange->usageEnd; + if (inclusiveEnd != RA_INTER_RANGE_START && inclusiveEnd != RA_INTER_RANGE_END) + inclusiveEnd--; // subtract one, because usageEnd is exclusive, but the end value of the interval passed to createSubrange is inclusive + raInterval interval; + interval.SetInterval(abstractRange->usageStart, true, inclusiveEnd, true); + raLivenessRange* subrange = IMLRA_CreateRange(ctx.deprGenContext, imlSegment, vGPR, name, interval.start, interval.end); + // traverse forward + if (abstractRange->usageEnd == RA_INTER_RANGE_END) + { + if (imlSegment->nextSegmentBranchTaken) + { + IMLRARegAbstractLiveness* branchTakenRange = _GetAbstractRange(ctx, imlSegment->nextSegmentBranchTaken, vGPR); + if (branchTakenRange && branchTakenRange->usageStart == RA_INTER_RANGE_START) + { + subrange->subrangeBranchTaken = PPCRecRA_convertToMappedRanges(ctx, imlSegment->nextSegmentBranchTaken, vGPR, name); + subrange->subrangeBranchTaken->previousRanges.push_back(subrange); + cemu_assert_debug(subrange->subrangeBranchTaken->interval.ExtendsPreviousSegment()); + } + } + if (imlSegment->nextSegmentBranchNotTaken) + { + IMLRARegAbstractLiveness* branchNotTakenRange = _GetAbstractRange(ctx, imlSegment->nextSegmentBranchNotTaken, vGPR); + if (branchNotTakenRange && branchNotTakenRange->usageStart == RA_INTER_RANGE_START) + { + subrange->subrangeBranchNotTaken = PPCRecRA_convertToMappedRanges(ctx, imlSegment->nextSegmentBranchNotTaken, vGPR, name); + subrange->subrangeBranchNotTaken->previousRanges.push_back(subrange); + cemu_assert_debug(subrange->subrangeBranchNotTaken->interval.ExtendsPreviousSegment()); + } + } + } + // traverse backward + if (abstractRange->usageStart == RA_INTER_RANGE_START) + { + for (auto& it : imlSegment->list_prevSegments) + { + IMLRARegAbstractLiveness* prevRange = _GetAbstractRange(ctx, it, vGPR); + if (!prevRange) + continue; + if (prevRange->usageEnd == RA_INTER_RANGE_END) + PPCRecRA_convertToMappedRanges(ctx, it, vGPR, name); + } + } + return subrange; +} + +void IMLRA_UpdateOrAddSubrangeLocation(raLivenessRange* subrange, raInstructionEdge pos) +{ + if (subrange->list_accessLocations.empty()) + { + subrange->list_accessLocations.emplace_back(pos); + return; + } + if(subrange->list_accessLocations.back().pos == pos) + return; + cemu_assert_debug(subrange->list_accessLocations.back().pos < pos); + subrange->list_accessLocations.emplace_back(pos); +} + +// take abstract range data and create LivenessRanges +void IMLRA_ConvertAbstractToLivenessRanges(IMLRegisterAllocatorContext& ctx, IMLSegment* imlSegment) +{ + const std::unordered_map<IMLRegID, raLivenessRange*>& regToSubrange = IMLRA_GetSubrangeMap(imlSegment); + + auto AddOrUpdateFixedRegRequirement = [&](IMLRegID regId, sint32 instructionIndex, bool isInput, const IMLPhysRegisterSet& physRegSet) { + raLivenessRange* subrange = regToSubrange.find(regId)->second; + cemu_assert_debug(subrange); + raFixedRegRequirement tmp; + tmp.pos.Set(instructionIndex, isInput); + tmp.allowedReg = physRegSet; + if (subrange->list_fixedRegRequirements.empty() || subrange->list_fixedRegRequirements.back().pos != tmp.pos) + subrange->list_fixedRegRequirements.push_back(tmp); + }; + + // convert abstract min-max ranges to liveness range objects + auto& segMap = ctx.GetSegmentAbstractRangeMap(imlSegment); + for (auto& it : segMap) + { + if (it.second.isProcessed) + continue; + IMLRegID regId = it.first; + PPCRecRA_convertToMappedRanges(ctx, imlSegment, regId, ctx.raParam->regIdToName.find(regId)->second); + } + // fill created ranges with read/write location indices + // note that at this point there is only one range per register per segment + // and the algorithm below relies on this + size_t index = 0; + IMLUsedRegisters gprTracking; + while (index < imlSegment->imlList.size()) + { + imlSegment->imlList[index].CheckRegisterUsage(&gprTracking); + raInstructionEdge pos((sint32)index, true); + gprTracking.ForEachReadGPR([&](IMLReg gprReg) { + IMLRegID gprId = gprReg.GetRegID(); + raLivenessRange* subrange = regToSubrange.find(gprId)->second; + IMLRA_UpdateOrAddSubrangeLocation(subrange, pos); + }); + pos = {(sint32)index, false}; + gprTracking.ForEachWrittenGPR([&](IMLReg gprReg) { + IMLRegID gprId = gprReg.GetRegID(); + raLivenessRange* subrange = regToSubrange.find(gprId)->second; + IMLRA_UpdateOrAddSubrangeLocation(subrange, pos); + }); + // check fixed register requirements + IMLFixedRegisters fixedRegs; + GetInstructionFixedRegisters(&imlSegment->imlList[index], fixedRegs); + for (auto& fixedRegAccess : fixedRegs.listInput) + { + if (fixedRegAccess.reg != IMLREG_INVALID) + AddOrUpdateFixedRegRequirement(fixedRegAccess.reg.GetRegID(), index, true, fixedRegAccess.physRegSet); + } + for (auto& fixedRegAccess : fixedRegs.listOutput) + { + if (fixedRegAccess.reg != IMLREG_INVALID) + AddOrUpdateFixedRegRequirement(fixedRegAccess.reg.GetRegID(), index, false, fixedRegAccess.physRegSet); + } + index++; + } +} + +void IMLRA_extendAbstractRangeToEndOfSegment(IMLRegisterAllocatorContext& ctx, IMLSegment* imlSegment, IMLRegID regId) +{ + auto& segDistMap = ctx.GetSegmentAbstractRangeMap(imlSegment); + auto it = segDistMap.find(regId); + if (it == segDistMap.end()) + { + sint32 startIndex; + if (imlSegment->HasSuffixInstruction()) + startIndex = imlSegment->GetSuffixInstructionIndex(); + else + startIndex = RA_INTER_RANGE_END; + segDistMap.try_emplace((IMLRegID)regId, IMLRegFormat::INVALID_FORMAT, startIndex, RA_INTER_RANGE_END); + } + else + { + it->second.usageEnd = RA_INTER_RANGE_END; + } +} + +void IMLRA_extendAbstractRangeToBeginningOfSegment(IMLRegisterAllocatorContext& ctx, IMLSegment* imlSegment, IMLRegID regId) +{ + auto& segDistMap = ctx.GetSegmentAbstractRangeMap(imlSegment); + auto it = segDistMap.find(regId); + if (it == segDistMap.end()) + { + segDistMap.try_emplace((IMLRegID)regId, IMLRegFormat::INVALID_FORMAT, RA_INTER_RANGE_START, RA_INTER_RANGE_START); + } + else + { + it->second.usageStart = RA_INTER_RANGE_START; + } + // propagate backwards + for (auto& it : imlSegment->list_prevSegments) + { + IMLRA_extendAbstractRangeToEndOfSegment(ctx, it, regId); + } +} + +void IMLRA_connectAbstractRanges(IMLRegisterAllocatorContext& ctx, IMLRegID regId, IMLSegment** route, sint32 routeDepth) +{ +#ifdef CEMU_DEBUG_ASSERT + if (routeDepth < 2) + assert_dbg(); +#endif + // extend starting range to end of segment + IMLRA_extendAbstractRangeToEndOfSegment(ctx, route[0], regId); + // extend all the connecting segments in both directions + for (sint32 i = 1; i < (routeDepth - 1); i++) + { + IMLRA_extendAbstractRangeToEndOfSegment(ctx, route[i], regId); + IMLRA_extendAbstractRangeToBeginningOfSegment(ctx, route[i], regId); + } + // extend the final segment towards the beginning + IMLRA_extendAbstractRangeToBeginningOfSegment(ctx, route[routeDepth - 1], regId); +} + +void _IMLRA_checkAndTryExtendRange(IMLRegisterAllocatorContext& ctx, IMLSegment* currentSegment, IMLRegID regID, sint32 distanceLeft, IMLSegment** route, sint32 routeDepth) +{ + if (routeDepth >= 64) + { + cemuLog_logDebug(LogType::Force, "Recompiler RA route maximum depth exceeded\n"); + return; + } + route[routeDepth] = currentSegment; + + IMLRARegAbstractLiveness* range = _GetAbstractRange(ctx, currentSegment, regID); + + if (!range) + { + // measure distance over entire segment + distanceLeft -= (sint32)currentSegment->imlList.size(); + if (distanceLeft > 0) + { + if (currentSegment->nextSegmentBranchNotTaken) + _IMLRA_checkAndTryExtendRange(ctx, currentSegment->nextSegmentBranchNotTaken, regID, distanceLeft, route, routeDepth + 1); + if (currentSegment->nextSegmentBranchTaken) + _IMLRA_checkAndTryExtendRange(ctx, currentSegment->nextSegmentBranchTaken, regID, distanceLeft, route, routeDepth + 1); + } + return; + } + else + { + // measure distance to range + if (range->usageStart == RA_INTER_RANGE_END) + { + if (distanceLeft < (sint32)currentSegment->imlList.size()) + return; // range too far away + } + else if (range->usageStart != RA_INTER_RANGE_START && range->usageStart > distanceLeft) + return; // out of range + // found close range -> connect ranges + IMLRA_connectAbstractRanges(ctx, regID, route, routeDepth + 1); + } +} + +void PPCRecRA_checkAndTryExtendRange(IMLRegisterAllocatorContext& ctx, IMLSegment* currentSegment, IMLRARegAbstractLiveness* range, IMLRegID regID) +{ + cemu_assert_debug(range->usageEnd >= 0); + // count instructions to end of initial segment + sint32 instructionsUntilEndOfSeg; + if (range->usageEnd == RA_INTER_RANGE_END) + instructionsUntilEndOfSeg = 0; + else + instructionsUntilEndOfSeg = (sint32)currentSegment->imlList.size() - range->usageEnd; + cemu_assert_debug(instructionsUntilEndOfSeg >= 0); + sint32 remainingScanDist = 45 - instructionsUntilEndOfSeg; + if (remainingScanDist <= 0) + return; // can't reach end + + IMLSegment* route[64]; + route[0] = currentSegment; + if (currentSegment->nextSegmentBranchNotTaken) + _IMLRA_checkAndTryExtendRange(ctx, currentSegment->nextSegmentBranchNotTaken, regID, remainingScanDist, route, 1); + if (currentSegment->nextSegmentBranchTaken) + _IMLRA_checkAndTryExtendRange(ctx, currentSegment->nextSegmentBranchTaken, regID, remainingScanDist, route, 1); +} + +void PPCRecRA_mergeCloseRangesForSegmentV2(IMLRegisterAllocatorContext& ctx, IMLSegment* imlSegment) +{ + auto& segMap = ctx.GetSegmentAbstractRangeMap(imlSegment); + for (auto& it : segMap) + { + PPCRecRA_checkAndTryExtendRange(ctx, imlSegment, &(it.second), it.first); + } +#ifdef CEMU_DEBUG_ASSERT + if (imlSegment->list_prevSegments.empty() == false && imlSegment->isEnterable) + assert_dbg(); + if ((imlSegment->nextSegmentBranchNotTaken != nullptr || imlSegment->nextSegmentBranchTaken != nullptr) && imlSegment->nextSegmentIsUncertain) + assert_dbg(); +#endif +} + +void PPCRecRA_followFlowAndExtendRanges(IMLRegisterAllocatorContext& ctx, IMLSegment* imlSegment) +{ + std::vector<IMLSegment*> list_segments; + std::vector<bool> list_processedSegment; + size_t segmentCount = ctx.deprGenContext->segmentList2.size(); + list_segments.reserve(segmentCount + 1); + list_processedSegment.resize(segmentCount); + + auto markSegProcessed = [&list_processedSegment](IMLSegment* seg) { + list_processedSegment[seg->momentaryIndex] = true; + }; + auto isSegProcessed = [&list_processedSegment](IMLSegment* seg) -> bool { + return list_processedSegment[seg->momentaryIndex]; + }; + markSegProcessed(imlSegment); + + sint32 index = 0; + list_segments.push_back(imlSegment); + while (index < list_segments.size()) + { + IMLSegment* currentSegment = list_segments[index]; + PPCRecRA_mergeCloseRangesForSegmentV2(ctx, currentSegment); + // follow flow + if (currentSegment->nextSegmentBranchNotTaken && !isSegProcessed(currentSegment->nextSegmentBranchNotTaken)) + { + markSegProcessed(currentSegment->nextSegmentBranchNotTaken); + list_segments.push_back(currentSegment->nextSegmentBranchNotTaken); + } + if (currentSegment->nextSegmentBranchTaken && !isSegProcessed(currentSegment->nextSegmentBranchTaken)) + { + markSegProcessed(currentSegment->nextSegmentBranchTaken); + list_segments.push_back(currentSegment->nextSegmentBranchTaken); + } + index++; + } +} + +void IMLRA_MergeCloseAbstractRanges(IMLRegisterAllocatorContext& ctx) +{ + for (size_t s = 0; s < ctx.deprGenContext->segmentList2.size(); s++) + { + IMLSegment* imlSegment = ctx.deprGenContext->segmentList2[s]; + if (!imlSegment->list_prevSegments.empty()) + continue; // not an entry/standalone segment + PPCRecRA_followFlowAndExtendRanges(ctx, imlSegment); + } +} + +void IMLRA_ExtendAbstractRangesOutOfLoops(IMLRegisterAllocatorContext& ctx) +{ + for (size_t s = 0; s < ctx.deprGenContext->segmentList2.size(); s++) + { + IMLSegment* imlSegment = ctx.deprGenContext->segmentList2[s]; + auto localLoopDepth = imlSegment->loopDepth; + if (localLoopDepth <= 0) + continue; // not inside a loop + // look for loop exit + bool hasLoopExit = false; + if (imlSegment->nextSegmentBranchTaken && imlSegment->nextSegmentBranchTaken->loopDepth < localLoopDepth) + { + hasLoopExit = true; + } + if (imlSegment->nextSegmentBranchNotTaken && imlSegment->nextSegmentBranchNotTaken->loopDepth < localLoopDepth) + { + hasLoopExit = true; + } + if (hasLoopExit == false) + continue; + + // extend looping ranges into all exits (this allows the data flow analyzer to move stores out of the loop) + auto& segMap = ctx.GetSegmentAbstractRangeMap(imlSegment); + for (auto& it : segMap) + { + if (it.second.usageEnd != RA_INTER_RANGE_END) + continue; + if (imlSegment->nextSegmentBranchTaken) + IMLRA_extendAbstractRangeToBeginningOfSegment(ctx, imlSegment->nextSegmentBranchTaken, it.first); + if (imlSegment->nextSegmentBranchNotTaken) + IMLRA_extendAbstractRangeToBeginningOfSegment(ctx, imlSegment->nextSegmentBranchNotTaken, it.first); + } + } +} + +void IMLRA_ProcessFlowAndCalculateLivenessRanges(IMLRegisterAllocatorContext& ctx) +{ + IMLRA_MergeCloseAbstractRanges(ctx); + // extra pass to move register loads and stores out of loops + IMLRA_ExtendAbstractRangesOutOfLoops(ctx); + // calculate liveness ranges + for (auto& segIt : ctx.deprGenContext->segmentList2) + IMLRA_ConvertAbstractToLivenessRanges(ctx, segIt); +} + +void IMLRA_AnalyzeSubrangeDataDependency(raLivenessRange* subrange) +{ + bool isRead = false; + bool isWritten = false; + bool isOverwritten = false; + for (auto& location : subrange->list_accessLocations) + { + if (location.IsRead()) + { + isRead = true; + } + if (location.IsWrite()) + { + if (isRead == false) + isOverwritten = true; + isWritten = true; + } + } + subrange->_noLoad = isOverwritten; + subrange->hasStore = isWritten; + + if (subrange->interval.ExtendsPreviousSegment()) + subrange->_noLoad = true; +} + +struct subrangeEndingInfo_t +{ + raLivenessRange* subrangeList[SUBRANGE_LIST_SIZE]; + sint32 subrangeCount; + + bool hasUndefinedEndings; +}; + +void _findSubrangeWriteEndings(raLivenessRange* subrange, uint32 iterationIndex, sint32 depth, subrangeEndingInfo_t* info) +{ + if (depth >= 30) + { + info->hasUndefinedEndings = true; + return; + } + if (subrange->lastIterationIndex == iterationIndex) + return; // already processed + subrange->lastIterationIndex = iterationIndex; + if (subrange->hasStoreDelayed) + return; // no need to traverse this subrange + IMLSegment* imlSegment = subrange->imlSegment; + if (!subrange->interval.ExtendsIntoNextSegment()) + { + // ending segment + if (info->subrangeCount >= SUBRANGE_LIST_SIZE) + { + info->hasUndefinedEndings = true; + return; + } + else + { + info->subrangeList[info->subrangeCount] = subrange; + info->subrangeCount++; + } + return; + } + + // traverse next subranges in flow + if (imlSegment->nextSegmentBranchNotTaken) + { + if (subrange->subrangeBranchNotTaken == nullptr) + { + info->hasUndefinedEndings = true; + } + else + { + _findSubrangeWriteEndings(subrange->subrangeBranchNotTaken, iterationIndex, depth + 1, info); + } + } + if (imlSegment->nextSegmentBranchTaken) + { + if (subrange->subrangeBranchTaken == nullptr) + { + info->hasUndefinedEndings = true; + } + else + { + _findSubrangeWriteEndings(subrange->subrangeBranchTaken, iterationIndex, depth + 1, info); + } + } +} + +static void IMLRA_AnalyzeRangeDataFlow(raLivenessRange* subrange) +{ + if (!subrange->interval.ExtendsIntoNextSegment()) + return; + // analyze data flow across segments (if this segment has writes) + if (subrange->hasStore) + { + subrangeEndingInfo_t writeEndingInfo; + writeEndingInfo.subrangeCount = 0; + writeEndingInfo.hasUndefinedEndings = false; + _findSubrangeWriteEndings(subrange, IMLRA_GetNextIterationIndex(), 0, &writeEndingInfo); + if (writeEndingInfo.hasUndefinedEndings == false) + { + // get cost of delaying store into endings + sint32 delayStoreCost = 0; + bool alreadyStoredInAllEndings = true; + for (sint32 i = 0; i < writeEndingInfo.subrangeCount; i++) + { + raLivenessRange* subrangeItr = writeEndingInfo.subrangeList[i]; + if (subrangeItr->hasStore) + continue; // this ending already stores, no extra cost + alreadyStoredInAllEndings = false; + sint32 storeCost = IMLRA_GetSegmentReadWriteCost(subrangeItr->imlSegment); + delayStoreCost = std::max(storeCost, delayStoreCost); + } + if (alreadyStoredInAllEndings) + { + subrange->hasStore = false; + subrange->hasStoreDelayed = true; + } + else if (delayStoreCost <= IMLRA_GetSegmentReadWriteCost(subrange->imlSegment)) + { + subrange->hasStore = false; + subrange->hasStoreDelayed = true; + for (sint32 i = 0; i < writeEndingInfo.subrangeCount; i++) + { + raLivenessRange* subrangeItr = writeEndingInfo.subrangeList[i]; + subrangeItr->hasStore = true; + } + } + } + } +} + +void IMLRA_AnalyzeRangeDataFlow(ppcImlGenContext_t* ppcImlGenContext) +{ + // this function is called after _AssignRegisters(), which means that all liveness ranges are already final and must not be modified anymore + // track read/write dependencies per segment + for (auto& seg : ppcImlGenContext->segmentList2) + { + raLivenessRange* subrange = seg->raInfo.linkedList_allSubranges; + while (subrange) + { + IMLRA_AnalyzeSubrangeDataDependency(subrange); + subrange = subrange->link_allSegmentRanges.next; + } + } + // propagate information across segment boundaries + for (auto& seg : ppcImlGenContext->segmentList2) + { + raLivenessRange* subrange = seg->raInfo.linkedList_allSubranges; + while (subrange) + { + IMLRA_AnalyzeRangeDataFlow(subrange); + subrange = subrange->link_allSegmentRanges.next; + } + } +} + +/* Generate move instructions */ + +inline IMLReg _MakeNativeReg(IMLRegFormat baseFormat, IMLRegID regId) +{ + return IMLReg(baseFormat, baseFormat, 0, regId); +} + +// prepass for IMLRA_GenerateSegmentMoveInstructions which updates all virtual registers to their physical counterparts +void IMLRA_RewriteRegisters(IMLRegisterAllocatorContext& ctx, IMLSegment* imlSegment) +{ + std::unordered_map<IMLRegID, IMLRegID> virtId2PhysReg; + boost::container::small_vector<raLivenessRange*, 64> activeRanges; + raLivenessRange* currentRange = imlSegment->raInfo.linkedList_allSubranges; + raInstructionEdge currentEdge; + for (size_t i = 0; i < imlSegment->imlList.size(); i++) + { + currentEdge.Set(i, false); // set to instruction index on output edge + // activate ranges which begin before or during this instruction + while (currentRange && currentRange->interval.start <= currentEdge) + { + cemu_assert_debug(virtId2PhysReg.find(currentRange->GetVirtualRegister()) == virtId2PhysReg.end() || virtId2PhysReg[currentRange->GetVirtualRegister()] == currentRange->GetPhysicalRegister()); // check for register conflict + + virtId2PhysReg[currentRange->GetVirtualRegister()] = currentRange->GetPhysicalRegister(); + activeRanges.push_back(currentRange); + currentRange = currentRange->link_allSegmentRanges.next; + } + // rewrite registers + imlSegment->imlList[i].RewriteGPR(virtId2PhysReg); + // deactivate ranges which end during this instruction + auto it = activeRanges.begin(); + while (it != activeRanges.end()) + { + if ((*it)->interval.end <= currentEdge) + { + virtId2PhysReg.erase((*it)->GetVirtualRegister()); + it = activeRanges.erase(it); + } + else + ++it; + } + } +} + +void IMLRA_GenerateSegmentMoveInstructions2(IMLRegisterAllocatorContext& ctx, IMLSegment* imlSegment) +{ + IMLRA_RewriteRegisters(ctx, imlSegment); + +#if DEBUG_RA_INSTRUCTION_GEN + cemuLog_log(LogType::Force, ""); + cemuLog_log(LogType::Force, "[Seg before RA]"); + IMLDebug_DumpSegment(nullptr, imlSegment, true); +#endif + + bool hadSuffixInstruction = imlSegment->HasSuffixInstruction(); + + std::vector<IMLInstruction> rebuiltInstructions; + sint32 numInstructionsWithoutSuffix = (sint32)imlSegment->imlList.size() - (imlSegment->HasSuffixInstruction() ? 1 : 0); + + if (imlSegment->imlList.empty()) + { + // empty segments need special handling (todo - look into merging this with the core logic below eventually) + // store all ranges + raLivenessRange* currentRange = imlSegment->raInfo.linkedList_allSubranges; + while (currentRange) + { + if (currentRange->hasStore) + rebuiltInstructions.emplace_back().make_name_r(currentRange->GetName(), _MakeNativeReg(ctx.regIdToBaseFormat[currentRange->GetVirtualRegister()], currentRange->GetPhysicalRegister())); + currentRange = currentRange->link_allSegmentRanges.next; + } + // load ranges + currentRange = imlSegment->raInfo.linkedList_allSubranges; + while (currentRange) + { + if (!currentRange->_noLoad) + { + cemu_assert_debug(currentRange->interval.ExtendsIntoNextSegment()); + rebuiltInstructions.emplace_back().make_r_name(_MakeNativeReg(ctx.regIdToBaseFormat[currentRange->GetVirtualRegister()], currentRange->GetPhysicalRegister()), currentRange->GetName()); + } + currentRange = currentRange->link_allSegmentRanges.next; + } + imlSegment->imlList = std::move(rebuiltInstructions); + return; + } + + // make sure that no range exceeds the suffix instruction input edge except if they need to be loaded for the next segment (todo - for those, set the start point accordingly?) + { + raLivenessRange* currentRange = imlSegment->raInfo.linkedList_allSubranges; + raInstructionEdge edge; + if (imlSegment->HasSuffixInstruction()) + edge.Set(numInstructionsWithoutSuffix, true); + else + edge.Set(numInstructionsWithoutSuffix - 1, false); + + while (currentRange) + { + if (!currentRange->interval.IsNextSegmentOnly() && currentRange->interval.end > edge) + { + currentRange->interval.SetEnd(edge); + } + currentRange = currentRange->link_allSegmentRanges.next; + } + } + +#if DEBUG_RA_INSTRUCTION_GEN + cemuLog_log(LogType::Force, ""); + cemuLog_log(LogType::Force, "--- Intermediate liveness info ---"); + { + raLivenessRange* dbgRange = imlSegment->raInfo.linkedList_allSubranges; + while (dbgRange) + { + cemuLog_log(LogType::Force, "Range i{}: {}-{}", dbgRange->GetVirtualRegister(), dbgRange->interval2.start.GetDebugString(), dbgRange->interval2.end.GetDebugString()); + dbgRange = dbgRange->link_allSegmentRanges.next; + } + } +#endif + + boost::container::small_vector<raLivenessRange*, 64> activeRanges; + // first we add all the ranges that extend from the previous segment, some of these will end immediately at the first instruction so we might need to store them early + raLivenessRange* currentRange = imlSegment->raInfo.linkedList_allSubranges; + // make all ranges active that start on RA_INTER_RANGE_START + while (currentRange && currentRange->interval.start.ConnectsToPreviousSegment()) + { + activeRanges.push_back(currentRange); + currentRange = currentRange->link_allSegmentRanges.next; + } + // store all ranges that end before the first output edge (includes RA_INTER_RANGE_START) + auto it = activeRanges.begin(); + raInstructionEdge firstOutputEdge; + firstOutputEdge.Set(0, false); + while (it != activeRanges.end()) + { + if ((*it)->interval.end < firstOutputEdge) + { + raLivenessRange* storedRange = *it; + if (storedRange->hasStore) + rebuiltInstructions.emplace_back().make_name_r(storedRange->GetName(), _MakeNativeReg(ctx.regIdToBaseFormat[storedRange->GetVirtualRegister()], storedRange->GetPhysicalRegister())); + it = activeRanges.erase(it); + continue; + } + ++it; + } + + sint32 numInstructions = (sint32)imlSegment->imlList.size(); + for (sint32 i = 0; i < numInstructions; i++) + { + raInstructionEdge curEdge; + // input edge + curEdge.SetRaw(i * 2 + 1); // +1 to include ranges that start at the output of the instruction + while (currentRange && currentRange->interval.start <= curEdge) + { + if (!currentRange->_noLoad) + { + rebuiltInstructions.emplace_back().make_r_name(_MakeNativeReg(ctx.regIdToBaseFormat[currentRange->GetVirtualRegister()], currentRange->GetPhysicalRegister()), currentRange->GetName()); + } + activeRanges.push_back(currentRange); + currentRange = currentRange->link_allSegmentRanges.next; + } + // copy instruction + rebuiltInstructions.push_back(imlSegment->imlList[i]); + // output edge + curEdge.SetRaw(i * 2 + 1 + 1); + // also store ranges that end on the next input edge, we handle this by adding an extra 1 above + auto it = activeRanges.begin(); + while (it != activeRanges.end()) + { + if ((*it)->interval.end <= curEdge) + { + // range expires + // todo - check hasStore + raLivenessRange* storedRange = *it; + if (storedRange->hasStore) + { + cemu_assert_debug(i != numInstructionsWithoutSuffix); // not allowed to emit after suffix + rebuiltInstructions.emplace_back().make_name_r(storedRange->GetName(), _MakeNativeReg(ctx.regIdToBaseFormat[storedRange->GetVirtualRegister()], storedRange->GetPhysicalRegister())); + } + it = activeRanges.erase(it); + continue; + } + ++it; + } + } + // if there is no suffix instruction we currently need to handle the final loads here + cemu_assert_debug(hadSuffixInstruction == imlSegment->HasSuffixInstruction()); + if (imlSegment->HasSuffixInstruction()) + { + cemu_assert_debug(!currentRange); // currentRange should be NULL? + for (auto& remainingRange : activeRanges) + { + cemu_assert_debug(!remainingRange->hasStore); + } + } + else + { + for (auto& remainingRange : activeRanges) + { + cemu_assert_debug(!remainingRange->hasStore); // this range still needs to be stored + } + while (currentRange) + { + cemu_assert_debug(currentRange->interval.IsNextSegmentOnly()); + cemu_assert_debug(!currentRange->_noLoad); + rebuiltInstructions.emplace_back().make_r_name(_MakeNativeReg(ctx.regIdToBaseFormat[currentRange->GetVirtualRegister()], currentRange->GetPhysicalRegister()), currentRange->GetName()); + currentRange = currentRange->link_allSegmentRanges.next; + } + } + + imlSegment->imlList = std::move(rebuiltInstructions); + cemu_assert_debug(hadSuffixInstruction == imlSegment->HasSuffixInstruction()); + +#if DEBUG_RA_INSTRUCTION_GEN + cemuLog_log(LogType::Force, ""); + cemuLog_log(LogType::Force, "[Seg after RA]"); + IMLDebug_DumpSegment(nullptr, imlSegment, false); +#endif +} + +void IMLRA_GenerateMoveInstructions(IMLRegisterAllocatorContext& ctx) +{ + for (size_t s = 0; s < ctx.deprGenContext->segmentList2.size(); s++) + { + IMLSegment* imlSegment = ctx.deprGenContext->segmentList2[s]; + IMLRA_GenerateSegmentMoveInstructions2(ctx, imlSegment); + } +} + +static void DbgVerifyFixedRegRequirements(IMLSegment* imlSegment) +{ +#if DEBUG_RA_EXTRA_VALIDATION + std::vector<raFixedRegRequirementWithVGPR> frr = IMLRA_BuildSegmentInstructionFixedRegList(imlSegment); + for(auto& fixedReq : frr) + { + for (raLivenessRange* range = imlSegment->raInfo.linkedList_allSubranges; range; range = range->link_allSegmentRanges.next) + { + if (!range->interval2.ContainsEdge(fixedReq.pos)) + continue; + // verify if the requirement is compatible + if(range->GetVirtualRegister() == fixedReq.regId) + { + cemu_assert(range->HasPhysicalRegister()); + cemu_assert(fixedReq.allowedReg.IsAvailable(range->GetPhysicalRegister())); // virtual register matches, but not assigned the right physical register + } + else + { + cemu_assert(!fixedReq.allowedReg.IsAvailable(range->GetPhysicalRegister())); // virtual register does not match, but using the reserved physical register + } + } + } +#endif +} + +static void DbgVerifyAllRanges(IMLRegisterAllocatorContext& ctx) +{ +#if DEBUG_RA_EXTRA_VALIDATION + for (size_t s = 0; s < ctx.deprGenContext->segmentList2.size(); s++) + { + IMLSegment* imlSegment = ctx.deprGenContext->segmentList2[s]; + raLivenessRange* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; + while (subrangeItr) + { + PPCRecRA_debugValidateSubrange(subrangeItr); + subrangeItr = subrangeItr->link_allSegmentRanges.next; + } + } + // check that no range validates register requirements + for (size_t s = 0; s < ctx.deprGenContext->segmentList2.size(); s++) + { + DbgVerifyFixedRegRequirements(ctx.deprGenContext->segmentList2[s]); + } +#endif +} + +void IMLRegisterAllocator_AllocateRegisters(ppcImlGenContext_t* ppcImlGenContext, IMLRegisterAllocatorParameters& raParam) +{ + IMLRegisterAllocatorContext ctx; + ctx.raParam = &raParam; + ctx.deprGenContext = ppcImlGenContext; + + IMLRA_ReshapeForRegisterAllocation(ppcImlGenContext); + ppcImlGenContext->UpdateSegmentIndices(); // update momentaryIndex of each segment + ctx.perSegmentAbstractRanges.resize(ppcImlGenContext->segmentList2.size()); + IMLRA_CalculateLivenessRanges(ctx); + IMLRA_ProcessFlowAndCalculateLivenessRanges(ctx); + IMLRA_AssignRegisters(ctx, ppcImlGenContext); + DbgVerifyAllRanges(ctx); + IMLRA_AnalyzeRangeDataFlow(ppcImlGenContext); + IMLRA_GenerateMoveInstructions(ctx); + + IMLRA_DeleteAllRanges(ppcImlGenContext); +} diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocator.h b/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocator.h new file mode 100644 index 00000000..0a54e4cb --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocator.h @@ -0,0 +1,125 @@ +#pragma once + +// container for storing a set of register indices +// specifically optimized towards storing typical range of physical register indices (expected to be below 64) +class IMLPhysRegisterSet +{ +public: + void SetAvailable(uint32 index) + { + cemu_assert_debug(index < 64); + m_regBitmask |= ((uint64)1 << index); + } + + void SetReserved(uint32 index) + { + cemu_assert_debug(index < 64); + m_regBitmask &= ~((uint64)1 << index); + } + + void SetAllAvailable() + { + m_regBitmask = ~0ull; + } + + bool HasAllAvailable() const + { + return m_regBitmask == ~0ull; + } + + bool IsAvailable(uint32 index) const + { + return (m_regBitmask & ((uint64)1 << index)) != 0; + } + + IMLPhysRegisterSet& operator&=(const IMLPhysRegisterSet& other) + { + this->m_regBitmask &= other.m_regBitmask; + return *this; + } + + IMLPhysRegisterSet& operator=(const IMLPhysRegisterSet& other) + { + this->m_regBitmask = other.m_regBitmask; + return *this; + } + + void RemoveRegisters(const IMLPhysRegisterSet& other) + { + this->m_regBitmask &= ~other.m_regBitmask; + } + + bool HasAnyAvailable() const + { + return m_regBitmask != 0; + } + + bool HasExactlyOneAvailable() const + { + return m_regBitmask != 0 && (m_regBitmask & (m_regBitmask - 1)) == 0; + } + + // returns index of first available register. Do not call when HasAnyAvailable() == false + IMLPhysReg GetFirstAvailableReg() + { + cemu_assert_debug(m_regBitmask != 0); + sint32 regIndex = 0; + auto tmp = m_regBitmask; + while ((tmp & 0xFF) == 0) + { + regIndex += 8; + tmp >>= 8; + } + while ((tmp & 0x1) == 0) + { + regIndex++; + tmp >>= 1; + } + return regIndex; + } + + // returns index of next available register (search includes any register index >= startIndex) + // returns -1 if there is no more register + IMLPhysReg GetNextAvailableReg(sint32 startIndex) const + { + if (startIndex >= 64) + return -1; + uint32 regIndex = startIndex; + auto tmp = m_regBitmask; + tmp >>= regIndex; + if (!tmp) + return -1; + while ((tmp & 0xFF) == 0) + { + regIndex += 8; + tmp >>= 8; + } + while ((tmp & 0x1) == 0) + { + regIndex++; + tmp >>= 1; + } + return regIndex; + } + + sint32 CountAvailableRegs() const + { + return std::popcount(m_regBitmask); + } + +private: + uint64 m_regBitmask{ 0 }; +}; + +struct IMLRegisterAllocatorParameters +{ + inline IMLPhysRegisterSet& GetPhysRegPool(IMLRegFormat regFormat) + { + return perTypePhysPool[stdx::to_underlying(regFormat)]; + } + + IMLPhysRegisterSet perTypePhysPool[stdx::to_underlying(IMLRegFormat::TYPE_COUNT)]; + std::unordered_map<IMLRegID, IMLName> regIdToName; +}; + +void IMLRegisterAllocator_AllocateRegisters(ppcImlGenContext_t* ppcImlGenContext, IMLRegisterAllocatorParameters& raParam); \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocatorRanges.cpp b/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocatorRanges.cpp new file mode 100644 index 00000000..583d5905 --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocatorRanges.cpp @@ -0,0 +1,635 @@ +#include "../PPCRecompiler.h" +#include "../PPCRecompilerIml.h" +#include "IMLRegisterAllocatorRanges.h" +#include "util/helpers/MemoryPool.h" + +uint32 IMLRA_GetNextIterationIndex(); + +IMLRegID raLivenessRange::GetVirtualRegister() const +{ + return virtualRegister; +} + +sint32 raLivenessRange::GetPhysicalRegister() const +{ + return physicalRegister; +} + +IMLName raLivenessRange::GetName() const +{ + return name; +} + +void raLivenessRange::SetPhysicalRegister(IMLPhysReg physicalRegister) +{ + this->physicalRegister = physicalRegister; +} + +void raLivenessRange::SetPhysicalRegisterForCluster(IMLPhysReg physicalRegister) +{ + auto clusterRanges = GetAllSubrangesInCluster(); + for(auto& range : clusterRanges) + range->physicalRegister = physicalRegister; +} + +boost::container::small_vector<raLivenessRange*, 128> raLivenessRange::GetAllSubrangesInCluster() +{ + uint32 iterationIndex = IMLRA_GetNextIterationIndex(); + boost::container::small_vector<raLivenessRange*, 128> subranges; + subranges.push_back(this); + this->lastIterationIndex = iterationIndex; + size_t i = 0; + while(i<subranges.size()) + { + raLivenessRange* cur = subranges[i]; + i++; + // check successors + if(cur->subrangeBranchTaken && cur->subrangeBranchTaken->lastIterationIndex != iterationIndex) + { + cur->subrangeBranchTaken->lastIterationIndex = iterationIndex; + subranges.push_back(cur->subrangeBranchTaken); + } + if(cur->subrangeBranchNotTaken && cur->subrangeBranchNotTaken->lastIterationIndex != iterationIndex) + { + cur->subrangeBranchNotTaken->lastIterationIndex = iterationIndex; + subranges.push_back(cur->subrangeBranchNotTaken); + } + // check predecessors + for(auto& prev : cur->previousRanges) + { + if(prev->lastIterationIndex != iterationIndex) + { + prev->lastIterationIndex = iterationIndex; + subranges.push_back(prev); + } + } + } + return subranges; +} + +void raLivenessRange::GetAllowedRegistersExRecursive(raLivenessRange* range, uint32 iterationIndex, IMLPhysRegisterSet& allowedRegs) +{ + range->lastIterationIndex = iterationIndex; + for (auto& it : range->list_fixedRegRequirements) + allowedRegs &= it.allowedReg; + // check successors + if (range->subrangeBranchTaken && range->subrangeBranchTaken->lastIterationIndex != iterationIndex) + GetAllowedRegistersExRecursive(range->subrangeBranchTaken, iterationIndex, allowedRegs); + if (range->subrangeBranchNotTaken && range->subrangeBranchNotTaken->lastIterationIndex != iterationIndex) + GetAllowedRegistersExRecursive(range->subrangeBranchNotTaken, iterationIndex, allowedRegs); + // check predecessors + for (auto& prev : range->previousRanges) + { + if (prev->lastIterationIndex != iterationIndex) + GetAllowedRegistersExRecursive(prev, iterationIndex, allowedRegs); + } +}; + +bool raLivenessRange::GetAllowedRegistersEx(IMLPhysRegisterSet& allowedRegisters) +{ + uint32 iterationIndex = IMLRA_GetNextIterationIndex(); + allowedRegisters.SetAllAvailable(); + GetAllowedRegistersExRecursive(this, iterationIndex, allowedRegisters); + return !allowedRegisters.HasAllAvailable(); +} + +IMLPhysRegisterSet raLivenessRange::GetAllowedRegisters(IMLPhysRegisterSet regPool) +{ + IMLPhysRegisterSet fixedRegRequirements = regPool; + if(interval.ExtendsPreviousSegment() || interval.ExtendsIntoNextSegment()) + { + auto clusterRanges = GetAllSubrangesInCluster(); + for(auto& subrange : clusterRanges) + { + for(auto& fixedRegLoc : subrange->list_fixedRegRequirements) + fixedRegRequirements &= fixedRegLoc.allowedReg; + } + return fixedRegRequirements; + } + for(auto& fixedRegLoc : list_fixedRegRequirements) + fixedRegRequirements &= fixedRegLoc.allowedReg; + return fixedRegRequirements; +} + +void PPCRecRARange_addLink_perVirtualGPR(std::unordered_map<IMLRegID, raLivenessRange*>& root, raLivenessRange* subrange) +{ + IMLRegID regId = subrange->GetVirtualRegister(); + auto it = root.find(regId); + if (it == root.end()) + { + // new single element + root.try_emplace(regId, subrange); + subrange->link_sameVirtualRegister.prev = nullptr; + subrange->link_sameVirtualRegister.next = nullptr; + } + else + { + // insert in first position + raLivenessRange* priorFirst = it->second; + subrange->link_sameVirtualRegister.next = priorFirst; + it->second = subrange; + subrange->link_sameVirtualRegister.prev = nullptr; + priorFirst->link_sameVirtualRegister.prev = subrange; + } +} + +void PPCRecRARange_addLink_allSegmentRanges(raLivenessRange** root, raLivenessRange* subrange) +{ + subrange->link_allSegmentRanges.next = *root; + if (*root) + (*root)->link_allSegmentRanges.prev = subrange; + subrange->link_allSegmentRanges.prev = nullptr; + *root = subrange; +} + +void PPCRecRARange_removeLink_perVirtualGPR(std::unordered_map<IMLRegID, raLivenessRange*>& root, raLivenessRange* subrange) +{ +#ifdef CEMU_DEBUG_ASSERT + raLivenessRange* cur = root.find(subrange->GetVirtualRegister())->second; + bool hasRangeFound = false; + while(cur) + { + if(cur == subrange) + { + hasRangeFound = true; + break; + } + cur = cur->link_sameVirtualRegister.next; + } + cemu_assert_debug(hasRangeFound); +#endif + IMLRegID regId = subrange->GetVirtualRegister(); + raLivenessRange* nextRange = subrange->link_sameVirtualRegister.next; + raLivenessRange* prevRange = subrange->link_sameVirtualRegister.prev; + raLivenessRange* newBase = prevRange ? prevRange : nextRange; + if (prevRange) + prevRange->link_sameVirtualRegister.next = subrange->link_sameVirtualRegister.next; + if (nextRange) + nextRange->link_sameVirtualRegister.prev = subrange->link_sameVirtualRegister.prev; + + if (!prevRange) + { + if (nextRange) + { + root.find(regId)->second = nextRange; + } + else + { + cemu_assert_debug(root.find(regId)->second == subrange); + root.erase(regId); + } + } +#ifdef CEMU_DEBUG_ASSERT + subrange->link_sameVirtualRegister.prev = (raLivenessRange*)1; + subrange->link_sameVirtualRegister.next = (raLivenessRange*)1; +#endif +} + +void PPCRecRARange_removeLink_allSegmentRanges(raLivenessRange** root, raLivenessRange* subrange) +{ + raLivenessRange* tempPrev = subrange->link_allSegmentRanges.prev; + if (subrange->link_allSegmentRanges.prev) + subrange->link_allSegmentRanges.prev->link_allSegmentRanges.next = subrange->link_allSegmentRanges.next; + else + (*root) = subrange->link_allSegmentRanges.next; + if (subrange->link_allSegmentRanges.next) + subrange->link_allSegmentRanges.next->link_allSegmentRanges.prev = tempPrev; +#ifdef CEMU_DEBUG_ASSERT + subrange->link_allSegmentRanges.prev = (raLivenessRange*)1; + subrange->link_allSegmentRanges.next = (raLivenessRange*)1; +#endif +} + +MemoryPoolPermanentObjects<raLivenessRange> memPool_livenessSubrange(4096); + +// startPosition and endPosition are inclusive +raLivenessRange* IMLRA_CreateRange(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* imlSegment, IMLRegID virtualRegister, IMLName name, raInstructionEdge startPosition, raInstructionEdge endPosition) +{ + raLivenessRange* range = memPool_livenessSubrange.acquireObj(); + range->previousRanges.clear(); + range->list_accessLocations.clear(); + range->list_fixedRegRequirements.clear(); + range->imlSegment = imlSegment; + + cemu_assert_debug(startPosition <= endPosition); + range->interval.start = startPosition; + range->interval.end = endPosition; + + // register mapping + range->virtualRegister = virtualRegister; + range->name = name; + range->physicalRegister = -1; + // default values + range->hasStore = false; + range->hasStoreDelayed = false; + range->lastIterationIndex = 0; + range->subrangeBranchNotTaken = nullptr; + range->subrangeBranchTaken = nullptr; + cemu_assert_debug(range->previousRanges.empty()); + range->_noLoad = false; + // add to segment linked lists + PPCRecRARange_addLink_perVirtualGPR(imlSegment->raInfo.linkedList_perVirtualRegister, range); + PPCRecRARange_addLink_allSegmentRanges(&imlSegment->raInfo.linkedList_allSubranges, range); + return range; +} + +void _unlinkSubrange(raLivenessRange* range) +{ + IMLSegment* imlSegment = range->imlSegment; + PPCRecRARange_removeLink_perVirtualGPR(imlSegment->raInfo.linkedList_perVirtualRegister, range); + PPCRecRARange_removeLink_allSegmentRanges(&imlSegment->raInfo.linkedList_allSubranges, range); + // unlink reverse references + if(range->subrangeBranchTaken) + range->subrangeBranchTaken->previousRanges.erase(std::find(range->subrangeBranchTaken->previousRanges.begin(), range->subrangeBranchTaken->previousRanges.end(), range)); + if(range->subrangeBranchNotTaken) + range->subrangeBranchNotTaken->previousRanges.erase(std::find(range->subrangeBranchNotTaken->previousRanges.begin(), range->subrangeBranchNotTaken->previousRanges.end(), range)); + range->subrangeBranchTaken = (raLivenessRange*)(uintptr_t)-1; + range->subrangeBranchNotTaken = (raLivenessRange*)(uintptr_t)-1; + // remove forward references + for(auto& prev : range->previousRanges) + { + if(prev->subrangeBranchTaken == range) + prev->subrangeBranchTaken = nullptr; + if(prev->subrangeBranchNotTaken == range) + prev->subrangeBranchNotTaken = nullptr; + } + range->previousRanges.clear(); +} + +void IMLRA_DeleteRange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange* range) +{ + _unlinkSubrange(range); + range->list_accessLocations.clear(); + range->list_fixedRegRequirements.clear(); + memPool_livenessSubrange.releaseObj(range); +} + +void IMLRA_DeleteRangeCluster(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange* range) +{ + auto clusterRanges = range->GetAllSubrangesInCluster(); + for (auto& subrange : clusterRanges) + IMLRA_DeleteRange(ppcImlGenContext, subrange); +} + +void IMLRA_DeleteAllRanges(ppcImlGenContext_t* ppcImlGenContext) +{ + for(auto& seg : ppcImlGenContext->segmentList2) + { + raLivenessRange* cur; + while(cur = seg->raInfo.linkedList_allSubranges) + IMLRA_DeleteRange(ppcImlGenContext, cur); + seg->raInfo.linkedList_allSubranges = nullptr; + seg->raInfo.linkedList_perVirtualRegister.clear(); + } +} + +void IMLRA_MergeSubranges(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange* subrange, raLivenessRange* absorbedSubrange) +{ +#ifdef CEMU_DEBUG_ASSERT + PPCRecRA_debugValidateSubrange(subrange); + PPCRecRA_debugValidateSubrange(absorbedSubrange); + if (subrange->imlSegment != absorbedSubrange->imlSegment) + assert_dbg(); + cemu_assert_debug(subrange->interval.end == absorbedSubrange->interval.start); + + if (subrange->subrangeBranchTaken || subrange->subrangeBranchNotTaken) + assert_dbg(); + if (subrange == absorbedSubrange) + assert_dbg(); +#endif + // update references + subrange->subrangeBranchTaken = absorbedSubrange->subrangeBranchTaken; + subrange->subrangeBranchNotTaken = absorbedSubrange->subrangeBranchNotTaken; + absorbedSubrange->subrangeBranchTaken = nullptr; + absorbedSubrange->subrangeBranchNotTaken = nullptr; + if(subrange->subrangeBranchTaken) + *std::find(subrange->subrangeBranchTaken->previousRanges.begin(), subrange->subrangeBranchTaken->previousRanges.end(), absorbedSubrange) = subrange; + if(subrange->subrangeBranchNotTaken) + *std::find(subrange->subrangeBranchNotTaken->previousRanges.begin(), subrange->subrangeBranchNotTaken->previousRanges.end(), absorbedSubrange) = subrange; + + // merge usage locations + for (auto& accessLoc : absorbedSubrange->list_accessLocations) + subrange->list_accessLocations.push_back(accessLoc); + absorbedSubrange->list_accessLocations.clear(); + // merge fixed reg locations +#ifdef CEMU_DEBUG_ASSERT + if(!subrange->list_fixedRegRequirements.empty() && !absorbedSubrange->list_fixedRegRequirements.empty()) + { + cemu_assert_debug(subrange->list_fixedRegRequirements.back().pos < absorbedSubrange->list_fixedRegRequirements.front().pos); + } +#endif + for (auto& fixedReg : absorbedSubrange->list_fixedRegRequirements) + subrange->list_fixedRegRequirements.push_back(fixedReg); + absorbedSubrange->list_fixedRegRequirements.clear(); + + subrange->interval.end = absorbedSubrange->interval.end; + + PPCRecRA_debugValidateSubrange(subrange); + + IMLRA_DeleteRange(ppcImlGenContext, absorbedSubrange); +} + +// remove all inter-segment connections from the range cluster and split it into local ranges. Ranges are trimmed and if they have no access location they will be removed +void IMLRA_ExplodeRangeCluster(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange* originRange) +{ + cemu_assert_debug(originRange->interval.ExtendsPreviousSegment() || originRange->interval.ExtendsIntoNextSegment()); // only call this on ranges that span multiple segments + auto clusterRanges = originRange->GetAllSubrangesInCluster(); + for (auto& subrange : clusterRanges) + { + if (subrange->list_accessLocations.empty()) + continue; + raInterval interval; + interval.SetInterval(subrange->list_accessLocations.front().pos, subrange->list_accessLocations.back().pos); + raLivenessRange* newSubrange = IMLRA_CreateRange(ppcImlGenContext, subrange->imlSegment, subrange->GetVirtualRegister(), subrange->GetName(), interval.start, interval.end); + // copy locations and fixed reg indices + newSubrange->list_accessLocations = subrange->list_accessLocations; + newSubrange->list_fixedRegRequirements = subrange->list_fixedRegRequirements; + if(originRange->HasPhysicalRegister()) + { + cemu_assert_debug(subrange->list_fixedRegRequirements.empty()); // avoid unassigning a register from a range with a fixed register requirement + } + // validate + if(!newSubrange->list_accessLocations.empty()) + { + cemu_assert_debug(newSubrange->list_accessLocations.front().pos >= newSubrange->interval.start); + cemu_assert_debug(newSubrange->list_accessLocations.back().pos <= newSubrange->interval.end); + } + if(!newSubrange->list_fixedRegRequirements.empty()) + { + cemu_assert_debug(newSubrange->list_fixedRegRequirements.front().pos >= newSubrange->interval.start); // fixed register requirements outside of the actual access range probably means there is a mistake in GetInstructionFixedRegisters() + cemu_assert_debug(newSubrange->list_fixedRegRequirements.back().pos <= newSubrange->interval.end); + } + } + // delete the original range cluster + IMLRA_DeleteRangeCluster(ppcImlGenContext, originRange); +} + +#ifdef CEMU_DEBUG_ASSERT +void PPCRecRA_debugValidateSubrange(raLivenessRange* range) +{ + // validate subrange + if (range->subrangeBranchTaken && range->subrangeBranchTaken->imlSegment != range->imlSegment->nextSegmentBranchTaken) + assert_dbg(); + if (range->subrangeBranchNotTaken && range->subrangeBranchNotTaken->imlSegment != range->imlSegment->nextSegmentBranchNotTaken) + assert_dbg(); + + if(range->subrangeBranchTaken || range->subrangeBranchNotTaken) + { + cemu_assert_debug(range->interval.end.ConnectsToNextSegment()); + } + if(!range->previousRanges.empty()) + { + cemu_assert_debug(range->interval.start.ConnectsToPreviousSegment()); + } + // validate locations + if (!range->list_accessLocations.empty()) + { + cemu_assert_debug(range->list_accessLocations.front().pos >= range->interval.start); + cemu_assert_debug(range->list_accessLocations.back().pos <= range->interval.end); + } + // validate fixed reg requirements + if (!range->list_fixedRegRequirements.empty()) + { + cemu_assert_debug(range->list_fixedRegRequirements.front().pos >= range->interval.start); + cemu_assert_debug(range->list_fixedRegRequirements.back().pos <= range->interval.end); + for(sint32 i = 0; i < (sint32)range->list_fixedRegRequirements.size()-1; i++) + cemu_assert_debug(range->list_fixedRegRequirements[i].pos < range->list_fixedRegRequirements[i+1].pos); + } + +} +#else +void PPCRecRA_debugValidateSubrange(raLivenessRange* range) {} +#endif + +// trim start and end of range to match first and last read/write locations +// does not trim start/endpoints which extend into the next/previous segment +void IMLRA_TrimRangeToUse(raLivenessRange* range) +{ + if(range->list_accessLocations.empty()) + { + // special case where we trim ranges extending from other segments to a single instruction edge + cemu_assert_debug(!range->interval.start.IsInstructionIndex() || !range->interval.end.IsInstructionIndex()); + if(range->interval.start.IsInstructionIndex()) + range->interval.start = range->interval.end; + if(range->interval.end.IsInstructionIndex()) + range->interval.end = range->interval.start; + return; + } + // trim start and end + raInterval prevInterval = range->interval; + if(range->interval.start.IsInstructionIndex()) + range->interval.start = range->list_accessLocations.front().pos; + if(range->interval.end.IsInstructionIndex()) + range->interval.end = range->list_accessLocations.back().pos; + // extra checks +#ifdef CEMU_DEBUG_ASSERT + cemu_assert_debug(range->interval.start <= range->interval.end); + for(auto& loc : range->list_accessLocations) + { + cemu_assert_debug(range->interval.ContainsEdge(loc.pos)); + } + cemu_assert_debug(prevInterval.ContainsWholeInterval(range->interval)); +#endif +} + +// split range at the given position +// After the split there will be two ranges: +// head -> subrange is shortened to end at splitIndex (exclusive) +// tail -> a new subrange that ranges from splitIndex (inclusive) to the end of the original subrange +// if head has a physical register assigned it will not carry over to tail +// The return value is the tail range +// If trimToUsage is true, the end of the head subrange and the start of the tail subrange will be shrunk to fit the read/write locations within. If there are no locations then the range will be deleted +raLivenessRange* IMLRA_SplitRange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange*& subrange, raInstructionEdge splitPosition, bool trimToUsage) +{ + cemu_assert_debug(splitPosition.IsInstructionIndex()); + cemu_assert_debug(!subrange->interval.IsNextSegmentOnly() && !subrange->interval.IsPreviousSegmentOnly()); + cemu_assert_debug(subrange->interval.ContainsEdge(splitPosition)); + // determine new intervals + raInterval headInterval, tailInterval; + headInterval.SetInterval(subrange->interval.start, splitPosition-1); + tailInterval.SetInterval(splitPosition, subrange->interval.end); + cemu_assert_debug(headInterval.start <= headInterval.end); + cemu_assert_debug(tailInterval.start <= tailInterval.end); + // create tail + raLivenessRange* tailSubrange = IMLRA_CreateRange(ppcImlGenContext, subrange->imlSegment, subrange->GetVirtualRegister(), subrange->GetName(), tailInterval.start, tailInterval.end); + tailSubrange->SetPhysicalRegister(subrange->GetPhysicalRegister()); + // carry over branch targets and update reverse references + tailSubrange->subrangeBranchTaken = subrange->subrangeBranchTaken; + tailSubrange->subrangeBranchNotTaken = subrange->subrangeBranchNotTaken; + subrange->subrangeBranchTaken = nullptr; + subrange->subrangeBranchNotTaken = nullptr; + if(tailSubrange->subrangeBranchTaken) + *std::find(tailSubrange->subrangeBranchTaken->previousRanges.begin(), tailSubrange->subrangeBranchTaken->previousRanges.end(), subrange) = tailSubrange; + if(tailSubrange->subrangeBranchNotTaken) + *std::find(tailSubrange->subrangeBranchNotTaken->previousRanges.begin(), tailSubrange->subrangeBranchNotTaken->previousRanges.end(), subrange) = tailSubrange; + // we assume that list_locations is ordered by instruction index and contains no duplicate indices, so lets check that here just in case +#ifdef CEMU_DEBUG_ASSERT + if(subrange->list_accessLocations.size() > 1) + { + for(size_t i=0; i<subrange->list_accessLocations.size()-1; i++) + { + cemu_assert_debug(subrange->list_accessLocations[i].pos < subrange->list_accessLocations[i+1].pos); + } + } +#endif + // split locations + auto it = std::lower_bound( + subrange->list_accessLocations.begin(), subrange->list_accessLocations.end(), splitPosition, + [](const raAccessLocation& accessLoc, raInstructionEdge value) { return accessLoc.pos < value; } + ); + size_t originalCount = subrange->list_accessLocations.size(); + tailSubrange->list_accessLocations.insert(tailSubrange->list_accessLocations.end(), it, subrange->list_accessLocations.end()); + subrange->list_accessLocations.erase(it, subrange->list_accessLocations.end()); + cemu_assert_debug(subrange->list_accessLocations.empty() || subrange->list_accessLocations.back().pos < splitPosition); + cemu_assert_debug(tailSubrange->list_accessLocations.empty() || tailSubrange->list_accessLocations.front().pos >= splitPosition); + cemu_assert_debug(subrange->list_accessLocations.size() + tailSubrange->list_accessLocations.size() == originalCount); + // split fixed reg requirements + for (sint32 i = 0; i < subrange->list_fixedRegRequirements.size(); i++) + { + raFixedRegRequirement* fixedReg = subrange->list_fixedRegRequirements.data() + i; + if (tailInterval.ContainsEdge(fixedReg->pos)) + { + tailSubrange->list_fixedRegRequirements.push_back(*fixedReg); + } + } + // remove tail fixed reg requirements from head + for (sint32 i = 0; i < subrange->list_fixedRegRequirements.size(); i++) + { + raFixedRegRequirement* fixedReg = subrange->list_fixedRegRequirements.data() + i; + if (!headInterval.ContainsEdge(fixedReg->pos)) + { + subrange->list_fixedRegRequirements.resize(i); + break; + } + } + // adjust intervals + subrange->interval = headInterval; + tailSubrange->interval = tailInterval; + // trim to hole + if(trimToUsage) + { + if(subrange->list_accessLocations.empty() && (subrange->interval.start.IsInstructionIndex() && subrange->interval.end.IsInstructionIndex())) + { + IMLRA_DeleteRange(ppcImlGenContext, subrange); + subrange = nullptr; + } + else + { + IMLRA_TrimRangeToUse(subrange); + } + if(tailSubrange->list_accessLocations.empty() && (tailSubrange->interval.start.IsInstructionIndex() && tailSubrange->interval.end.IsInstructionIndex())) + { + IMLRA_DeleteRange(ppcImlGenContext, tailSubrange); + tailSubrange = nullptr; + } + else + { + IMLRA_TrimRangeToUse(tailSubrange); + } + } + // validation + cemu_assert_debug(!subrange || subrange->interval.start <= subrange->interval.end); + cemu_assert_debug(!tailSubrange || tailSubrange->interval.start <= tailSubrange->interval.end); + cemu_assert_debug(!tailSubrange || tailSubrange->interval.start >= splitPosition); + if (!trimToUsage) + cemu_assert_debug(!tailSubrange || tailSubrange->interval.start == splitPosition); + + if(subrange) + PPCRecRA_debugValidateSubrange(subrange); + if(tailSubrange) + PPCRecRA_debugValidateSubrange(tailSubrange); + return tailSubrange; +} + +sint32 IMLRA_GetSegmentReadWriteCost(IMLSegment* imlSegment) +{ + sint32 v = imlSegment->loopDepth + 1; + v *= 5; + return v*v; // 25, 100, 225, 400 +} + +// calculate additional cost of range that it would have after calling _ExplodeRange() on it +sint32 IMLRA_CalculateAdditionalCostOfRangeExplode(raLivenessRange* subrange) +{ + auto ranges = subrange->GetAllSubrangesInCluster(); + sint32 cost = 0;//-PPCRecRARange_estimateTotalCost(ranges); + for (auto& subrange : ranges) + { + if (subrange->list_accessLocations.empty()) + continue; // this range would be deleted and thus has no cost + sint32 segmentLoadStoreCost = IMLRA_GetSegmentReadWriteCost(subrange->imlSegment); + bool hasAdditionalLoad = subrange->interval.ExtendsPreviousSegment(); + bool hasAdditionalStore = subrange->interval.ExtendsIntoNextSegment(); + if(hasAdditionalLoad && subrange->list_accessLocations.front().IsWrite()) // if written before read then a load isn't necessary + { + cemu_assert_debug(!subrange->list_accessLocations.front().IsRead()); + cost += segmentLoadStoreCost; + } + if(hasAdditionalStore) + { + bool hasWrite = std::find_if(subrange->list_accessLocations.begin(), subrange->list_accessLocations.end(), [](const raAccessLocation& loc) { return loc.IsWrite(); }) != subrange->list_accessLocations.end(); + if(!hasWrite) // ranges which don't modify their value do not need to be stored + cost += segmentLoadStoreCost; + } + } + // todo - properly calculating all the data-flow dependency based costs is more complex so this currently is an approximation + return cost; +} + +sint32 IMLRA_CalculateAdditionalCostAfterSplit(raLivenessRange* subrange, raInstructionEdge splitPosition) +{ + // validation +#ifdef CEMU_DEBUG_ASSERT + if (subrange->interval.ExtendsIntoNextSegment()) + assert_dbg(); +#endif + cemu_assert_debug(splitPosition.IsInstructionIndex()); + + sint32 cost = 0; + // find split position in location list + if (subrange->list_accessLocations.empty()) + return 0; + if (splitPosition <= subrange->list_accessLocations.front().pos) + return 0; + if (splitPosition > subrange->list_accessLocations.back().pos) + return 0; + + size_t firstTailLocationIndex = 0; + for (size_t i = 0; i < subrange->list_accessLocations.size(); i++) + { + if (subrange->list_accessLocations[i].pos >= splitPosition) + { + firstTailLocationIndex = i; + break; + } + } + std::span<raAccessLocation> headLocations{subrange->list_accessLocations.data(), firstTailLocationIndex}; + std::span<raAccessLocation> tailLocations{subrange->list_accessLocations.data() + firstTailLocationIndex, subrange->list_accessLocations.size() - firstTailLocationIndex}; + cemu_assert_debug(headLocations.empty() || headLocations.back().pos < splitPosition); + cemu_assert_debug(tailLocations.empty() || tailLocations.front().pos >= splitPosition); + + sint32 segmentLoadStoreCost = IMLRA_GetSegmentReadWriteCost(subrange->imlSegment); + + auto CalculateCostFromLocationRange = [segmentLoadStoreCost](std::span<raAccessLocation> locations, bool trackLoadCost = true, bool trackStoreCost = true) -> sint32 + { + if(locations.empty()) + return 0; + sint32 cost = 0; + if(locations.front().IsRead() && trackLoadCost) + cost += segmentLoadStoreCost; // not overwritten, so there is a load cost + bool hasWrite = std::find_if(locations.begin(), locations.end(), [](const raAccessLocation& loc) { return loc.IsWrite(); }) != locations.end(); + if(hasWrite && trackStoreCost) + cost += segmentLoadStoreCost; // modified, so there is a store cost + return cost; + }; + + sint32 baseCost = CalculateCostFromLocationRange(subrange->list_accessLocations); + + bool tailOverwritesValue = !tailLocations.empty() && !tailLocations.front().IsRead() && tailLocations.front().IsWrite(); + + sint32 newCost = CalculateCostFromLocationRange(headLocations) + CalculateCostFromLocationRange(tailLocations, !tailOverwritesValue, true); + cemu_assert_debug(newCost >= baseCost); + cost = newCost - baseCost; + + return cost; +} \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocatorRanges.h b/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocatorRanges.h new file mode 100644 index 00000000..b0685cc5 --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocatorRanges.h @@ -0,0 +1,364 @@ +#pragma once +#include "IMLRegisterAllocator.h" + +struct raLivenessSubrangeLink +{ + struct raLivenessRange* prev; + struct raLivenessRange* next; +}; + +struct raInstructionEdge +{ + friend struct raInterval; +public: + raInstructionEdge() + { + index = 0; + } + + raInstructionEdge(sint32 instructionIndex, bool isInputEdge) + { + Set(instructionIndex, isInputEdge); + } + + void Set(sint32 instructionIndex, bool isInputEdge) + { + if(instructionIndex == RA_INTER_RANGE_START || instructionIndex == RA_INTER_RANGE_END) + { + index = instructionIndex; + return; + } + index = instructionIndex * 2 + (isInputEdge ? 0 : 1); + cemu_assert_debug(index >= 0 && index < 0x100000*2); // make sure index value is sane + } + + void SetRaw(sint32 index) + { + this->index = index; + cemu_assert_debug(index == RA_INTER_RANGE_START || index == RA_INTER_RANGE_END || (index >= 0 && index < 0x100000*2)); // make sure index value is sane + } + + // sint32 GetRaw() + // { + // this->index = index; + // } + + std::string GetDebugString() + { + if(index == RA_INTER_RANGE_START) + return "RA_START"; + else if(index == RA_INTER_RANGE_END) + return "RA_END"; + std::string str = fmt::format("{}", GetInstructionIndex()); + if(IsOnInputEdge()) + str += "i"; + else if(IsOnOutputEdge()) + str += "o"; + return str; + } + + sint32 GetInstructionIndex() const + { + cemu_assert_debug(index != RA_INTER_RANGE_START && index != RA_INTER_RANGE_END); + return index >> 1; + } + + // returns instruction index or RA_INTER_RANGE_START/RA_INTER_RANGE_END + sint32 GetInstructionIndexEx() const + { + if(index == RA_INTER_RANGE_START || index == RA_INTER_RANGE_END) + return index; + return index >> 1; + } + + sint32 GetRaw() const + { + return index; + } + + bool IsOnInputEdge() const + { + cemu_assert_debug(index != RA_INTER_RANGE_START && index != RA_INTER_RANGE_END); + return (index&1) == 0; + } + + bool IsOnOutputEdge() const + { + cemu_assert_debug(index != RA_INTER_RANGE_START && index != RA_INTER_RANGE_END); + return (index&1) != 0; + } + + bool ConnectsToPreviousSegment() const + { + return index == RA_INTER_RANGE_START; + } + + bool ConnectsToNextSegment() const + { + return index == RA_INTER_RANGE_END; + } + + bool IsInstructionIndex() const + { + return index != RA_INTER_RANGE_START && index != RA_INTER_RANGE_END; + } + + // comparison operators + bool operator>(const raInstructionEdge& other) const + { + return index > other.index; + } + bool operator<(const raInstructionEdge& other) const + { + return index < other.index; + } + bool operator<=(const raInstructionEdge& other) const + { + return index <= other.index; + } + bool operator>=(const raInstructionEdge& other) const + { + return index >= other.index; + } + bool operator==(const raInstructionEdge& other) const + { + return index == other.index; + } + + raInstructionEdge operator+(sint32 offset) const + { + cemu_assert_debug(IsInstructionIndex()); + cemu_assert_debug(offset >= 0 && offset < RA_INTER_RANGE_END); + raInstructionEdge edge; + edge.index = index + offset; + return edge; + } + + raInstructionEdge operator-(sint32 offset) const + { + cemu_assert_debug(IsInstructionIndex()); + cemu_assert_debug(offset >= 0 && offset < RA_INTER_RANGE_END); + raInstructionEdge edge; + edge.index = index - offset; + return edge; + } + + raInstructionEdge& operator++() + { + cemu_assert_debug(IsInstructionIndex()); + index++; + return *this; + } + +private: + sint32 index; // can also be RA_INTER_RANGE_START or RA_INTER_RANGE_END, otherwise contains instruction index * 2 + +}; + +struct raAccessLocation +{ + raAccessLocation(raInstructionEdge pos) : pos(pos) {} + + bool IsRead() const + { + return pos.IsOnInputEdge(); + } + + bool IsWrite() const + { + return pos.IsOnOutputEdge(); + } + + raInstructionEdge pos; +}; + +struct raInterval +{ + raInterval() + { + + } + + raInterval(raInstructionEdge start, raInstructionEdge end) + { + SetInterval(start, end); + } + + // isStartOnInput = Input+Output edge on first instruction. If false then only output + // isEndOnOutput = Input+Output edge on last instruction. If false then only input + void SetInterval(sint32 start, bool isStartOnInput, sint32 end, bool isEndOnOutput) + { + this->start.Set(start, isStartOnInput); + this->end.Set(end, !isEndOnOutput); + } + + void SetInterval(raInstructionEdge start, raInstructionEdge end) + { + cemu_assert_debug(start <= end); + this->start = start; + this->end = end; + } + + void SetStart(const raInstructionEdge& edge) + { + start = edge; + } + + void SetEnd(const raInstructionEdge& edge) + { + end = edge; + } + + sint32 GetStartIndex() const + { + return start.GetInstructionIndex(); + } + + sint32 GetEndIndex() const + { + return end.GetInstructionIndex(); + } + + bool ExtendsPreviousSegment() const + { + return start.ConnectsToPreviousSegment(); + } + + bool ExtendsIntoNextSegment() const + { + return end.ConnectsToNextSegment(); + } + + bool IsNextSegmentOnly() const + { + return start.ConnectsToNextSegment() && end.ConnectsToNextSegment(); + } + + bool IsPreviousSegmentOnly() const + { + return start.ConnectsToPreviousSegment() && end.ConnectsToPreviousSegment(); + } + + // returns true if range is contained within a single segment + bool IsLocal() const + { + return start.GetRaw() > RA_INTER_RANGE_START && end.GetRaw() < RA_INTER_RANGE_END; + } + + bool ContainsInstructionIndex(sint32 instructionIndex) const + { + cemu_assert_debug(instructionIndex != RA_INTER_RANGE_START && instructionIndex != RA_INTER_RANGE_END); + return instructionIndex >= start.GetInstructionIndexEx() && instructionIndex <= end.GetInstructionIndexEx(); + } + + // similar to ContainsInstructionIndex, but allows RA_INTER_RANGE_START/END as input + bool ContainsInstructionIndexEx(sint32 instructionIndex) const + { + if(instructionIndex == RA_INTER_RANGE_START) + return start.ConnectsToPreviousSegment(); + if(instructionIndex == RA_INTER_RANGE_END) + return end.ConnectsToNextSegment(); + return instructionIndex >= start.GetInstructionIndexEx() && instructionIndex <= end.GetInstructionIndexEx(); + } + + bool ContainsEdge(const raInstructionEdge& edge) const + { + return edge >= start && edge <= end; + } + + bool ContainsWholeInterval(const raInterval& other) const + { + return other.start >= start && other.end <= end; + } + + bool IsOverlapping(const raInterval& other) const + { + return start <= other.end && end >= other.start; + } + + sint32 GetPreciseDistance() + { + cemu_assert_debug(!start.ConnectsToNextSegment()); // how to handle this? + if(start == end) + return 1; + cemu_assert_debug(!end.ConnectsToPreviousSegment() && !end.ConnectsToNextSegment()); + if(start.ConnectsToPreviousSegment()) + return end.GetRaw() + 1; + + return end.GetRaw() - start.GetRaw() + 1; // +1 because end is inclusive + } + +//private: not making these directly accessible only forces us to create loads of verbose getters and setters + raInstructionEdge start; + raInstructionEdge end; +}; + +struct raFixedRegRequirement +{ + raInstructionEdge pos; + IMLPhysRegisterSet allowedReg; +}; + +struct raLivenessRange +{ + IMLSegment* imlSegment; + raInterval interval; + + // dirty state tracking + bool _noLoad; + bool hasStore; + bool hasStoreDelayed; + // next + raLivenessRange* subrangeBranchTaken; + raLivenessRange* subrangeBranchNotTaken; + // reverse counterpart of BranchTaken/BranchNotTaken + boost::container::small_vector<raLivenessRange*, 4> previousRanges; + // processing + uint32 lastIterationIndex; + // instruction read/write locations + std::vector<raAccessLocation> list_accessLocations; + // ordered list of all raInstructionEdge indices which require a fixed register + std::vector<raFixedRegRequirement> list_fixedRegRequirements; + // linked list (subranges with same GPR virtual register) + raLivenessSubrangeLink link_sameVirtualRegister; + // linked list (all subranges for this segment) + raLivenessSubrangeLink link_allSegmentRanges; + // register info + IMLRegID virtualRegister; + IMLName name; + // register allocator result + IMLPhysReg physicalRegister; + + boost::container::small_vector<raLivenessRange*, 128> GetAllSubrangesInCluster(); + bool GetAllowedRegistersEx(IMLPhysRegisterSet& allowedRegisters); // if the cluster has fixed register requirements in any instruction this returns the combined register mask. Otherwise returns false in which case allowedRegisters is left undefined + IMLPhysRegisterSet GetAllowedRegisters(IMLPhysRegisterSet regPool); // return regPool with fixed register requirements filtered out + + IMLRegID GetVirtualRegister() const; + sint32 GetPhysicalRegister() const; + bool HasPhysicalRegister() const { return physicalRegister >= 0; } + IMLName GetName() const; + void SetPhysicalRegister(IMLPhysReg physicalRegister); + void SetPhysicalRegisterForCluster(IMLPhysReg physicalRegister); + void UnsetPhysicalRegister() { physicalRegister = -1; } + + private: + void GetAllowedRegistersExRecursive(raLivenessRange* range, uint32 iterationIndex, IMLPhysRegisterSet& allowedRegs); +}; + +raLivenessRange* IMLRA_CreateRange(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* imlSegment, IMLRegID virtualRegister, IMLName name, raInstructionEdge startPosition, raInstructionEdge endPosition); +void IMLRA_DeleteRange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange* subrange); +void IMLRA_DeleteAllRanges(ppcImlGenContext_t* ppcImlGenContext); + +void IMLRA_ExplodeRangeCluster(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange* originRange); + +void IMLRA_MergeSubranges(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange* subrange, raLivenessRange* absorbedSubrange); + +raLivenessRange* IMLRA_SplitRange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange*& subrange, raInstructionEdge splitPosition, bool trimToUsage = false); + +void PPCRecRA_debugValidateSubrange(raLivenessRange* subrange); + +// cost estimation +sint32 IMLRA_GetSegmentReadWriteCost(IMLSegment* imlSegment); +sint32 IMLRA_CalculateAdditionalCostOfRangeExplode(raLivenessRange* subrange); +//sint32 PPCRecRARange_estimateAdditionalCostAfterSplit(raLivenessRange* subrange, sint32 splitIndex); +sint32 IMLRA_CalculateAdditionalCostAfterSplit(raLivenessRange* subrange, raInstructionEdge splitPosition); \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IMLSegment.cpp b/src/Cafe/HW/Espresso/Recompiler/IML/IMLSegment.cpp new file mode 100644 index 00000000..f3b6834f --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IMLSegment.cpp @@ -0,0 +1,133 @@ +#include "IMLInstruction.h" +#include "IMLSegment.h" + +void IMLSegment::SetEnterable(uint32 enterAddress) +{ + cemu_assert_debug(!isEnterable || enterPPCAddress == enterAddress); + isEnterable = true; + enterPPCAddress = enterAddress; +} + +bool IMLSegment::HasSuffixInstruction() const +{ + if (imlList.empty()) + return false; + const IMLInstruction& imlInstruction = imlList.back(); + return imlInstruction.IsSuffixInstruction(); +} + +sint32 IMLSegment::GetSuffixInstructionIndex() const +{ + cemu_assert_debug(HasSuffixInstruction()); + return (sint32)(imlList.size() - 1); +} + +IMLInstruction* IMLSegment::GetLastInstruction() +{ + if (imlList.empty()) + return nullptr; + return &imlList.back(); +} + +void IMLSegment::SetLinkBranchNotTaken(IMLSegment* imlSegmentDst) +{ + if (nextSegmentBranchNotTaken) + nextSegmentBranchNotTaken->list_prevSegments.erase(std::find(nextSegmentBranchNotTaken->list_prevSegments.begin(), nextSegmentBranchNotTaken->list_prevSegments.end(), this)); + nextSegmentBranchNotTaken = imlSegmentDst; + if(imlSegmentDst) + imlSegmentDst->list_prevSegments.push_back(this); +} + +void IMLSegment::SetLinkBranchTaken(IMLSegment* imlSegmentDst) +{ + if (nextSegmentBranchTaken) + nextSegmentBranchTaken->list_prevSegments.erase(std::find(nextSegmentBranchTaken->list_prevSegments.begin(), nextSegmentBranchTaken->list_prevSegments.end(), this)); + nextSegmentBranchTaken = imlSegmentDst; + if (imlSegmentDst) + imlSegmentDst->list_prevSegments.push_back(this); +} + +IMLInstruction* IMLSegment::AppendInstruction() +{ + IMLInstruction& inst = imlList.emplace_back(); + memset(&inst, 0, sizeof(IMLInstruction)); + return &inst; +} + +void IMLSegment_SetLinkBranchNotTaken(IMLSegment* imlSegmentSrc, IMLSegment* imlSegmentDst) +{ + // make sure segments aren't already linked + if (imlSegmentSrc->nextSegmentBranchNotTaken == imlSegmentDst) + return; + // add as next segment for source + if (imlSegmentSrc->nextSegmentBranchNotTaken != nullptr) + assert_dbg(); + imlSegmentSrc->nextSegmentBranchNotTaken = imlSegmentDst; + // add as previous segment for destination + imlSegmentDst->list_prevSegments.push_back(imlSegmentSrc); +} + +void IMLSegment_SetLinkBranchTaken(IMLSegment* imlSegmentSrc, IMLSegment* imlSegmentDst) +{ + // make sure segments aren't already linked + if (imlSegmentSrc->nextSegmentBranchTaken == imlSegmentDst) + return; + // add as next segment for source + if (imlSegmentSrc->nextSegmentBranchTaken != nullptr) + assert_dbg(); + imlSegmentSrc->nextSegmentBranchTaken = imlSegmentDst; + // add as previous segment for destination + imlSegmentDst->list_prevSegments.push_back(imlSegmentSrc); +} + +void IMLSegment_RemoveLink(IMLSegment* imlSegmentSrc, IMLSegment* imlSegmentDst) +{ + if (imlSegmentSrc->nextSegmentBranchNotTaken == imlSegmentDst) + { + imlSegmentSrc->nextSegmentBranchNotTaken = nullptr; + } + else if (imlSegmentSrc->nextSegmentBranchTaken == imlSegmentDst) + { + imlSegmentSrc->nextSegmentBranchTaken = nullptr; + } + else + assert_dbg(); + + bool matchFound = false; + for (sint32 i = 0; i < imlSegmentDst->list_prevSegments.size(); i++) + { + if (imlSegmentDst->list_prevSegments[i] == imlSegmentSrc) + { + imlSegmentDst->list_prevSegments.erase(imlSegmentDst->list_prevSegments.begin() + i); + matchFound = true; + break; + } + } + if (matchFound == false) + assert_dbg(); +} + +/* + * Replaces all links to segment orig with linkts to segment new + */ +void IMLSegment_RelinkInputSegment(IMLSegment* imlSegmentOrig, IMLSegment* imlSegmentNew) +{ + while (imlSegmentOrig->list_prevSegments.size() != 0) + { + IMLSegment* prevSegment = imlSegmentOrig->list_prevSegments[0]; + if (prevSegment->nextSegmentBranchNotTaken == imlSegmentOrig) + { + IMLSegment_RemoveLink(prevSegment, imlSegmentOrig); + IMLSegment_SetLinkBranchNotTaken(prevSegment, imlSegmentNew); + } + else if (prevSegment->nextSegmentBranchTaken == imlSegmentOrig) + { + IMLSegment_RemoveLink(prevSegment, imlSegmentOrig); + IMLSegment_SetLinkBranchTaken(prevSegment, imlSegmentNew); + } + else + { + assert_dbg(); + } + } +} diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IMLSegment.h b/src/Cafe/HW/Espresso/Recompiler/IML/IMLSegment.h new file mode 100644 index 00000000..10e3dc06 --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IMLSegment.h @@ -0,0 +1,193 @@ +#pragma once +#include "IMLInstruction.h" + +#include <boost/container/small_vector.hpp> + +// special values to mark the index of ranges that reach across the segment border +#define RA_INTER_RANGE_START (-1) +#define RA_INTER_RANGE_END (0x70000000) + +struct IMLSegmentPoint +{ + friend struct IMLSegmentInterval; + + sint32 index; + struct IMLSegment* imlSegment; // do we really need to track this? SegmentPoints are always accessed via the segment that they are part of + IMLSegmentPoint* next; + IMLSegmentPoint* prev; + + // the index is the instruction index times two. + // this gives us the ability to cover half an instruction with RA ranges + // covering only the first half of an instruction (0-0) means that the register is read, but not preserved + // covering first and the second half means the register is read and preserved + // covering only the second half means the register is written but not read + + sint32 GetInstructionIndex() const + { + return index; + } + + void SetInstructionIndex(sint32 index) + { + this->index = index; + } + + void ShiftIfAfter(sint32 instructionIndex, sint32 shiftCount) + { + if (!IsPreviousSegment() && !IsNextSegment()) + { + if (GetInstructionIndex() >= instructionIndex) + index += shiftCount; + } + } + + void DecrementByOneInstruction() + { + index--; + } + + // the segment point can point beyond the first and last instruction which indicates that it is an infinite range reaching up to the previous or next segment + bool IsPreviousSegment() const { return index == RA_INTER_RANGE_START; } + bool IsNextSegment() const { return index == RA_INTER_RANGE_END; } + + // overload operand > and < + bool operator>(const IMLSegmentPoint& other) const { return index > other.index; } + bool operator<(const IMLSegmentPoint& other) const { return index < other.index; } + bool operator==(const IMLSegmentPoint& other) const { return index == other.index; } + bool operator!=(const IMLSegmentPoint& other) const { return index != other.index; } + + // overload comparison operands for sint32 + bool operator>(const sint32 other) const { return index > other; } + bool operator<(const sint32 other) const { return index < other; } + bool operator<=(const sint32 other) const { return index <= other; } + bool operator>=(const sint32 other) const { return index >= other; } +}; + +struct IMLSegmentInterval +{ + IMLSegmentPoint start; + IMLSegmentPoint end; + + bool ContainsInstructionIndex(sint32 offset) const { return start <= offset && end > offset; } + + bool IsRangeOverlapping(const IMLSegmentInterval& other) + { + // todo - compare the raw index + sint32 r1start = this->start.GetInstructionIndex(); + sint32 r1end = this->end.GetInstructionIndex(); + sint32 r2start = other.start.GetInstructionIndex(); + sint32 r2end = other.end.GetInstructionIndex(); + if (r1start < r2end && r1end > r2start) + return true; + if (this->start.IsPreviousSegment() && r1start == r2start) + return true; + if (this->end.IsNextSegment() && r1end == r2end) + return true; + return false; + } + + bool ExtendsIntoPreviousSegment() const + { + return start.IsPreviousSegment(); + } + + bool ExtendsIntoNextSegment() const + { + return end.IsNextSegment(); + } + + bool IsNextSegmentOnly() const + { + if(!start.IsNextSegment()) + return false; + cemu_assert_debug(end.IsNextSegment()); + return true; + } + + bool IsPreviousSegmentOnly() const + { + if (!end.IsPreviousSegment()) + return false; + cemu_assert_debug(start.IsPreviousSegment()); + return true; + } + + sint32 GetDistance() const + { + // todo - assert if either start or end is outside the segment + // we may also want to switch this to raw indices? + return end.GetInstructionIndex() - start.GetInstructionIndex(); + } +}; + +struct PPCSegmentRegisterAllocatorInfo_t +{ + // used during loop detection + bool isPartOfProcessedLoop{}; + sint32 lastIterationIndex{}; + // linked lists + struct raLivenessRange* linkedList_allSubranges{}; + std::unordered_map<IMLRegID, struct raLivenessRange*> linkedList_perVirtualRegister; +}; + +struct IMLSegment +{ + sint32 momentaryIndex{}; // index in segment list, generally not kept up to date except if needed (necessary for loop detection) + sint32 loopDepth{}; + uint32 ppcAddress{}; // ppc address (0xFFFFFFFF if not associated with an address) + uint32 x64Offset{}; // x64 code offset of segment start + // list of intermediate instructions in this segment + std::vector<IMLInstruction> imlList; + // segment link + IMLSegment* nextSegmentBranchNotTaken{}; // this is also the default for segments where there is no branch + IMLSegment* nextSegmentBranchTaken{}; + bool nextSegmentIsUncertain{}; + std::vector<IMLSegment*> list_prevSegments{}; + // source for overwrite analysis (if nextSegmentIsUncertain is true) + // sometimes a segment is marked as an exit point, but for the purposes of dead code elimination we know the next segment + IMLSegment* deadCodeEliminationHintSeg{}; + std::vector<IMLSegment*> list_deadCodeHintBy{}; + // enterable segments + bool isEnterable{}; // this segment can be entered from outside the recompiler (no preloaded registers necessary) + uint32 enterPPCAddress{}; // used if isEnterable is true + // register allocator info + PPCSegmentRegisterAllocatorInfo_t raInfo{}; + // segment state API + void SetEnterable(uint32 enterAddress); + void SetLinkBranchNotTaken(IMLSegment* imlSegmentDst); + void SetLinkBranchTaken(IMLSegment* imlSegmentDst); + + IMLSegment* GetBranchTaken() + { + return nextSegmentBranchTaken; + } + + IMLSegment* GetBranchNotTaken() + { + return nextSegmentBranchNotTaken; + } + + void SetNextSegmentForOverwriteHints(IMLSegment* seg) + { + cemu_assert_debug(!deadCodeEliminationHintSeg); + deadCodeEliminationHintSeg = seg; + if (seg) + seg->list_deadCodeHintBy.push_back(this); + } + + // instruction API + IMLInstruction* AppendInstruction(); + + bool HasSuffixInstruction() const; + sint32 GetSuffixInstructionIndex() const; + IMLInstruction* GetLastInstruction(); + + // segment points + IMLSegmentPoint* segmentPointList{}; +}; + + +void IMLSegment_SetLinkBranchNotTaken(IMLSegment* imlSegmentSrc, IMLSegment* imlSegmentDst); +void IMLSegment_SetLinkBranchTaken(IMLSegment* imlSegmentSrc, IMLSegment* imlSegmentDst); +void IMLSegment_RelinkInputSegment(IMLSegment* imlSegmentOrig, IMLSegment* imlSegmentNew); +void IMLSegment_RemoveLink(IMLSegment* imlSegmentSrc, IMLSegment* imlSegmentDst); diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCFunctionBoundaryTracker.h b/src/Cafe/HW/Espresso/Recompiler/PPCFunctionBoundaryTracker.h index e558292b..96b5143e 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCFunctionBoundaryTracker.h +++ b/src/Cafe/HW/Espresso/Recompiler/PPCFunctionBoundaryTracker.h @@ -21,6 +21,16 @@ public: }; public: + ~PPCFunctionBoundaryTracker() + { + while (!map_ranges.empty()) + { + PPCRange_t* range = *map_ranges.begin(); + delete range; + map_ranges.erase(map_ranges.begin()); + } + } + void trackStartPoint(MPTR startAddress) { processRange(startAddress, nullptr, nullptr); @@ -40,10 +50,34 @@ public: return false; } + std::vector<PPCRange_t> GetRanges() + { + std::vector<PPCRange_t> r; + for (auto& it : map_ranges) + r.emplace_back(*it); + return r; + } + + bool ContainsAddress(uint32 addr) const + { + for (auto& it : map_ranges) + { + if (addr >= it->startAddress && addr < it->getEndAddress()) + return true; + } + return false; + } + + const std::set<uint32>& GetBranchTargets() const + { + return map_branchTargetsAll; + } + private: void addBranchDestination(PPCRange_t* sourceRange, MPTR address) { - map_branchTargets.emplace(address); + map_queuedBranchTargets.emplace(address); + map_branchTargetsAll.emplace(address); } // process flow of instruction @@ -114,7 +148,7 @@ private: Espresso::BOField BO; uint32 BI; bool LK; - Espresso::decodeOp_BCLR(opcode, BO, BI, LK); + Espresso::decodeOp_BCSPR(opcode, BO, BI, LK); if (BO.branchAlways() && !LK) { // unconditional BLR @@ -218,7 +252,7 @@ private: auto rangeItr = map_ranges.begin(); PPCRange_t* previousRange = nullptr; - for (std::set<uint32_t>::const_iterator targetItr = map_branchTargets.begin() ; targetItr != map_branchTargets.end(); ) + for (std::set<uint32_t>::const_iterator targetItr = map_queuedBranchTargets.begin() ; targetItr != map_queuedBranchTargets.end(); ) { while (rangeItr != map_ranges.end() && ((*rangeItr)->startAddress + (*rangeItr)->length) <= (*targetItr)) { @@ -239,7 +273,7 @@ private: (*targetItr) < ((*rangeItr)->startAddress + (*rangeItr)->length)) { // delete visited targets - targetItr = map_branchTargets.erase(targetItr); + targetItr = map_queuedBranchTargets.erase(targetItr); continue; } @@ -289,5 +323,6 @@ private: }; std::set<PPCRange_t*, RangePtrCmp> map_ranges; - std::set<uint32> map_branchTargets; + std::set<uint32> map_queuedBranchTargets; + std::set<uint32> map_branchTargetsAll; }; \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp index 24e87bd1..76264717 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp @@ -2,7 +2,6 @@ #include "PPCFunctionBoundaryTracker.h" #include "PPCRecompiler.h" #include "PPCRecompilerIml.h" -#include "PPCRecompilerX64.h" #include "Cafe/OS/RPL/rpl.h" #include "util/containers/RangeStore.h" #include "Cafe/OS/libs/coreinit/coreinit_CodeGen.h" @@ -14,6 +13,14 @@ #include "util/helpers/helpers.h" #include "util/MemMapper/MemMapper.h" +#include "IML/IML.h" +#include "IML/IMLRegisterAllocator.h" +#include "BackendX64/BackendX64.h" +#include "util/highresolutiontimer/HighResolutionTimer.h" + +#define PPCREC_FORCE_SYNCHRONOUS_COMPILATION 0 // if 1, then function recompilation will block and execute on the thread that called PPCRecompiler_visitAddressNoBlock +#define PPCREC_LOG_RECOMPILATION_RESULTS 0 + struct PPCInvalidationRange { MPTR startAddress; @@ -37,11 +44,36 @@ void ATTR_MS_ABI (*PPCRecompiler_leaveRecompilerCode_unvisited)(); PPCRecompilerInstanceData_t* ppcRecompilerInstanceData; +#if PPCREC_FORCE_SYNCHRONOUS_COMPILATION +static std::mutex s_singleRecompilationMutex; +#endif + bool ppcRecompilerEnabled = false; +void PPCRecompiler_recompileAtAddress(uint32 address); + // this function does never block and can fail if the recompiler lock cannot be acquired immediately void PPCRecompiler_visitAddressNoBlock(uint32 enterAddress) { +#if PPCREC_FORCE_SYNCHRONOUS_COMPILATION + if (ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4] != PPCRecompiler_leaveRecompilerCode_unvisited) + return; + PPCRecompilerState.recompilerSpinlock.lock(); + if (ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4] != PPCRecompiler_leaveRecompilerCode_unvisited) + { + PPCRecompilerState.recompilerSpinlock.unlock(); + return; + } + ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4] = PPCRecompiler_leaveRecompilerCode_visited; + PPCRecompilerState.recompilerSpinlock.unlock(); + s_singleRecompilationMutex.lock(); + if (ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4] == PPCRecompiler_leaveRecompilerCode_visited) + { + PPCRecompiler_recompileAtAddress(enterAddress); + } + s_singleRecompilationMutex.unlock(); + return; +#endif // quick read-only check without lock if (ppcRecompilerInstanceData->ppcRecompilerDirectJumpTable[enterAddress / 4] != PPCRecompiler_leaveRecompilerCode_unvisited) return; @@ -127,15 +159,15 @@ void PPCRecompiler_attemptEnter(PPCInterpreter_t* hCPU, uint32 enterAddress) PPCRecompiler_enter(hCPU, funcPtr); } } +bool PPCRecompiler_ApplyIMLPasses(ppcImlGenContext_t& ppcImlGenContext); -PPCRecFunction_t* PPCRecompiler_recompileFunction(PPCFunctionBoundaryTracker::PPCRange_t range, std::set<uint32>& entryAddresses, std::vector<std::pair<MPTR, uint32>>& entryPointsOut) +PPCRecFunction_t* PPCRecompiler_recompileFunction(PPCFunctionBoundaryTracker::PPCRange_t range, std::set<uint32>& entryAddresses, std::vector<std::pair<MPTR, uint32>>& entryPointsOut, PPCFunctionBoundaryTracker& boundaryTracker) { if (range.startAddress >= PPC_REC_CODE_AREA_END) { cemuLog_log(LogType::Force, "Attempting to recompile function outside of allowed code area"); return nullptr; } - uint32 codeGenRangeStart; uint32 codeGenRangeSize = 0; coreinit::OSGetCodegenVirtAddrRangeInternal(codeGenRangeStart, codeGenRangeSize); @@ -153,29 +185,61 @@ PPCRecFunction_t* PPCRecompiler_recompileFunction(PPCFunctionBoundaryTracker::PP PPCRecFunction_t* ppcRecFunc = new PPCRecFunction_t(); ppcRecFunc->ppcAddress = range.startAddress; ppcRecFunc->ppcSize = range.length; + +#if PPCREC_LOG_RECOMPILATION_RESULTS + BenchmarkTimer bt; + bt.Start(); +#endif + // generate intermediate code ppcImlGenContext_t ppcImlGenContext = { 0 }; - bool compiledSuccessfully = PPCRecompiler_generateIntermediateCode(ppcImlGenContext, ppcRecFunc, entryAddresses); + ppcImlGenContext.debug_entryPPCAddress = range.startAddress; + bool compiledSuccessfully = PPCRecompiler_generateIntermediateCode(ppcImlGenContext, ppcRecFunc, entryAddresses, boundaryTracker); if (compiledSuccessfully == false) { - // todo: Free everything - PPCRecompiler_freeContext(&ppcImlGenContext); delete ppcRecFunc; - return NULL; + return nullptr; } + + uint32 ppcRecLowerAddr = LaunchSettings::GetPPCRecLowerAddr(); + uint32 ppcRecUpperAddr = LaunchSettings::GetPPCRecUpperAddr(); + + if (ppcRecLowerAddr != 0 && ppcRecUpperAddr != 0) + { + if (ppcRecFunc->ppcAddress < ppcRecLowerAddr || ppcRecFunc->ppcAddress > ppcRecUpperAddr) + { + delete ppcRecFunc; + return nullptr; + } + } + + // apply passes + if (!PPCRecompiler_ApplyIMLPasses(ppcImlGenContext)) + { + delete ppcRecFunc; + return nullptr; + } + // emit x64 code bool x64GenerationSuccess = PPCRecompiler_generateX64Code(ppcRecFunc, &ppcImlGenContext); if (x64GenerationSuccess == false) { - PPCRecompiler_freeContext(&ppcImlGenContext); return nullptr; } + if (ActiveSettings::DumpRecompilerFunctionsEnabled()) + { + FileStream* fs = FileStream::createFile2(ActiveSettings::GetUserDataPath(fmt::format("dump/recompiler/ppc_{:08x}.bin", ppcRecFunc->ppcAddress))); + if (fs) + { + fs->writeData(ppcRecFunc->x86Code, ppcRecFunc->x86Size); + delete fs; + } + } // collect list of PPC-->x64 entry points entryPointsOut.clear(); - for (sint32 s = 0; s < ppcImlGenContext.segmentListCount; s++) + for(IMLSegment* imlSegment : ppcImlGenContext.segmentList2) { - PPCRecImlSegment_t* imlSegment = ppcImlGenContext.segmentList[s]; if (imlSegment->isEnterable == false) continue; @@ -185,10 +249,83 @@ PPCRecFunction_t* PPCRecompiler_recompileFunction(PPCFunctionBoundaryTracker::PP entryPointsOut.emplace_back(ppcEnterOffset, x64Offset); } - PPCRecompiler_freeContext(&ppcImlGenContext); +#if PPCREC_LOG_RECOMPILATION_RESULTS + bt.Stop(); + uint32 codeHash = 0; + for (uint32 i = 0; i < ppcRecFunc->x86Size; i++) + { + codeHash = _rotr(codeHash, 3); + codeHash += ((uint8*)ppcRecFunc->x86Code)[i]; + } + cemuLog_log(LogType::Force, "[Recompiler] PPC 0x{:08x} -> x64: 0x{:x} Took {:.4}ms | Size {:04x} CodeHash {:08x}", (uint32)ppcRecFunc->ppcAddress, (uint64)(uintptr_t)ppcRecFunc->x86Code, bt.GetElapsedMilliseconds(), ppcRecFunc->x86Size, codeHash); +#endif + return ppcRecFunc; } +void PPCRecompiler_NativeRegisterAllocatorPass(ppcImlGenContext_t& ppcImlGenContext) +{ + IMLRegisterAllocatorParameters raParam; + + for (auto& it : ppcImlGenContext.mappedRegs) + raParam.regIdToName.try_emplace(it.second.GetRegID(), it.first); + + auto& gprPhysPool = raParam.GetPhysRegPool(IMLRegFormat::I64); + gprPhysPool.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_RAX); + gprPhysPool.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_RDX); + gprPhysPool.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_RBX); + gprPhysPool.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_RBP); + gprPhysPool.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_RSI); + gprPhysPool.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_RDI); + gprPhysPool.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_R8); + gprPhysPool.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_R9); + gprPhysPool.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_R10); + gprPhysPool.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_R11); + gprPhysPool.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_R12); + gprPhysPool.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_RCX); + + // add XMM registers, except XMM15 which is the temporary register + auto& fprPhysPool = raParam.GetPhysRegPool(IMLRegFormat::F64); + fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 0); + fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 1); + fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 2); + fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 3); + fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 4); + fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 5); + fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 6); + fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 7); + fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 8); + fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 9); + fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 10); + fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 11); + fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 12); + fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 13); + fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 14); + + IMLRegisterAllocator_AllocateRegisters(&ppcImlGenContext, raParam); +} + +bool PPCRecompiler_ApplyIMLPasses(ppcImlGenContext_t& ppcImlGenContext) +{ + // isolate entry points from function flow (enterable segments must not be the target of any other segment) + // this simplifies logic during register allocation + PPCRecompilerIML_isolateEnterableSegments(&ppcImlGenContext); + + // if GQRs can be predicted, optimize PSQ load/stores + PPCRecompiler_optimizePSQLoadAndStore(&ppcImlGenContext); + + // merge certain float load+store patterns (must happen before FPR register remapping) + IMLOptimizer_OptimizeDirectFloatCopies(&ppcImlGenContext); + // delay byte swapping for certain load+store patterns + IMLOptimizer_OptimizeDirectIntegerCopies(&ppcImlGenContext); + + IMLOptimizer_StandardOptimizationPass(ppcImlGenContext); + + PPCRecompiler_NativeRegisterAllocatorPass(ppcImlGenContext); + + return true; +} + bool PPCRecompiler_makeRecompiledFunctionActive(uint32 initialEntryPoint, PPCFunctionBoundaryTracker::PPCRange_t& range, PPCRecFunction_t* ppcRecFunc, std::vector<std::pair<MPTR, uint32>>& entryPoints) { // update jump table @@ -202,7 +339,7 @@ bool PPCRecompiler_makeRecompiledFunctionActive(uint32 initialEntryPoint, PPCFun return false; } - // check if the current range got invalidated in the time it took to recompile it + // check if the current range got invalidated during the time it took to recompile it bool isInvalidated = false; for (auto& invRange : PPCRecompilerState.invalidationRanges) { @@ -280,7 +417,7 @@ void PPCRecompiler_recompileAtAddress(uint32 address) PPCRecompilerState.recompilerSpinlock.unlock(); std::vector<std::pair<MPTR, uint32>> functionEntryPoints; - auto func = PPCRecompiler_recompileFunction(range, entryAddresses, functionEntryPoints); + auto func = PPCRecompiler_recompileFunction(range, entryAddresses, functionEntryPoints, funcBoundaries); if (!func) { @@ -295,6 +432,10 @@ std::atomic_bool s_recompilerThreadStopSignal{false}; void PPCRecompiler_thread() { SetThreadName("PPCRecompiler"); +#if PPCREC_FORCE_SYNCHRONOUS_COMPILATION + return; +#endif + while (true) { if(s_recompilerThreadStopSignal) @@ -475,6 +616,41 @@ void PPCRecompiler_invalidateRange(uint32 startAddr, uint32 endAddr) #if defined(ARCH_X86_64) void PPCRecompiler_initPlatform() { + ppcRecompilerInstanceData->_x64XMM_xorNegateMaskBottom[0] = 1ULL << 63ULL; + ppcRecompilerInstanceData->_x64XMM_xorNegateMaskBottom[1] = 0ULL; + ppcRecompilerInstanceData->_x64XMM_xorNegateMaskPair[0] = 1ULL << 63ULL; + ppcRecompilerInstanceData->_x64XMM_xorNegateMaskPair[1] = 1ULL << 63ULL; + ppcRecompilerInstanceData->_x64XMM_xorNOTMask[0] = 0xFFFFFFFFFFFFFFFFULL; + ppcRecompilerInstanceData->_x64XMM_xorNOTMask[1] = 0xFFFFFFFFFFFFFFFFULL; + ppcRecompilerInstanceData->_x64XMM_andAbsMaskBottom[0] = ~(1ULL << 63ULL); + ppcRecompilerInstanceData->_x64XMM_andAbsMaskBottom[1] = ~0ULL; + ppcRecompilerInstanceData->_x64XMM_andAbsMaskPair[0] = ~(1ULL << 63ULL); + ppcRecompilerInstanceData->_x64XMM_andAbsMaskPair[1] = ~(1ULL << 63ULL); + ppcRecompilerInstanceData->_x64XMM_andFloatAbsMaskBottom[0] = ~(1 << 31); + ppcRecompilerInstanceData->_x64XMM_andFloatAbsMaskBottom[1] = 0xFFFFFFFF; + ppcRecompilerInstanceData->_x64XMM_andFloatAbsMaskBottom[2] = 0xFFFFFFFF; + ppcRecompilerInstanceData->_x64XMM_andFloatAbsMaskBottom[3] = 0xFFFFFFFF; + ppcRecompilerInstanceData->_x64XMM_singleWordMask[0] = 0xFFFFFFFFULL; + ppcRecompilerInstanceData->_x64XMM_singleWordMask[1] = 0ULL; + ppcRecompilerInstanceData->_x64XMM_constDouble1_1[0] = 1.0; + ppcRecompilerInstanceData->_x64XMM_constDouble1_1[1] = 1.0; + ppcRecompilerInstanceData->_x64XMM_constDouble0_0[0] = 0.0; + ppcRecompilerInstanceData->_x64XMM_constDouble0_0[1] = 0.0; + ppcRecompilerInstanceData->_x64XMM_constFloat0_0[0] = 0.0f; + ppcRecompilerInstanceData->_x64XMM_constFloat0_0[1] = 0.0f; + ppcRecompilerInstanceData->_x64XMM_constFloat1_1[0] = 1.0f; + ppcRecompilerInstanceData->_x64XMM_constFloat1_1[1] = 1.0f; + *(uint32*)&ppcRecompilerInstanceData->_x64XMM_constFloatMin[0] = 0x00800000; + *(uint32*)&ppcRecompilerInstanceData->_x64XMM_constFloatMin[1] = 0x00800000; + ppcRecompilerInstanceData->_x64XMM_flushDenormalMask1[0] = 0x7F800000; + ppcRecompilerInstanceData->_x64XMM_flushDenormalMask1[1] = 0x7F800000; + ppcRecompilerInstanceData->_x64XMM_flushDenormalMask1[2] = 0x7F800000; + ppcRecompilerInstanceData->_x64XMM_flushDenormalMask1[3] = 0x7F800000; + ppcRecompilerInstanceData->_x64XMM_flushDenormalMaskResetSignBits[0] = ~0x80000000; + ppcRecompilerInstanceData->_x64XMM_flushDenormalMaskResetSignBits[1] = ~0x80000000; + ppcRecompilerInstanceData->_x64XMM_flushDenormalMaskResetSignBits[2] = ~0x80000000; + ppcRecompilerInstanceData->_x64XMM_flushDenormalMaskResetSignBits[3] = ~0x80000000; + // mxcsr ppcRecompilerInstanceData->_x64XMM_mxCsr_ftzOn = 0x1F80 | 0x8000; ppcRecompilerInstanceData->_x64XMM_mxCsr_ftzOff = 0x1F80; @@ -512,42 +688,6 @@ void PPCRecompiler_init() PPCRecompiler_allocateRange(mmuRange_TRAMPOLINE_AREA.getBase(), mmuRange_TRAMPOLINE_AREA.getSize()); PPCRecompiler_allocateRange(mmuRange_CODECAVE.getBase(), mmuRange_CODECAVE.getSize()); - // init x64 recompiler instance data - ppcRecompilerInstanceData->_x64XMM_xorNegateMaskBottom[0] = 1ULL << 63ULL; - ppcRecompilerInstanceData->_x64XMM_xorNegateMaskBottom[1] = 0ULL; - ppcRecompilerInstanceData->_x64XMM_xorNegateMaskPair[0] = 1ULL << 63ULL; - ppcRecompilerInstanceData->_x64XMM_xorNegateMaskPair[1] = 1ULL << 63ULL; - ppcRecompilerInstanceData->_x64XMM_xorNOTMask[0] = 0xFFFFFFFFFFFFFFFFULL; - ppcRecompilerInstanceData->_x64XMM_xorNOTMask[1] = 0xFFFFFFFFFFFFFFFFULL; - ppcRecompilerInstanceData->_x64XMM_andAbsMaskBottom[0] = ~(1ULL << 63ULL); - ppcRecompilerInstanceData->_x64XMM_andAbsMaskBottom[1] = ~0ULL; - ppcRecompilerInstanceData->_x64XMM_andAbsMaskPair[0] = ~(1ULL << 63ULL); - ppcRecompilerInstanceData->_x64XMM_andAbsMaskPair[1] = ~(1ULL << 63ULL); - ppcRecompilerInstanceData->_x64XMM_andFloatAbsMaskBottom[0] = ~(1 << 31); - ppcRecompilerInstanceData->_x64XMM_andFloatAbsMaskBottom[1] = 0xFFFFFFFF; - ppcRecompilerInstanceData->_x64XMM_andFloatAbsMaskBottom[2] = 0xFFFFFFFF; - ppcRecompilerInstanceData->_x64XMM_andFloatAbsMaskBottom[3] = 0xFFFFFFFF; - ppcRecompilerInstanceData->_x64XMM_singleWordMask[0] = 0xFFFFFFFFULL; - ppcRecompilerInstanceData->_x64XMM_singleWordMask[1] = 0ULL; - ppcRecompilerInstanceData->_x64XMM_constDouble1_1[0] = 1.0; - ppcRecompilerInstanceData->_x64XMM_constDouble1_1[1] = 1.0; - ppcRecompilerInstanceData->_x64XMM_constDouble0_0[0] = 0.0; - ppcRecompilerInstanceData->_x64XMM_constDouble0_0[1] = 0.0; - ppcRecompilerInstanceData->_x64XMM_constFloat0_0[0] = 0.0f; - ppcRecompilerInstanceData->_x64XMM_constFloat0_0[1] = 0.0f; - ppcRecompilerInstanceData->_x64XMM_constFloat1_1[0] = 1.0f; - ppcRecompilerInstanceData->_x64XMM_constFloat1_1[1] = 1.0f; - *(uint32*)&ppcRecompilerInstanceData->_x64XMM_constFloatMin[0] = 0x00800000; - *(uint32*)&ppcRecompilerInstanceData->_x64XMM_constFloatMin[1] = 0x00800000; - ppcRecompilerInstanceData->_x64XMM_flushDenormalMask1[0] = 0x7F800000; - ppcRecompilerInstanceData->_x64XMM_flushDenormalMask1[1] = 0x7F800000; - ppcRecompilerInstanceData->_x64XMM_flushDenormalMask1[2] = 0x7F800000; - ppcRecompilerInstanceData->_x64XMM_flushDenormalMask1[3] = 0x7F800000; - ppcRecompilerInstanceData->_x64XMM_flushDenormalMaskResetSignBits[0] = ~0x80000000; - ppcRecompilerInstanceData->_x64XMM_flushDenormalMaskResetSignBits[1] = ~0x80000000; - ppcRecompilerInstanceData->_x64XMM_flushDenormalMaskResetSignBits[2] = ~0x80000000; - ppcRecompilerInstanceData->_x64XMM_flushDenormalMaskResetSignBits[3] = ~0x80000000; - // setup GQR scale tables for (uint32 i = 0; i < 32; i++) @@ -623,4 +763,4 @@ void PPCRecompiler_Shutdown() // mark as unmapped ppcRecompiler_reservedBlockMask[i] = false; } -} \ No newline at end of file +} diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.h b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.h index 2e40f19d..706855d4 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.h +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.h @@ -1,4 +1,4 @@ -#include <vector> +#pragma once #define PPC_REC_CODE_AREA_START (0x00000000) // lower bound of executable memory area. Recompiler expects this address to be 0 #define PPC_REC_CODE_AREA_END (0x10000000) // upper bound of executable memory area @@ -6,336 +6,113 @@ #define PPC_REC_ALIGN_TO_4MB(__v) (((__v)+4*1024*1024-1)&~(4*1024*1024-1)) -#define PPC_REC_MAX_VIRTUAL_GPR (40) // enough to store 32 GPRs + a few SPRs + temp registers (usually only 1-2) +#define PPC_REC_MAX_VIRTUAL_GPR (40 + 32) // enough to store 32 GPRs + a few SPRs + temp registers (usually only 1-2) -typedef struct +struct ppcRecRange_t { uint32 ppcAddress; uint32 ppcSize; - //void* x86Start; - //size_t x86Size; void* storedRange; -}ppcRecRange_t; +}; -typedef struct +struct PPCRecFunction_t { uint32 ppcAddress; uint32 ppcSize; // ppc code size of function void* x86Code; // pointer to x86 code size_t x86Size; std::vector<ppcRecRange_t> list_ranges; -}PPCRecFunction_t; - -#define PPCREC_IML_OP_FLAG_SIGNEXTEND (1<<0) -#define PPCREC_IML_OP_FLAG_SWITCHENDIAN (1<<1) -#define PPCREC_IML_OP_FLAG_NOT_EXPANDED (1<<2) // set single-precision load instructions to indicate that the value should not be rounded to double-precision -#define PPCREC_IML_OP_FLAG_UNUSED (1<<7) // used to mark instructions that are not used - -typedef struct -{ - uint8 type; - uint8 operation; - uint8 crRegister; // set to 0xFF if not set, not all IML instruction types support cr. - uint8 crMode; // only used when crRegister is valid, used to differentiate between various forms of condition flag set/clear behavior - uint32 crIgnoreMask; // bit set for every respective CR bit that doesn't need to be updated - uint32 associatedPPCAddress; // ppc address that is associated with this instruction - union - { - struct - { - uint8 _padding[7]; - }padding; - struct - { - // R (op) A [update cr* in mode *] - uint8 registerResult; - uint8 registerA; - }op_r_r; - struct - { - // R = A (op) B [update cr* in mode *] - uint8 registerResult; - uint8 registerA; - uint8 registerB; - }op_r_r_r; - struct - { - // R = A (op) immS32 [update cr* in mode *] - uint8 registerResult; - uint8 registerA; - sint32 immS32; - }op_r_r_s32; - struct - { - // R/F = NAME or NAME = R/F - uint8 registerIndex; - uint8 copyWidth; - uint32 name; - uint8 flags; - }op_r_name; - struct - { - // R (op) s32 [update cr* in mode *] - uint8 registerIndex; - sint32 immS32; - }op_r_immS32; - struct - { - uint32 address; - uint8 flags; - }op_jumpmark; - struct - { - uint32 param; - uint32 param2; - uint16 paramU16; - }op_macro; - struct - { - uint32 jumpmarkAddress; - bool jumpAccordingToSegment; //PPCRecImlSegment_t* destinationSegment; // if set, this replaces jumpmarkAddress - uint8 condition; // only used when crRegisterIndex is 8 or above (update: Apparently only used to mark jumps without a condition? -> Cleanup) - uint8 crRegisterIndex; - uint8 crBitIndex; - bool bitMustBeSet; - }op_conditionalJump; - struct - { - uint8 registerData; - uint8 registerMem; - uint8 registerMem2; - uint8 registerGQR; - uint8 copyWidth; - //uint8 flags; - struct - { - bool swapEndian : 1; - bool signExtend : 1; - bool notExpanded : 1; // for floats - }flags2; - uint8 mode; // transfer mode (copy width, ps0/ps1 behavior) - sint32 immS32; - }op_storeLoad; - struct - { - struct - { - uint8 registerMem; - sint32 immS32; - }src; - struct - { - uint8 registerMem; - sint32 immS32; - }dst; - uint8 copyWidth; - }op_mem2mem; - struct - { - uint8 registerResult; - uint8 registerOperand; - uint8 flags; - }op_fpr_r_r; - struct - { - uint8 registerResult; - uint8 registerOperandA; - uint8 registerOperandB; - uint8 flags; - }op_fpr_r_r_r; - struct - { - uint8 registerResult; - uint8 registerOperandA; - uint8 registerOperandB; - uint8 registerOperandC; - uint8 flags; - }op_fpr_r_r_r_r; - struct - { - uint8 registerResult; - //uint8 flags; - }op_fpr_r; - struct - { - uint32 ppcAddress; - uint32 x64Offset; - }op_ppcEnter; - struct - { - uint8 crD; // crBitIndex (result) - uint8 crA; // crBitIndex - uint8 crB; // crBitIndex - }op_cr; - // conditional operations (emitted if supported by target platform) - struct - { - // r_s32 - uint8 registerIndex; - sint32 immS32; - // condition - uint8 crRegisterIndex; - uint8 crBitIndex; - bool bitMustBeSet; - }op_conditional_r_s32; - }; -}PPCRecImlInstruction_t; - -typedef struct _PPCRecImlSegment_t PPCRecImlSegment_t; - -typedef struct _ppcRecompilerSegmentPoint_t -{ - sint32 index; - PPCRecImlSegment_t* imlSegment; - _ppcRecompilerSegmentPoint_t* next; - _ppcRecompilerSegmentPoint_t* prev; -}ppcRecompilerSegmentPoint_t; - -struct raLivenessLocation_t -{ - sint32 index; - bool isRead; - bool isWrite; - - raLivenessLocation_t() = default; - - raLivenessLocation_t(sint32 index, bool isRead, bool isWrite) - : index(index), isRead(isRead), isWrite(isWrite) {}; }; -struct raLivenessSubrangeLink_t -{ - struct raLivenessSubrange_t* prev; - struct raLivenessSubrange_t* next; -}; +#include "Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.h" +#include "Cafe/HW/Espresso/Recompiler/IML/IMLSegment.h" -struct raLivenessSubrange_t -{ - struct raLivenessRange_t* range; - PPCRecImlSegment_t* imlSegment; - ppcRecompilerSegmentPoint_t start; - ppcRecompilerSegmentPoint_t end; - // dirty state tracking - bool _noLoad; - bool hasStore; - bool hasStoreDelayed; - // next - raLivenessSubrange_t* subrangeBranchTaken; - raLivenessSubrange_t* subrangeBranchNotTaken; - // processing - uint32 lastIterationIndex; - // instruction locations - std::vector<raLivenessLocation_t> list_locations; - // linked list (subranges with same GPR virtual register) - raLivenessSubrangeLink_t link_sameVirtualRegisterGPR; - // linked list (all subranges for this segment) - raLivenessSubrangeLink_t link_segmentSubrangesGPR; -}; - -struct raLivenessRange_t -{ - sint32 virtualRegister; - sint32 physicalRegister; - sint32 name; - std::vector<raLivenessSubrange_t*> list_subranges; -}; - -struct PPCSegmentRegisterAllocatorInfo_t -{ - // analyzer stage - bool isPartOfProcessedLoop{}; // used during loop detection - sint32 lastIterationIndex{}; - // linked lists - raLivenessSubrange_t* linkedList_allSubranges{}; - raLivenessSubrange_t* linkedList_perVirtualGPR[PPC_REC_MAX_VIRTUAL_GPR]{}; -}; - -struct PPCRecVGPRDistances_t -{ - struct _RegArrayEntry - { - sint32 usageStart{}; - sint32 usageEnd{}; - }reg[PPC_REC_MAX_VIRTUAL_GPR]; - bool isProcessed[PPC_REC_MAX_VIRTUAL_GPR]{}; -}; - -typedef struct _PPCRecImlSegment_t -{ - sint32 momentaryIndex{}; // index in segment list, generally not kept up to date except if needed (necessary for loop detection) - sint32 startOffset{}; // offset to first instruction in iml instruction list - sint32 count{}; // number of instructions in segment - uint32 ppcAddress{}; // ppc address (0xFFFFFFFF if not associated with an address) - uint32 x64Offset{}; // x64 code offset of segment start - uint32 cycleCount{}; // number of PPC cycles required to execute this segment (roughly) - // list of intermediate instructions in this segment - PPCRecImlInstruction_t* imlList{}; - sint32 imlListSize{}; - sint32 imlListCount{}; - // segment link - _PPCRecImlSegment_t* nextSegmentBranchNotTaken{}; // this is also the default for segments where there is no branch - _PPCRecImlSegment_t* nextSegmentBranchTaken{}; - bool nextSegmentIsUncertain{}; - sint32 loopDepth{}; - //sList_t* list_prevSegments; - std::vector<_PPCRecImlSegment_t*> list_prevSegments{}; - // PPC range of segment - uint32 ppcAddrMin{}; - uint32 ppcAddrMax{}; - // enterable segments - bool isEnterable{}; // this segment can be entered from outside the recompiler (no preloaded registers necessary) - uint32 enterPPCAddress{}; // used if isEnterable is true - // jump destination segments - bool isJumpDestination{}; // segment is a destination for one or more (conditional) jumps - uint32 jumpDestinationPPCAddress{}; - // PPC FPR use mask - bool ppcFPRUsed[32]{}; // same as ppcGPRUsed, but for FPR - // CR use mask - uint32 crBitsInput{}; // bits that are expected to be set from the previous segment (read in this segment but not overwritten) - uint32 crBitsRead{}; // all bits that are read in this segment - uint32 crBitsWritten{}; // bits that are written in this segment - // register allocator info - PPCSegmentRegisterAllocatorInfo_t raInfo{}; - PPCRecVGPRDistances_t raDistances{}; - bool raRangeExtendProcessed{}; - // segment points - ppcRecompilerSegmentPoint_t* segmentPointList{}; -}PPCRecImlSegment_t; +struct IMLInstruction* PPCRecompilerImlGen_generateNewEmptyInstruction(struct ppcImlGenContext_t* ppcImlGenContext); struct ppcImlGenContext_t { - PPCRecFunction_t* functionRef; + class PPCFunctionBoundaryTracker* boundaryTracker; uint32* currentInstruction; uint32 ppcAddressOfCurrentInstruction; + IMLSegment* currentOutputSegment; + struct PPCBasicBlockInfo* currentBasicBlock{}; // fpr mode bool LSQE{ true }; bool PSE{ true }; // cycle counter uint32 cyclesSinceLastBranch; // used to track ppc cycles - // temporary general purpose registers - uint32 mappedRegister[PPC_REC_MAX_VIRTUAL_GPR]; - // temporary floating point registers (single and double precision) - uint32 mappedFPRRegister[256]; - // list of intermediate instructions - PPCRecImlInstruction_t* imlList; - sint32 imlListSize; - sint32 imlListCount; + std::unordered_map<IMLName, IMLReg> mappedRegs; + + uint32 GetMaxRegId() const + { + if (mappedRegs.empty()) + return 0; + return mappedRegs.size()-1; + } + // list of segments - PPCRecImlSegment_t** segmentList; - sint32 segmentListSize; - sint32 segmentListCount; + std::vector<IMLSegment*> segmentList2; // code generation control bool hasFPUInstruction; // if true, PPCEnter macro will create FP_UNAVAIL checks -> Not needed in user mode - // register allocator info - struct - { - std::vector<raLivenessRange_t*> list_ranges; - }raInfo; // analysis info struct { bool modifiesGQR[8]; }tracking; + // debug helpers + uint32 debug_entryPPCAddress{0}; + + ~ppcImlGenContext_t() + { + for (IMLSegment* imlSegment : segmentList2) + delete imlSegment; + segmentList2.clear(); + } + + // append raw instruction + IMLInstruction& emitInst() + { + return *PPCRecompilerImlGen_generateNewEmptyInstruction(this); + } + + IMLSegment* NewSegment() + { + IMLSegment* seg = new IMLSegment(); + segmentList2.emplace_back(seg); + return seg; + } + + size_t GetSegmentIndex(IMLSegment* seg) + { + for (size_t i = 0; i < segmentList2.size(); i++) + { + if (segmentList2[i] == seg) + return i; + } + cemu_assert_error(); + return 0; + } + + IMLSegment* InsertSegment(size_t index) + { + IMLSegment* newSeg = new IMLSegment(); + segmentList2.insert(segmentList2.begin() + index, 1, newSeg); + return newSeg; + } + + std::span<IMLSegment*> InsertSegments(size_t index, size_t count) + { + segmentList2.insert(segmentList2.begin() + index, count, {}); + for (size_t i = index; i < (index + count); i++) + segmentList2[i] = new IMLSegment(); + return { segmentList2.data() + index, count}; + } + + void UpdateSegmentIndices() + { + for (size_t i = 0; i < segmentList2.size(); i++) + segmentList2[i]->momentaryIndex = (sint32)i; + } }; typedef void ATTR_MS_ABI (*PPCREC_JUMP_ENTRY)(); @@ -385,8 +162,6 @@ extern void ATTR_MS_ABI (*PPCRecompiler_leaveRecompilerCode_unvisited)(); #define PPC_REC_INVALID_FUNCTION ((PPCRecFunction_t*)-1) -// todo - move some of the stuff above into PPCRecompilerInternal.h - // recompiler interface void PPCRecompiler_recompileIfUnvisited(uint32 enterAddress); diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerIml.h b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerIml.h index 86af33b2..5d30267d 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerIml.h +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerIml.h @@ -1,275 +1,29 @@ +bool PPCRecompiler_generateIntermediateCode(ppcImlGenContext_t& ppcImlGenContext, PPCRecFunction_t* PPCRecFunction, std::set<uint32>& entryAddresses, class PPCFunctionBoundaryTracker& boundaryTracker); -#define PPCREC_CR_REG_TEMP 8 // there are only 8 cr registers (0-7) we use the 8th as temporary cr register that is never stored (BDNZ instruction for example) +IMLSegment* PPCIMLGen_CreateSplitSegmentAtEnd(ppcImlGenContext_t& ppcImlGenContext, PPCBasicBlockInfo& basicBlockInfo); +IMLSegment* PPCIMLGen_CreateNewSegmentAsBranchTarget(ppcImlGenContext_t& ppcImlGenContext, PPCBasicBlockInfo& basicBlockInfo); -enum -{ - PPCREC_IML_OP_ASSIGN, // '=' operator - PPCREC_IML_OP_ENDIAN_SWAP, // '=' operator with 32bit endian swap - PPCREC_IML_OP_ADD, // '+' operator - PPCREC_IML_OP_SUB, // '-' operator - PPCREC_IML_OP_SUB_CARRY_UPDATE_CARRY, // complex operation, result = operand + ~operand2 + carry bit, updates carry bit - PPCREC_IML_OP_COMPARE_SIGNED, // arithmetic/signed comparison operator (updates cr) - PPCREC_IML_OP_COMPARE_UNSIGNED, // logical/unsigned comparison operator (updates cr) - PPCREC_IML_OP_MULTIPLY_SIGNED, // '*' operator (signed multiply) - PPCREC_IML_OP_MULTIPLY_HIGH_UNSIGNED, // unsigned 64bit multiply, store only high 32bit-word of result - PPCREC_IML_OP_MULTIPLY_HIGH_SIGNED, // signed 64bit multiply, store only high 32bit-word of result - PPCREC_IML_OP_DIVIDE_SIGNED, // '/' operator (signed divide) - PPCREC_IML_OP_DIVIDE_UNSIGNED, // '/' operator (unsigned divide) - PPCREC_IML_OP_ADD_CARRY, // complex operation, result = operand + carry bit, updates carry bit - PPCREC_IML_OP_ADD_CARRY_ME, // complex operation, result = operand + carry bit + (-1), updates carry bit - PPCREC_IML_OP_ADD_UPDATE_CARRY, // '+' operator but also updates carry flag - PPCREC_IML_OP_ADD_CARRY_UPDATE_CARRY, // '+' operator and also adds carry, updates carry flag - // assign operators with cast - PPCREC_IML_OP_ASSIGN_S16_TO_S32, // copy 16bit and sign extend - PPCREC_IML_OP_ASSIGN_S8_TO_S32, // copy 8bit and sign extend - // binary operation - PPCREC_IML_OP_OR, // '|' operator - PPCREC_IML_OP_ORC, // '|' operator, second operand is complemented first - PPCREC_IML_OP_AND, // '&' operator - PPCREC_IML_OP_XOR, // '^' operator - PPCREC_IML_OP_LEFT_ROTATE, // left rotate operator - PPCREC_IML_OP_LEFT_SHIFT, // shift left operator - PPCREC_IML_OP_RIGHT_SHIFT, // right shift operator (unsigned) - PPCREC_IML_OP_NOT, // complement each bit - PPCREC_IML_OP_NEG, // negate - // ppc - PPCREC_IML_OP_RLWIMI, // RLWIMI instruction (rotate, merge based on mask) - PPCREC_IML_OP_SRAW, // SRAWI/SRAW instruction (algebraic shift right, sets ca flag) - PPCREC_IML_OP_SLW, // SLW (shift based on register by up to 63 bits) - PPCREC_IML_OP_SRW, // SRW (shift based on register by up to 63 bits) - PPCREC_IML_OP_CNTLZW, - PPCREC_IML_OP_SUBFC, // SUBFC and SUBFIC (subtract from and set carry) - PPCREC_IML_OP_DCBZ, // clear 32 bytes aligned to 0x20 - PPCREC_IML_OP_MFCR, // copy cr to gpr - PPCREC_IML_OP_MTCRF, // copy gpr to cr (with mask) - // condition register - PPCREC_IML_OP_CR_CLEAR, // clear cr bit - PPCREC_IML_OP_CR_SET, // set cr bit - PPCREC_IML_OP_CR_OR, // OR cr bits - PPCREC_IML_OP_CR_ORC, // OR cr bits, complement second input operand bit first - PPCREC_IML_OP_CR_AND, // AND cr bits - PPCREC_IML_OP_CR_ANDC, // AND cr bits, complement second input operand bit first - // FPU - PPCREC_IML_OP_FPR_ADD_BOTTOM, - PPCREC_IML_OP_FPR_ADD_PAIR, - PPCREC_IML_OP_FPR_SUB_PAIR, - PPCREC_IML_OP_FPR_SUB_BOTTOM, - PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM, - PPCREC_IML_OP_FPR_MULTIPLY_PAIR, - PPCREC_IML_OP_FPR_DIVIDE_BOTTOM, - PPCREC_IML_OP_FPR_DIVIDE_PAIR, - PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, - PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM_AND_TOP, - PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, - PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_TOP, // leave bottom of destination untouched - PPCREC_IML_OP_FPR_COPY_TOP_TO_TOP, // leave bottom of destination untouched - PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM, // leave top of destination untouched - PPCREC_IML_OP_FPR_COPY_BOTTOM_AND_TOP_SWAPPED, - PPCREC_IML_OP_FPR_EXPAND_BOTTOM32_TO_BOTTOM64_AND_TOP64, // expand bottom f32 to f64 in bottom and top half - PPCREC_IML_OP_FPR_BOTTOM_FRES_TO_BOTTOM_AND_TOP, // calculate reciprocal with Espresso accuracy of source bottom half and write result to destination bottom and top half - PPCREC_IML_OP_FPR_FCMPO_BOTTOM, - PPCREC_IML_OP_FPR_FCMPU_BOTTOM, - PPCREC_IML_OP_FPR_FCMPU_TOP, - PPCREC_IML_OP_FPR_NEGATE_BOTTOM, - PPCREC_IML_OP_FPR_NEGATE_PAIR, - PPCREC_IML_OP_FPR_ABS_BOTTOM, // abs(fp0) - PPCREC_IML_OP_FPR_ABS_PAIR, - PPCREC_IML_OP_FPR_FRES_PAIR, // 1.0/fp approx (Espresso accuracy) - PPCREC_IML_OP_FPR_FRSQRTE_PAIR, // 1.0/sqrt(fp) approx (Espresso accuracy) - PPCREC_IML_OP_FPR_NEGATIVE_ABS_BOTTOM, // -abs(fp0) - PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_BOTTOM, // round 64bit double to 64bit double with 32bit float precision (in bottom half of xmm register) - PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_PAIR, // round two 64bit doubles to 64bit double with 32bit float precision - PPCREC_IML_OP_FPR_BOTTOM_RECIPROCAL_SQRT, - PPCREC_IML_OP_FPR_BOTTOM_FCTIWZ, - PPCREC_IML_OP_FPR_SELECT_BOTTOM, // selectively copy bottom value from operand B or C based on value in operand A - PPCREC_IML_OP_FPR_SELECT_PAIR, // selectively copy top/bottom from operand B or C based on value in top/bottom of operand A - // PS - PPCREC_IML_OP_FPR_SUM0, - PPCREC_IML_OP_FPR_SUM1, -}; +void PPCIMLGen_AssertIfNotLastSegmentInstruction(ppcImlGenContext_t& ppcImlGenContext); -#define PPCREC_IML_OP_FPR_COPY_PAIR (PPCREC_IML_OP_ASSIGN) - -enum -{ - PPCREC_IML_MACRO_BLR, // macro for BLR instruction code - PPCREC_IML_MACRO_BLRL, // macro for BLRL instruction code - PPCREC_IML_MACRO_BCTR, // macro for BCTR instruction code - PPCREC_IML_MACRO_BCTRL, // macro for BCTRL instruction code - PPCREC_IML_MACRO_BL, // call to different function (can be within same function) - PPCREC_IML_MACRO_B_FAR, // branch to different function - PPCREC_IML_MACRO_COUNT_CYCLES, // decrease current remaining thread cycles by a certain amount - PPCREC_IML_MACRO_HLE, // HLE function call - PPCREC_IML_MACRO_MFTB, // get TB register value (low or high) - PPCREC_IML_MACRO_LEAVE, // leaves recompiler and switches to interpeter - // debugging - PPCREC_IML_MACRO_DEBUGBREAK, // throws a debugbreak -}; - -enum -{ - PPCREC_JUMP_CONDITION_NONE, - PPCREC_JUMP_CONDITION_E, // equal / zero - PPCREC_JUMP_CONDITION_NE, // not equal / not zero - PPCREC_JUMP_CONDITION_LE, // less or equal - PPCREC_JUMP_CONDITION_L, // less - PPCREC_JUMP_CONDITION_GE, // greater or equal - PPCREC_JUMP_CONDITION_G, // greater - // special case: - PPCREC_JUMP_CONDITION_SUMMARYOVERFLOW, // needs special handling - PPCREC_JUMP_CONDITION_NSUMMARYOVERFLOW, // not summaryoverflow - -}; - -enum -{ - PPCREC_CR_MODE_COMPARE_SIGNED, - PPCREC_CR_MODE_COMPARE_UNSIGNED, // alias logic compare - // others: PPCREC_CR_MODE_ARITHMETIC, - PPCREC_CR_MODE_ARITHMETIC, // arithmetic use (for use with add/sub instructions without generating extra code) - PPCREC_CR_MODE_LOGICAL, -}; - -enum -{ - PPCREC_IML_TYPE_NONE, - PPCREC_IML_TYPE_NO_OP, // no-op instruction - PPCREC_IML_TYPE_JUMPMARK, // possible jump destination (generated before each ppc instruction) - PPCREC_IML_TYPE_R_R, // r* (op) *r - PPCREC_IML_TYPE_R_R_R, // r* = r* (op) r* - PPCREC_IML_TYPE_R_R_S32, // r* = r* (op) s32* - PPCREC_IML_TYPE_LOAD, // r* = [r*+s32*] - PPCREC_IML_TYPE_LOAD_INDEXED, // r* = [r*+r*] - PPCREC_IML_TYPE_STORE, // [r*+s32*] = r* - PPCREC_IML_TYPE_STORE_INDEXED, // [r*+r*] = r* - PPCREC_IML_TYPE_R_NAME, // r* = name - PPCREC_IML_TYPE_NAME_R, // name* = r* - PPCREC_IML_TYPE_R_S32, // r* (op) imm - PPCREC_IML_TYPE_MACRO, - PPCREC_IML_TYPE_CJUMP, // conditional jump - PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK, // jumps only if remaining thread cycles >= 0 - PPCREC_IML_TYPE_PPC_ENTER, // used to mark locations that should be written to recompilerCallTable - PPCREC_IML_TYPE_CR, // condition register specific operations (one or more operands) - // conditional - PPCREC_IML_TYPE_CONDITIONAL_R_S32, - // FPR - PPCREC_IML_TYPE_FPR_R_NAME, // name = f* - PPCREC_IML_TYPE_FPR_NAME_R, // f* = name - PPCREC_IML_TYPE_FPR_LOAD, // r* = (bitdepth) [r*+s32*] (single or paired single mode) - PPCREC_IML_TYPE_FPR_LOAD_INDEXED, // r* = (bitdepth) [r*+r*] (single or paired single mode) - PPCREC_IML_TYPE_FPR_STORE, // (bitdepth) [r*+s32*] = r* (single or paired single mode) - PPCREC_IML_TYPE_FPR_STORE_INDEXED, // (bitdepth) [r*+r*] = r* (single or paired single mode) - PPCREC_IML_TYPE_FPR_R_R, - PPCREC_IML_TYPE_FPR_R_R_R, - PPCREC_IML_TYPE_FPR_R_R_R_R, - PPCREC_IML_TYPE_FPR_R, - // special - PPCREC_IML_TYPE_MEM2MEM, // memory to memory copy (deprecated) - -}; - -enum -{ - PPCREC_NAME_NONE, - PPCREC_NAME_TEMPORARY, - PPCREC_NAME_R0 = 1000, - PPCREC_NAME_SPR0 = 2000, - PPCREC_NAME_FPR0 = 3000, - PPCREC_NAME_TEMPORARY_FPR0 = 4000, // 0 to 7 - //PPCREC_NAME_CR0 = 3000, // value mapped condition register (usually it isn't needed and can be optimized away) -}; - -// special cases for LOAD/STORE -#define PPC_REC_LOAD_LWARX_MARKER (100) // lwarx instruction (similar to LWZX but sets reserved address/value) -#define PPC_REC_STORE_STWCX_MARKER (100) // stwcx instruction (similar to STWX but writes only if reservation from LWARX is valid) -#define PPC_REC_STORE_STSWI_1 (200) // stswi nb = 1 -#define PPC_REC_STORE_STSWI_2 (201) // stswi nb = 2 -#define PPC_REC_STORE_STSWI_3 (202) // stswi nb = 3 -#define PPC_REC_STORE_LSWI_1 (200) // lswi nb = 1 -#define PPC_REC_STORE_LSWI_2 (201) // lswi nb = 2 -#define PPC_REC_STORE_LSWI_3 (202) // lswi nb = 3 - -#define PPC_REC_INVALID_REGISTER 0xFF - -#define PPCREC_CR_BIT_LT 0 -#define PPCREC_CR_BIT_GT 1 -#define PPCREC_CR_BIT_EQ 2 -#define PPCREC_CR_BIT_SO 3 - -enum -{ - // fpr load - PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0, - PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1, - PPCREC_FPR_LD_MODE_DOUBLE_INTO_PS0, - PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0, - PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1, - PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0, - PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0_PS1, - PPCREC_FPR_LD_MODE_PSQ_S16_PS0, - PPCREC_FPR_LD_MODE_PSQ_S16_PS0_PS1, - PPCREC_FPR_LD_MODE_PSQ_U16_PS0, - PPCREC_FPR_LD_MODE_PSQ_U16_PS0_PS1, - PPCREC_FPR_LD_MODE_PSQ_S8_PS0, - PPCREC_FPR_LD_MODE_PSQ_S8_PS0_PS1, - PPCREC_FPR_LD_MODE_PSQ_U8_PS0, - PPCREC_FPR_LD_MODE_PSQ_U8_PS0_PS1, - // fpr store - PPCREC_FPR_ST_MODE_SINGLE_FROM_PS0, // store 1 single precision float from ps0 - PPCREC_FPR_ST_MODE_DOUBLE_FROM_PS0, // store 1 double precision float from ps0 - - PPCREC_FPR_ST_MODE_UI32_FROM_PS0, // store raw low-32bit of PS0 - - PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1, - PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0, - PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0_PS1, - PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0, - PPCREC_FPR_ST_MODE_PSQ_S8_PS0, - PPCREC_FPR_ST_MODE_PSQ_S8_PS0_PS1, - PPCREC_FPR_ST_MODE_PSQ_U8_PS0, - PPCREC_FPR_ST_MODE_PSQ_U8_PS0_PS1, - PPCREC_FPR_ST_MODE_PSQ_U16_PS0, - PPCREC_FPR_ST_MODE_PSQ_U16_PS0_PS1, - PPCREC_FPR_ST_MODE_PSQ_S16_PS0, - PPCREC_FPR_ST_MODE_PSQ_S16_PS0_PS1, -}; - -bool PPCRecompiler_generateIntermediateCode(ppcImlGenContext_t& ppcImlGenContext, PPCRecFunction_t* PPCRecFunction, std::set<uint32>& entryAddresses); -void PPCRecompiler_freeContext(ppcImlGenContext_t* ppcImlGenContext); // todo - move to destructor - -PPCRecImlInstruction_t* PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext_t* ppcImlGenContext); -void PPCRecompiler_pushBackIMLInstructions(PPCRecImlSegment_t* imlSegment, sint32 index, sint32 shiftBackCount); -PPCRecImlInstruction_t* PPCRecompiler_insertInstruction(PPCRecImlSegment_t* imlSegment, sint32 index); +IMLInstruction* PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext_t* ppcImlGenContext); +void PPCRecompiler_pushBackIMLInstructions(IMLSegment* imlSegment, sint32 index, sint32 shiftBackCount); +IMLInstruction* PPCRecompiler_insertInstruction(IMLSegment* imlSegment, sint32 index); void PPCRecompilerIml_insertSegments(ppcImlGenContext_t* ppcImlGenContext, sint32 index, sint32 count); -void PPCRecompilerIml_setSegmentPoint(ppcRecompilerSegmentPoint_t* segmentPoint, PPCRecImlSegment_t* imlSegment, sint32 index); -void PPCRecompilerIml_removeSegmentPoint(ppcRecompilerSegmentPoint_t* segmentPoint); +void PPCRecompilerIml_setSegmentPoint(IMLSegmentPoint* segmentPoint, IMLSegment* imlSegment, sint32 index); +void PPCRecompilerIml_removeSegmentPoint(IMLSegmentPoint* segmentPoint); // GPR register management -uint32 PPCRecompilerImlGen_loadRegister(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName, bool loadNew = false); -uint32 PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName); +IMLReg PPCRecompilerImlGen_loadRegister(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName); // FPR register management -uint32 PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName, bool loadNew = false); -uint32 PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName); +IMLReg PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName, bool loadNew = false); +IMLReg PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName); // IML instruction generation -void PPCRecompilerImlGen_generateNewInstruction_jump(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction, uint32 jumpmarkAddress); -void PPCRecompilerImlGen_generateNewInstruction_jumpSegment(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction); - -void PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext_t* ppcImlGenContext, uint32 operation, uint8 registerIndex, sint32 immS32, uint32 copyWidth, bool signExtend, bool bigEndian, uint8 crRegister, uint32 crMode); -void PPCRecompilerImlGen_generateNewInstruction_conditional_r_s32(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction, uint32 operation, uint8 registerIndex, sint32 immS32, uint32 crRegisterIndex, uint32 crBitIndex, bool bitMustBeSet); -void PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction, uint32 operation, uint8 registerResult, uint8 registerA, uint8 crRegister = PPC_REC_INVALID_REGISTER, uint8 crMode = 0); - - - -// IML instruction generation (new style, can generate new instructions but also overwrite existing ones) - -void PPCRecompilerImlGen_generateNewInstruction_noOp(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction); -void PPCRecompilerImlGen_generateNewInstruction_memory_memory(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction, uint8 srcMemReg, sint32 srcImmS32, uint8 dstMemReg, sint32 dstImmS32, uint8 copyWidth); - -void PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction, sint32 operation, uint8 registerResult, sint32 crRegister = PPC_REC_INVALID_REGISTER); +void PPCRecompilerImlGen_generateNewInstruction_conditional_r_s32(ppcImlGenContext_t* ppcImlGenContext, IMLInstruction* imlInstruction, uint32 operation, IMLReg registerIndex, sint32 immS32, uint32 crRegisterIndex, uint32 crBitIndex, bool bitMustBeSet); +void PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext_t* ppcImlGenContext, IMLInstruction* imlInstruction, sint32 operation, IMLReg registerResult); // IML generation - FPU bool PPCRecompilerImlGen_LFS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); @@ -347,76 +101,4 @@ bool PPCRecompilerImlGen_PS_CMPU1(ppcImlGenContext_t* ppcImlGenContext, uint32 o // IML general -bool PPCRecompiler_isSuffixInstruction(PPCRecImlInstruction_t* iml); -void PPCRecompilerIML_linkSegments(ppcImlGenContext_t* ppcImlGenContext); -void PPCRecompilerIml_setLinkBranchNotTaken(PPCRecImlSegment_t* imlSegmentSrc, PPCRecImlSegment_t* imlSegmentDst); -void PPCRecompilerIml_setLinkBranchTaken(PPCRecImlSegment_t* imlSegmentSrc, PPCRecImlSegment_t* imlSegmentDst); -void PPCRecompilerIML_relinkInputSegment(PPCRecImlSegment_t* imlSegmentOrig, PPCRecImlSegment_t* imlSegmentNew); -void PPCRecompilerIML_removeLink(PPCRecImlSegment_t* imlSegmentSrc, PPCRecImlSegment_t* imlSegmentDst); void PPCRecompilerIML_isolateEnterableSegments(ppcImlGenContext_t* ppcImlGenContext); - -PPCRecImlInstruction_t* PPCRecompilerIML_getLastInstruction(PPCRecImlSegment_t* imlSegment); - -// IML analyzer -typedef struct -{ - uint32 readCRBits; - uint32 writtenCRBits; -}PPCRecCRTracking_t; - -bool PPCRecompilerImlAnalyzer_isTightFiniteLoop(PPCRecImlSegment_t* imlSegment); -bool PPCRecompilerImlAnalyzer_canTypeWriteCR(PPCRecImlInstruction_t* imlInstruction); -void PPCRecompilerImlAnalyzer_getCRTracking(PPCRecImlInstruction_t* imlInstruction, PPCRecCRTracking_t* crTracking); - -// IML optimizer -bool PPCRecompiler_reduceNumberOfFPRRegisters(ppcImlGenContext_t* ppcImlGenContext); - -bool PPCRecompiler_manageFPRRegisters(ppcImlGenContext_t* ppcImlGenContext); - -void PPCRecompiler_removeRedundantCRUpdates(ppcImlGenContext_t* ppcImlGenContext); -void PPCRecompiler_optimizeDirectFloatCopies(ppcImlGenContext_t* ppcImlGenContext); -void PPCRecompiler_optimizeDirectIntegerCopies(ppcImlGenContext_t* ppcImlGenContext); - -void PPCRecompiler_optimizePSQLoadAndStore(ppcImlGenContext_t* ppcImlGenContext); - -// IML register allocator -void PPCRecompilerImm_allocateRegisters(ppcImlGenContext_t* ppcImlGenContext); - -// late optimizations -void PPCRecompiler_reorderConditionModifyInstructions(ppcImlGenContext_t* ppcImlGenContext); - -// debug - -void PPCRecompiler_dumpIMLSegment(PPCRecImlSegment_t* imlSegment, sint32 segmentIndex, bool printLivenessRangeInfo = false); - - -typedef struct -{ - union - { - struct - { - sint16 readNamedReg1; - sint16 readNamedReg2; - sint16 readNamedReg3; - sint16 writtenNamedReg1; - }; - sint16 gpr[4]; // 3 read + 1 write - }; - // FPR - union - { - struct - { - // note: If destination operand is not fully written, it will be added as a read FPR as well - sint16 readFPR1; - sint16 readFPR2; - sint16 readFPR3; - sint16 readFPR4; // usually this is set to the result FPR if only partially overwritten - sint16 writtenFPR1; - }; - sint16 fpr[4]; - }; -}PPCImlOptimizerUsedRegisters_t; - -void PPCRecompiler_checkRegisterUsage(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction, PPCImlOptimizerUsedRegisters_t* registersUsed); diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlAnalyzer.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlAnalyzer.cpp deleted file mode 100644 index 4962d30d..00000000 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlAnalyzer.cpp +++ /dev/null @@ -1,137 +0,0 @@ -#include "PPCRecompiler.h" -#include "PPCRecompilerIml.h" -#include "util/helpers/fixedSizeList.h" -#include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" - -/* - * Initializes a single segment and returns true if it is a finite loop - */ -bool PPCRecompilerImlAnalyzer_isTightFiniteLoop(PPCRecImlSegment_t* imlSegment) -{ - bool isTightFiniteLoop = false; - // base criteria, must jump to beginning of same segment - if (imlSegment->nextSegmentBranchTaken != imlSegment) - return false; - // loops using BDNZ are assumed to always be finite - for (sint32 t = 0; t < imlSegment->imlListCount; t++) - { - if (imlSegment->imlList[t].type == PPCREC_IML_TYPE_R_S32 && imlSegment->imlList[t].operation == PPCREC_IML_OP_SUB && imlSegment->imlList[t].crRegister == 8) - { - return true; - } - } - // for non-BDNZ loops, check for common patterns - // risky approach, look for ADD/SUB operations and assume that potential overflow means finite (does not include r_r_s32 ADD/SUB) - // this catches most loops with load-update and store-update instructions, but also those with decrementing counters - FixedSizeList<sint32, 64, true> list_modifiedRegisters; - for (sint32 t = 0; t < imlSegment->imlListCount; t++) - { - if (imlSegment->imlList[t].type == PPCREC_IML_TYPE_R_S32 && (imlSegment->imlList[t].operation == PPCREC_IML_OP_ADD || imlSegment->imlList[t].operation == PPCREC_IML_OP_SUB) ) - { - list_modifiedRegisters.addUnique(imlSegment->imlList[t].op_r_immS32.registerIndex); - } - } - if (list_modifiedRegisters.count > 0) - { - // remove all registers from the list that are modified by non-ADD/SUB instructions - // todo: We should also cover the case where ADD+SUB on the same register cancel the effect out - PPCImlOptimizerUsedRegisters_t registersUsed; - for (sint32 t = 0; t < imlSegment->imlListCount; t++) - { - if (imlSegment->imlList[t].type == PPCREC_IML_TYPE_R_S32 && (imlSegment->imlList[t].operation == PPCREC_IML_OP_ADD || imlSegment->imlList[t].operation == PPCREC_IML_OP_SUB)) - continue; - PPCRecompiler_checkRegisterUsage(NULL, imlSegment->imlList + t, ®istersUsed); - if(registersUsed.writtenNamedReg1 < 0) - continue; - list_modifiedRegisters.remove(registersUsed.writtenNamedReg1); - } - if (list_modifiedRegisters.count > 0) - { - return true; - } - } - return false; -} - -/* -* Returns true if the imlInstruction can overwrite CR (depending on value of ->crRegister) -*/ -bool PPCRecompilerImlAnalyzer_canTypeWriteCR(PPCRecImlInstruction_t* imlInstruction) -{ - if (imlInstruction->type == PPCREC_IML_TYPE_R_R) - return true; - if (imlInstruction->type == PPCREC_IML_TYPE_R_R_R) - return true; - if (imlInstruction->type == PPCREC_IML_TYPE_R_R_S32) - return true; - if (imlInstruction->type == PPCREC_IML_TYPE_R_S32) - return true; - if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R) - return true; - if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R) - return true; - if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R_R) - return true; - if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R) - return true; - return false; -} - -void PPCRecompilerImlAnalyzer_getCRTracking(PPCRecImlInstruction_t* imlInstruction, PPCRecCRTracking_t* crTracking) -{ - crTracking->readCRBits = 0; - crTracking->writtenCRBits = 0; - if (imlInstruction->type == PPCREC_IML_TYPE_CJUMP) - { - if (imlInstruction->op_conditionalJump.condition != PPCREC_JUMP_CONDITION_NONE) - { - uint32 crBitFlag = 1 << (imlInstruction->op_conditionalJump.crRegisterIndex * 4 + imlInstruction->op_conditionalJump.crBitIndex); - crTracking->readCRBits = (crBitFlag); - } - } - else if (imlInstruction->type == PPCREC_IML_TYPE_CONDITIONAL_R_S32) - { - uint32 crBitFlag = 1 << (imlInstruction->op_conditional_r_s32.crRegisterIndex * 4 + imlInstruction->op_conditional_r_s32.crBitIndex); - crTracking->readCRBits = crBitFlag; - } - else if (imlInstruction->type == PPCREC_IML_TYPE_R_S32 && imlInstruction->operation == PPCREC_IML_OP_MFCR) - { - crTracking->readCRBits = 0xFFFFFFFF; - } - else if (imlInstruction->type == PPCREC_IML_TYPE_R_S32 && imlInstruction->operation == PPCREC_IML_OP_MTCRF) - { - crTracking->writtenCRBits |= ppc_MTCRFMaskToCRBitMask((uint32)imlInstruction->op_r_immS32.immS32); - } - else if (imlInstruction->type == PPCREC_IML_TYPE_CR) - { - if (imlInstruction->operation == PPCREC_IML_OP_CR_CLEAR || - imlInstruction->operation == PPCREC_IML_OP_CR_SET) - { - uint32 crBitFlag = 1 << (imlInstruction->op_cr.crD); - crTracking->writtenCRBits = crBitFlag; - } - else if (imlInstruction->operation == PPCREC_IML_OP_CR_OR || - imlInstruction->operation == PPCREC_IML_OP_CR_ORC || - imlInstruction->operation == PPCREC_IML_OP_CR_AND || - imlInstruction->operation == PPCREC_IML_OP_CR_ANDC) - { - uint32 crBitFlag = 1 << (imlInstruction->op_cr.crD); - crTracking->writtenCRBits = crBitFlag; - crBitFlag = 1 << (imlInstruction->op_cr.crA); - crTracking->readCRBits = crBitFlag; - crBitFlag = 1 << (imlInstruction->op_cr.crB); - crTracking->readCRBits |= crBitFlag; - } - else - assert_dbg(); - } - else if (PPCRecompilerImlAnalyzer_canTypeWriteCR(imlInstruction) && imlInstruction->crRegister >= 0 && imlInstruction->crRegister <= 7) - { - crTracking->writtenCRBits |= (0xF << (imlInstruction->crRegister * 4)); - } - else if ((imlInstruction->type == PPCREC_IML_TYPE_STORE || imlInstruction->type == PPCREC_IML_TYPE_STORE_INDEXED) && imlInstruction->op_storeLoad.copyWidth == PPC_REC_STORE_STWCX_MARKER) - { - // overwrites CR0 - crTracking->writtenCRBits |= (0xF << 0); - } -} diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGen.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGen.cpp index b9685488..a705baf8 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGen.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGen.cpp @@ -1,563 +1,248 @@ #include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" #include "Cafe/HW/Espresso/Interpreter/PPCInterpreterHelper.h" +#include "Cafe/HW/Espresso/EspressoISA.h" #include "PPCRecompiler.h" #include "PPCRecompilerIml.h" -#include "PPCRecompilerX64.h" -#include "PPCRecompilerImlRanges.h" -#include "util/helpers/StringBuf.h" +#include "IML/IML.h" +#include "IML/IMLRegisterAllocatorRanges.h" +#include "PPCFunctionBoundaryTracker.h" +#include "Cafe/OS/libs/coreinit/coreinit_Time.h" bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext); -uint32 PPCRecompiler_iterateCurrentInstruction(ppcImlGenContext_t* ppcImlGenContext); -uint32 PPCRecompiler_getInstructionByOffset(ppcImlGenContext_t* ppcImlGenContext, uint32 offset); -PPCRecImlInstruction_t* PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext_t* ppcImlGenContext) +struct PPCBasicBlockInfo { - if( ppcImlGenContext->imlListCount+1 > ppcImlGenContext->imlListSize ) + PPCBasicBlockInfo(uint32 startAddress, const std::set<uint32>& entryAddresses) : startAddress(startAddress), lastAddress(startAddress) { - sint32 newSize = ppcImlGenContext->imlListCount*2 + 2; - ppcImlGenContext->imlList = (PPCRecImlInstruction_t*)realloc(ppcImlGenContext->imlList, sizeof(PPCRecImlInstruction_t)*newSize); - ppcImlGenContext->imlListSize = newSize; + isEnterable = entryAddresses.find(startAddress) != entryAddresses.end(); } - PPCRecImlInstruction_t* imlInstruction = ppcImlGenContext->imlList+ppcImlGenContext->imlListCount; - memset(imlInstruction, 0x00, sizeof(PPCRecImlInstruction_t)); - imlInstruction->crRegister = PPC_REC_INVALID_REGISTER; // dont update any cr register by default - imlInstruction->associatedPPCAddress = ppcImlGenContext->ppcAddressOfCurrentInstruction; - ppcImlGenContext->imlListCount++; - return imlInstruction; + + uint32 startAddress; + uint32 lastAddress; // inclusive + bool isEnterable{ false }; + bool hasContinuedFlow{ true }; // non-branch path goes to next segment, assumed by default + bool hasBranchTarget{ false }; + uint32 branchTarget{}; + + // associated IML segments + IMLSegment* firstSegment{}; // first segment in chain, used as branch target for other segments + IMLSegment* appendSegment{}; // last segment in chain, additional instructions should be appended to this segment + + void SetInitialSegment(IMLSegment* seg) + { + cemu_assert_debug(!firstSegment); + cemu_assert_debug(!appendSegment); + firstSegment = seg; + appendSegment = seg; + } + + IMLSegment* GetFirstSegmentInChain() + { + return firstSegment; + } + + IMLSegment* GetSegmentForInstructionAppend() + { + return appendSegment; + } +}; + +IMLInstruction* PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext_t* ppcImlGenContext) +{ + IMLInstruction& inst = ppcImlGenContext->currentOutputSegment->imlList.emplace_back(); + memset(&inst, 0x00, sizeof(IMLInstruction)); + return &inst; } -void PPCRecompilerImlGen_generateNewInstruction_jumpmark(ppcImlGenContext_t* ppcImlGenContext, uint32 address) +void PPCRecompilerImlGen_generateNewInstruction_r_memory_indexed(ppcImlGenContext_t* ppcImlGenContext, IMLReg registerDestination, IMLReg registerMemory1, IMLReg registerMemory2, uint32 copyWidth, bool signExtend, bool switchEndian) { - // no-op that indicates possible destination of a jump - PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); - imlInstruction->type = PPCREC_IML_TYPE_JUMPMARK; - imlInstruction->op_jumpmark.address = address; -} - -void PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext_t* ppcImlGenContext, uint32 macroId, uint32 param, uint32 param2, uint16 paramU16) -{ - // no-op that indicates possible destination of a jump - PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); - imlInstruction->type = PPCREC_IML_TYPE_MACRO; - imlInstruction->operation = macroId; - imlInstruction->op_macro.param = param; - imlInstruction->op_macro.param2 = param2; - imlInstruction->op_macro.paramU16 = paramU16; -} - -/* - * Generates a marker for Interpreter -> Recompiler entrypoints - * PPC_ENTER iml instructions have no associated PPC address but the instruction itself has one - */ -void PPCRecompilerImlGen_generateNewInstruction_ppcEnter(ppcImlGenContext_t* ppcImlGenContext, uint32 ppcAddress) -{ - // no-op that indicates possible destination of a jump - PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); - imlInstruction->type = PPCREC_IML_TYPE_PPC_ENTER; - imlInstruction->operation = 0; - imlInstruction->op_ppcEnter.ppcAddress = ppcAddress; - imlInstruction->op_ppcEnter.x64Offset = 0; - imlInstruction->associatedPPCAddress = 0; -} - -void PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction, uint32 operation, uint8 registerResult, uint8 registerA, uint8 crRegister, uint8 crMode) -{ - // operation with two register operands (e.g. "t0 = t1") - if(imlInstruction == NULL) - imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); - imlInstruction->type = PPCREC_IML_TYPE_R_R; - imlInstruction->operation = operation; - imlInstruction->crRegister = crRegister; - imlInstruction->crMode = crMode; - imlInstruction->op_r_r.registerResult = registerResult; - imlInstruction->op_r_r.registerA = registerA; -} - -void PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext_t* ppcImlGenContext, uint32 operation, uint8 registerResult, uint8 registerA, uint8 registerB, uint8 crRegister=PPC_REC_INVALID_REGISTER, uint8 crMode=0) -{ - // operation with three register operands (e.g. "t0 = t1 + t4") - PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); - imlInstruction->type = PPCREC_IML_TYPE_R_R_R; - imlInstruction->operation = operation; - imlInstruction->crRegister = crRegister; - imlInstruction->crMode = crMode; - imlInstruction->op_r_r_r.registerResult = registerResult; - imlInstruction->op_r_r_r.registerA = registerA; - imlInstruction->op_r_r_r.registerB = registerB; -} - -void PPCRecompilerImlGen_generateNewInstruction_r_r_s32(ppcImlGenContext_t* ppcImlGenContext, uint32 operation, uint8 registerResult, uint8 registerA, sint32 immS32, uint8 crRegister=PPC_REC_INVALID_REGISTER, uint8 crMode=0) -{ - // operation with two register operands and one signed immediate (e.g. "t0 = t1 + 1234") - PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); - imlInstruction->type = PPCREC_IML_TYPE_R_R_S32; - imlInstruction->operation = operation; - imlInstruction->crRegister = crRegister; - imlInstruction->crMode = crMode; - imlInstruction->op_r_r_s32.registerResult = registerResult; - imlInstruction->op_r_r_s32.registerA = registerA; - imlInstruction->op_r_r_s32.immS32 = immS32; -} - -void PPCRecompilerImlGen_generateNewInstruction_name_r(ppcImlGenContext_t* ppcImlGenContext, uint32 operation, uint8 registerIndex, uint32 name, uint32 copyWidth, bool signExtend, bool bigEndian) -{ - // Store name (e.g. "'r3' = t0" which translates to MOV [ESP+offset_r3], reg32) - PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); - imlInstruction->type = PPCREC_IML_TYPE_NAME_R; - imlInstruction->operation = operation; - imlInstruction->op_r_name.registerIndex = registerIndex; - imlInstruction->op_r_name.name = name; - imlInstruction->op_r_name.copyWidth = copyWidth; - imlInstruction->op_r_name.flags = (signExtend?PPCREC_IML_OP_FLAG_SIGNEXTEND:0)|(bigEndian?PPCREC_IML_OP_FLAG_SWITCHENDIAN:0); -} - -void PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext_t* ppcImlGenContext, uint32 operation, uint8 registerIndex, sint32 immS32, uint32 copyWidth, bool signExtend, bool bigEndian, uint8 crRegister, uint32 crMode) -{ - // two variations: - // operation without store (e.g. "'r3' < 123" which has no effect other than updating a condition flags register) - // operation with store (e.g. "'r3' = 123") - PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); - imlInstruction->type = PPCREC_IML_TYPE_R_S32; - imlInstruction->operation = operation; - imlInstruction->crRegister = crRegister; - imlInstruction->crMode = crMode; - imlInstruction->op_r_immS32.registerIndex = registerIndex; - imlInstruction->op_r_immS32.immS32 = immS32; -} - -void PPCRecompilerImlGen_generateNewInstruction_conditional_r_s32(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction, uint32 operation, uint8 registerIndex, sint32 immS32, uint32 crRegisterIndex, uint32 crBitIndex, bool bitMustBeSet) -{ - if(imlInstruction == NULL) - imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); - else - memset(imlInstruction, 0, sizeof(PPCRecImlInstruction_t)); - imlInstruction->type = PPCREC_IML_TYPE_CONDITIONAL_R_S32; - imlInstruction->operation = operation; - imlInstruction->crRegister = PPC_REC_INVALID_REGISTER; - // r_s32 operation - imlInstruction->op_conditional_r_s32.registerIndex = registerIndex; - imlInstruction->op_conditional_r_s32.immS32 = immS32; - // condition - imlInstruction->op_conditional_r_s32.crRegisterIndex = crRegisterIndex; - imlInstruction->op_conditional_r_s32.crBitIndex = crBitIndex; - imlInstruction->op_conditional_r_s32.bitMustBeSet = bitMustBeSet; -} - - -void PPCRecompilerImlGen_generateNewInstruction_jump(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction, uint32 jumpmarkAddress) -{ - // jump - if (imlInstruction == NULL) - imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); - else - memset(imlInstruction, 0, sizeof(PPCRecImlInstruction_t)); - imlInstruction->type = PPCREC_IML_TYPE_CJUMP; - imlInstruction->crRegister = PPC_REC_INVALID_REGISTER; - imlInstruction->op_conditionalJump.jumpmarkAddress = jumpmarkAddress; - imlInstruction->op_conditionalJump.jumpAccordingToSegment = false; - imlInstruction->op_conditionalJump.condition = PPCREC_JUMP_CONDITION_NONE; - imlInstruction->op_conditionalJump.crRegisterIndex = 0; - imlInstruction->op_conditionalJump.crBitIndex = 0; - imlInstruction->op_conditionalJump.bitMustBeSet = false; -} - -// jump based on segment branches -void PPCRecompilerImlGen_generateNewInstruction_jumpSegment(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction) -{ - // jump - if (imlInstruction == NULL) - imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); - imlInstruction->associatedPPCAddress = 0; - imlInstruction->type = PPCREC_IML_TYPE_CJUMP; - imlInstruction->crRegister = PPC_REC_INVALID_REGISTER; - imlInstruction->op_conditionalJump.jumpmarkAddress = 0; - imlInstruction->op_conditionalJump.jumpAccordingToSegment = true; - imlInstruction->op_conditionalJump.condition = PPCREC_JUMP_CONDITION_NONE; - imlInstruction->op_conditionalJump.crRegisterIndex = 0; - imlInstruction->op_conditionalJump.crBitIndex = 0; - imlInstruction->op_conditionalJump.bitMustBeSet = false; -} - -void PPCRecompilerImlGen_generateNewInstruction_noOp(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction) -{ - if (imlInstruction == NULL) - imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); - imlInstruction->type = PPCREC_IML_TYPE_NO_OP; - imlInstruction->operation = 0; - imlInstruction->crRegister = PPC_REC_INVALID_REGISTER; - imlInstruction->crMode = 0; -} - -void PPCRecompilerImlGen_generateNewInstruction_cr(ppcImlGenContext_t* ppcImlGenContext, uint32 operation, uint8 crD, uint8 crA, uint8 crB) -{ - // multiple variations: - // operation involving only one cr bit (like clear crD bit) - // operation involving three cr bits (like crD = crA or crB) - PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); - imlInstruction->type = PPCREC_IML_TYPE_CR; - imlInstruction->operation = operation; - imlInstruction->crRegister = PPC_REC_INVALID_REGISTER; - imlInstruction->crMode = 0; - imlInstruction->op_cr.crD = crD; - imlInstruction->op_cr.crA = crA; - imlInstruction->op_cr.crB = crB; -} - -void PPCRecompilerImlGen_generateNewInstruction_conditionalJump(ppcImlGenContext_t* ppcImlGenContext, uint32 jumpmarkAddress, uint32 jumpCondition, uint32 crRegisterIndex, uint32 crBitIndex, bool bitMustBeSet) -{ - // conditional jump - PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); - imlInstruction->type = PPCREC_IML_TYPE_CJUMP; - imlInstruction->crRegister = PPC_REC_INVALID_REGISTER; - imlInstruction->op_conditionalJump.jumpmarkAddress = jumpmarkAddress; - imlInstruction->op_conditionalJump.condition = jumpCondition; - imlInstruction->op_conditionalJump.crRegisterIndex = crRegisterIndex; - imlInstruction->op_conditionalJump.crBitIndex = crBitIndex; - imlInstruction->op_conditionalJump.bitMustBeSet = bitMustBeSet; -} - -void PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext_t* ppcImlGenContext, uint8 registerDestination, uint8 registerMemory, sint32 immS32, uint32 copyWidth, bool signExtend, bool switchEndian) -{ - // load from memory - PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); - imlInstruction->type = PPCREC_IML_TYPE_LOAD; - imlInstruction->operation = 0; - imlInstruction->crRegister = PPC_REC_INVALID_REGISTER; - imlInstruction->op_storeLoad.registerData = registerDestination; - imlInstruction->op_storeLoad.registerMem = registerMemory; - imlInstruction->op_storeLoad.immS32 = immS32; - imlInstruction->op_storeLoad.copyWidth = copyWidth; - //imlInstruction->op_storeLoad.flags = (signExtend ? PPCREC_IML_OP_FLAG_SIGNEXTEND : 0) | (switchEndian ? PPCREC_IML_OP_FLAG_SWITCHENDIAN : 0); - imlInstruction->op_storeLoad.flags2.swapEndian = switchEndian; - imlInstruction->op_storeLoad.flags2.signExtend = signExtend; -} - -void PPCRecompilerImlGen_generateNewInstruction_r_memory_indexed(ppcImlGenContext_t* ppcImlGenContext, uint8 registerDestination, uint8 registerMemory1, uint8 registerMemory2, uint32 copyWidth, bool signExtend, bool switchEndian) -{ - // load from memory - PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + cemu_assert_debug(registerMemory1.IsValid()); + cemu_assert_debug(registerMemory2.IsValid()); + cemu_assert_debug(registerDestination.IsValid()); + IMLInstruction* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); imlInstruction->type = PPCREC_IML_TYPE_LOAD_INDEXED; imlInstruction->operation = 0; - imlInstruction->crRegister = PPC_REC_INVALID_REGISTER; imlInstruction->op_storeLoad.registerData = registerDestination; imlInstruction->op_storeLoad.registerMem = registerMemory1; imlInstruction->op_storeLoad.registerMem2 = registerMemory2; imlInstruction->op_storeLoad.copyWidth = copyWidth; - //imlInstruction->op_storeLoad.flags = (signExtend?PPCREC_IML_OP_FLAG_SIGNEXTEND:0)|(switchEndian?PPCREC_IML_OP_FLAG_SWITCHENDIAN:0); imlInstruction->op_storeLoad.flags2.swapEndian = switchEndian; imlInstruction->op_storeLoad.flags2.signExtend = signExtend; } -void PPCRecompilerImlGen_generateNewInstruction_memory_r(ppcImlGenContext_t* ppcImlGenContext, uint8 registerSource, uint8 registerMemory, sint32 immS32, uint32 copyWidth, bool switchEndian) +void PPCRecompilerImlGen_generateNewInstruction_memory_r_indexed(ppcImlGenContext_t* ppcImlGenContext, IMLReg registerDestination, IMLReg registerMemory1, IMLReg registerMemory2, uint32 copyWidth, bool signExtend, bool switchEndian) { - // load from memory - PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); - imlInstruction->type = PPCREC_IML_TYPE_STORE; - imlInstruction->operation = 0; - imlInstruction->crRegister = PPC_REC_INVALID_REGISTER; - imlInstruction->op_storeLoad.registerData = registerSource; - imlInstruction->op_storeLoad.registerMem = registerMemory; - imlInstruction->op_storeLoad.immS32 = immS32; - imlInstruction->op_storeLoad.copyWidth = copyWidth; - //imlInstruction->op_storeLoad.flags = (switchEndian?PPCREC_IML_OP_FLAG_SWITCHENDIAN:0); - imlInstruction->op_storeLoad.flags2.swapEndian = switchEndian; - imlInstruction->op_storeLoad.flags2.signExtend = false; -} - -void PPCRecompilerImlGen_generateNewInstruction_memory_r_indexed(ppcImlGenContext_t* ppcImlGenContext, uint8 registerDestination, uint8 registerMemory1, uint8 registerMemory2, uint32 copyWidth, bool signExtend, bool switchEndian) -{ - // load from memory - PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + cemu_assert_debug(registerMemory1.IsValid()); + cemu_assert_debug(registerMemory2.IsValid()); + cemu_assert_debug(registerDestination.IsValid()); + IMLInstruction* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); imlInstruction->type = PPCREC_IML_TYPE_STORE_INDEXED; imlInstruction->operation = 0; - imlInstruction->crRegister = PPC_REC_INVALID_REGISTER; imlInstruction->op_storeLoad.registerData = registerDestination; imlInstruction->op_storeLoad.registerMem = registerMemory1; imlInstruction->op_storeLoad.registerMem2 = registerMemory2; imlInstruction->op_storeLoad.copyWidth = copyWidth; - //imlInstruction->op_storeLoad.flags = (signExtend?PPCREC_IML_OP_FLAG_SIGNEXTEND:0)|(switchEndian?PPCREC_IML_OP_FLAG_SWITCHENDIAN:0); imlInstruction->op_storeLoad.flags2.swapEndian = switchEndian; imlInstruction->op_storeLoad.flags2.signExtend = signExtend; } -void PPCRecompilerImlGen_generateNewInstruction_memory_memory(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction, uint8 srcMemReg, sint32 srcImmS32, uint8 dstMemReg, sint32 dstImmS32, uint8 copyWidth) +// create and fill two segments (branch taken and branch not taken) as a follow up to the current segment and then merge flow afterwards +template<typename F1n, typename F2n> +void PPCIMLGen_CreateSegmentBranchedPath(ppcImlGenContext_t& ppcImlGenContext, PPCBasicBlockInfo& basicBlockInfo, F1n genSegmentBranchTaken, F2n genSegmentBranchNotTaken) { - // copy from memory to memory - if(imlInstruction == NULL) - imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); - imlInstruction->type = PPCREC_IML_TYPE_MEM2MEM; - imlInstruction->operation = 0; - imlInstruction->crRegister = PPC_REC_INVALID_REGISTER; - imlInstruction->op_mem2mem.src.registerMem = srcMemReg; - imlInstruction->op_mem2mem.src.immS32 = srcImmS32; - imlInstruction->op_mem2mem.dst.registerMem = dstMemReg; - imlInstruction->op_mem2mem.dst.immS32 = dstImmS32; - imlInstruction->op_mem2mem.copyWidth = copyWidth; + IMLSegment* currentWriteSegment = basicBlockInfo.GetSegmentForInstructionAppend(); + + std::span<IMLSegment*> segments = ppcImlGenContext.InsertSegments(ppcImlGenContext.GetSegmentIndex(currentWriteSegment) + 1, 3); + IMLSegment* segBranchNotTaken = segments[0]; + IMLSegment* segBranchTaken = segments[1]; + IMLSegment* segMerge = segments[2]; + + // link the segments + segMerge->SetLinkBranchTaken(currentWriteSegment->GetBranchTaken()); + segMerge->SetLinkBranchNotTaken(currentWriteSegment->GetBranchNotTaken()); + currentWriteSegment->SetLinkBranchTaken(segBranchTaken); + currentWriteSegment->SetLinkBranchNotTaken(segBranchNotTaken); + segBranchTaken->SetLinkBranchNotTaken(segMerge); + segBranchNotTaken->SetLinkBranchTaken(segMerge); + // generate code for branch taken segment + ppcImlGenContext.currentOutputSegment = segBranchTaken; + genSegmentBranchTaken(ppcImlGenContext); + cemu_assert_debug(ppcImlGenContext.currentOutputSegment == segBranchTaken); + // generate code for branch not taken segment + ppcImlGenContext.currentOutputSegment = segBranchNotTaken; + genSegmentBranchNotTaken(ppcImlGenContext); + cemu_assert_debug(ppcImlGenContext.currentOutputSegment == segBranchNotTaken); + ppcImlGenContext.emitInst().make_jump(); + // make merge segment the new write segment + ppcImlGenContext.currentOutputSegment = segMerge; + basicBlockInfo.appendSegment = segMerge; } -uint32 PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName) +IMLReg PPCRecompilerImlGen_LookupReg(ppcImlGenContext_t* ppcImlGenContext, IMLName mappedName, IMLRegFormat regFormat) { - if( mappedName == PPCREC_NAME_NONE ) + auto it = ppcImlGenContext->mappedRegs.find(mappedName); + if (it != ppcImlGenContext->mappedRegs.end()) + return it->second; + // create new reg entry + IMLRegFormat baseFormat; + if (regFormat == IMLRegFormat::F64) + baseFormat = IMLRegFormat::F64; + else if (regFormat == IMLRegFormat::I32) + baseFormat = IMLRegFormat::I64; + else { - debug_printf("PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(): Invalid mappedName parameter\n"); - return PPC_REC_INVALID_REGISTER; + cemu_assert_suspicious(); } - for(uint32 i=0; i<(PPC_REC_MAX_VIRTUAL_GPR-1); i++) + IMLRegID newRegId = ppcImlGenContext->mappedRegs.size(); + IMLReg newReg(baseFormat, regFormat, 0, newRegId); + ppcImlGenContext->mappedRegs.try_emplace(mappedName, newReg); + return newReg; +} + +IMLName PPCRecompilerImlGen_GetRegName(ppcImlGenContext_t* ppcImlGenContext, IMLReg reg) +{ + for (auto& it : ppcImlGenContext->mappedRegs) { - if( ppcImlGenContext->mappedRegister[i] == PPCREC_NAME_NONE ) - { - ppcImlGenContext->mappedRegister[i] = mappedName; - return i; - } + if (it.second.GetRegID() == reg.GetRegID()) + return it.first; } + cemu_assert(false); return 0; } -uint32 PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName) -{ - for(uint32 i=0; i< PPC_REC_MAX_VIRTUAL_GPR; i++) - { - if( ppcImlGenContext->mappedRegister[i] == mappedName ) - { - return i; - } - } - return PPC_REC_INVALID_REGISTER; -} - uint32 PPCRecompilerImlGen_getAndLockFreeTemporaryFPR(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName) { - if( mappedName == PPCREC_NAME_NONE ) - { - debug_printf("PPCRecompilerImlGen_getAndLockFreeTemporaryFPR(): Invalid mappedName parameter\n"); - return PPC_REC_INVALID_REGISTER; - } - for(uint32 i=0; i<255; i++) - { - if( ppcImlGenContext->mappedFPRRegister[i] == PPCREC_NAME_NONE ) - { - ppcImlGenContext->mappedFPRRegister[i] = mappedName; - return i; - } - } + DEBUG_BREAK; + //if( mappedName == PPCREC_NAME_NONE ) + //{ + // debug_printf("PPCRecompilerImlGen_getAndLockFreeTemporaryFPR(): Invalid mappedName parameter\n"); + // return PPC_REC_INVALID_REGISTER; + //} + //for(uint32 i=0; i<255; i++) + //{ + // if( ppcImlGenContext->mappedFPRRegister[i] == PPCREC_NAME_NONE ) + // { + // ppcImlGenContext->mappedFPRRegister[i] = mappedName; + // return i; + // } + //} return 0; } uint32 PPCRecompilerImlGen_findFPRRegisterByMappedName(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName) { - for(uint32 i=0; i<255; i++) - { - if( ppcImlGenContext->mappedFPRRegister[i] == mappedName ) - { - return i; - } - } + DEBUG_BREAK; + //for(uint32 i=0; i<255; i++) + //{ + // if( ppcImlGenContext->mappedFPRRegister[i] == mappedName ) + // { + // return i; + // } + //} return PPC_REC_INVALID_REGISTER; } -/* - * Loads a PPC gpr into any of the available IML registers - * If loadNew is false, it will reuse already loaded instances - */ -uint32 PPCRecompilerImlGen_loadRegister(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName, bool loadNew) +IMLReg PPCRecompilerImlGen_loadRegister(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName) { - if( loadNew == false ) - { - uint32 loadedRegisterIndex = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, mappedName); - if( loadedRegisterIndex != PPC_REC_INVALID_REGISTER ) - return loadedRegisterIndex; - } - uint32 registerIndex = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, mappedName); - return registerIndex; + return PPCRecompilerImlGen_LookupReg(ppcImlGenContext, mappedName, IMLRegFormat::I32); } -/* - * Reuse already loaded register if present - * Otherwise create new IML register and map the name. The register contents will be undefined - */ -uint32 PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName) +IMLReg _GetRegGPR(ppcImlGenContext_t* ppcImlGenContext, uint32 index) { - uint32 loadedRegisterIndex = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, mappedName); - if( loadedRegisterIndex != PPC_REC_INVALID_REGISTER ) - return loadedRegisterIndex; - uint32 registerIndex = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, mappedName); - return registerIndex; + cemu_assert_debug(index < 32); + return PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + index); +} + +IMLReg _GetRegCR(ppcImlGenContext_t* ppcImlGenContext, uint32 index) +{ + cemu_assert_debug(index < 32); + return PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_CR + index); +} + +IMLReg _GetRegCR(ppcImlGenContext_t* ppcImlGenContext, uint8 crReg, uint8 crBit) +{ + cemu_assert_debug(crReg < 8); + cemu_assert_debug(crBit < 4); + return _GetRegCR(ppcImlGenContext, (crReg * 4) + crBit); +} + +IMLReg _GetRegTemporary(ppcImlGenContext_t* ppcImlGenContext, uint32 index) +{ + cemu_assert_debug(index < 4); + return PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + index); +} + +// get throw-away register. Only valid for the scope of a single translated instruction +// be careful to not collide with manually loaded temporary register +IMLReg _GetRegTemporaryS8(ppcImlGenContext_t* ppcImlGenContext, uint32 index) +{ + cemu_assert_debug(index < 4); + return PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + index); } /* * Loads a PPC fpr into any of the available IML FPU registers * If loadNew is false, it will check first if the fpr is already loaded into any IML register */ -uint32 PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName, bool loadNew) +IMLReg PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName, bool loadNew) { - if( loadNew == false ) - { - uint32 loadedRegisterIndex = PPCRecompilerImlGen_findFPRRegisterByMappedName(ppcImlGenContext, mappedName); - if( loadedRegisterIndex != PPC_REC_INVALID_REGISTER ) - return loadedRegisterIndex; - } - uint32 registerIndex = PPCRecompilerImlGen_getAndLockFreeTemporaryFPR(ppcImlGenContext, mappedName); - return registerIndex; + return PPCRecompilerImlGen_LookupReg(ppcImlGenContext, mappedName, IMLRegFormat::F64); } /* * Checks if a PPC fpr register is already loaded into any IML register - * If no, it will create a new undefined temporary IML FPU register and map the name (effectively overwriting the old ppc register) + * If not, it will create a new undefined temporary IML FPU register and map the name (effectively overwriting the old ppc register) */ -uint32 PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName) +IMLReg PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName) { - uint32 loadedRegisterIndex = PPCRecompilerImlGen_findFPRRegisterByMappedName(ppcImlGenContext, mappedName); - if( loadedRegisterIndex != PPC_REC_INVALID_REGISTER ) - return loadedRegisterIndex; - uint32 registerIndex = PPCRecompilerImlGen_getAndLockFreeTemporaryFPR(ppcImlGenContext, mappedName); - return registerIndex; -} - -void PPCRecompilerImlGen_TW(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ -//#ifdef CEMU_DEBUG_ASSERT -// PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, 0, 0); -//#endif - PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_LEAVE, ppcImlGenContext->ppcAddressOfCurrentInstruction, 0, 0); -} - -bool PPCRecompilerImlGen_MTSPR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - uint32 rD, spr1, spr2, spr; - PPC_OPC_TEMPL_XO(opcode, rD, spr1, spr2); - spr = spr1 | (spr2<<5); - if (spr == SPR_CTR || spr == SPR_LR) - { - uint32 gprReg = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0 + rD); - if (gprReg == PPC_REC_INVALID_REGISTER) - gprReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rD); - uint32 sprReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + spr); - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, sprReg, gprReg); - } - else if (spr >= SPR_UGQR0 && spr <= SPR_UGQR7) - { - uint32 gprReg = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0 + rD); - if (gprReg == PPC_REC_INVALID_REGISTER) - gprReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rD); - uint32 sprReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + spr); - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, sprReg, gprReg); - ppcImlGenContext->tracking.modifiesGQR[spr - SPR_UGQR0] = true; - } - else - return false; - return true; -} - -bool PPCRecompilerImlGen_MFSPR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - uint32 rD, spr1, spr2, spr; - PPC_OPC_TEMPL_XO(opcode, rD, spr1, spr2); - spr = spr1 | (spr2<<5); - if (spr == SPR_LR || spr == SPR_CTR) - { - uint32 sprReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + spr); - uint32 gprReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0 + rD); - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprReg, sprReg); - } - else if (spr >= SPR_UGQR0 && spr <= SPR_UGQR7) - { - uint32 sprReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + spr); - uint32 gprReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0 + rD); - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprReg, sprReg); - } - else - return false; - return true; -} - -bool PPCRecompilerImlGen_MFTB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - uint32 rD, spr1, spr2, spr; - PPC_OPC_TEMPL_XO(opcode, rD, spr1, spr2); - spr = spr1 | (spr2<<5); - - if (spr == 268 || spr == 269) - { - // TBL / TBU - uint32 param2 = spr | (rD << 16); - PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_MFTB, ppcImlGenContext->ppcAddressOfCurrentInstruction, param2, 0); - return true; - } - return false; -} - -bool PPCRecompilerImlGen_MFCR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rD, rA, rB; - PPC_OPC_TEMPL_X(opcode, rD, rA, rB); - uint32 gprReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0 + rD); - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_MFCR, gprReg, 0, 0, false, false, PPC_REC_INVALID_REGISTER, 0); - return true; -} - -bool PPCRecompilerImlGen_MTCRF(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - uint32 rS; - uint32 crMask; - PPC_OPC_TEMPL_XFX(opcode, rS, crMask); - uint32 gprReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0 + rS); - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_MTCRF, gprReg, crMask, 0, false, false, PPC_REC_INVALID_REGISTER, 0); - return true; -} - -void PPCRecompilerImlGen_CMP(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - uint32 cr; - int rA, rB; - PPC_OPC_TEMPL_X(opcode, cr, rA, rB); - cr >>= 2; - uint32 gprRegisterA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_COMPARE_SIGNED, gprRegisterA, gprRegisterB, cr, PPCREC_CR_MODE_COMPARE_SIGNED); -} - -void PPCRecompilerImlGen_CMPL(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - uint32 cr; - int rA, rB; - PPC_OPC_TEMPL_X(opcode, cr, rA, rB); - cr >>= 2; - uint32 gprRegisterA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_COMPARE_UNSIGNED, gprRegisterA, gprRegisterB, cr, PPCREC_CR_MODE_COMPARE_UNSIGNED); -} - -void PPCRecompilerImlGen_CMPI(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - uint32 cr; - int rA; - uint32 imm; - PPC_OPC_TEMPL_D_SImm(opcode, cr, rA, imm); - cr >>= 2; - sint32 b = imm; - // load gpr into register - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_COMPARE_SIGNED, gprRegister, b, 0, false, false, cr, PPCREC_CR_MODE_COMPARE_SIGNED); -} - -void PPCRecompilerImlGen_CMPLI(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - uint32 cr; - int rA; - uint32 imm; - PPC_OPC_TEMPL_D_UImm(opcode, cr, rA, imm); - cr >>= 2; - uint32 b = imm; - // load gpr into register - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_COMPARE_UNSIGNED, gprRegister, (sint32)b, 0, false, false, cr, PPCREC_CR_MODE_COMPARE_UNSIGNED); + return PPCRecompilerImlGen_LookupReg(ppcImlGenContext, mappedName, IMLRegFormat::F64); } bool PPCRecompiler_canInlineFunction(MPTR functionPtr, sint32* functionInstructionCount) { for (sint32 i = 0; i < 6; i++) { - uint32 opcode = memory_readU32(functionPtr+i*4); + uint32 opcode = memory_readU32(functionPtr + i * 4); switch ((opcode >> 26)) { case 14: // ADDI @@ -611,18 +296,220 @@ void PPCRecompiler_generateInlinedCode(ppcImlGenContext_t* ppcImlGenContext, uin { for (sint32 i = 0; i < instructionCount; i++) { - ppcImlGenContext->ppcAddressOfCurrentInstruction = startAddress + i*4; + ppcImlGenContext->ppcAddressOfCurrentInstruction = startAddress + i * 4; ppcImlGenContext->cyclesSinceLastBranch++; if (PPCRecompiler_decodePPCInstruction(ppcImlGenContext)) { - assert_dbg(); + cemu_assert_suspicious(); } } // add range - ppcRecRange_t recRange; - recRange.ppcAddress = startAddress; - recRange.ppcSize = instructionCount*4 + 4; // + 4 because we have to include the BLR - ppcImlGenContext->functionRef->list_ranges.push_back(recRange); + cemu_assert_unimplemented(); + //ppcRecRange_t recRange; + //recRange.ppcAddress = startAddress; + //recRange.ppcSize = instructionCount*4 + 4; // + 4 because we have to include the BLR + //ppcImlGenContext->functionRef->list_ranges.push_back(recRange); +} + +// for handling RC bit of many instructions +void PPCImlGen_UpdateCR0(ppcImlGenContext_t* ppcImlGenContext, IMLReg regR) +{ + IMLReg crBitRegLT = _GetRegCR(ppcImlGenContext, 0, Espresso::CR_BIT::CR_BIT_INDEX_LT); + IMLReg crBitRegGT = _GetRegCR(ppcImlGenContext, 0, Espresso::CR_BIT::CR_BIT_INDEX_GT); + IMLReg crBitRegEQ = _GetRegCR(ppcImlGenContext, 0, Espresso::CR_BIT::CR_BIT_INDEX_EQ); + // todo - SO bit + + ppcImlGenContext->emitInst().make_compare_s32(regR, 0, crBitRegLT, IMLCondition::SIGNED_LT); + ppcImlGenContext->emitInst().make_compare_s32(regR, 0, crBitRegGT, IMLCondition::SIGNED_GT); + ppcImlGenContext->emitInst().make_compare_s32(regR, 0, crBitRegEQ, IMLCondition::EQ); + + //ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, crBitRegSO, 0); // todo - copy from XER + + //ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, registerR, registerR, 0, PPCREC_CR_MODE_LOGICAL); +} + +void PPCRecompilerImlGen_TW(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + // split before and after to make sure the macro is in an isolated segment that we can make enterable + PPCIMLGen_CreateSplitSegmentAtEnd(*ppcImlGenContext, *ppcImlGenContext->currentBasicBlock); + ppcImlGenContext->currentOutputSegment->SetEnterable(ppcImlGenContext->ppcAddressOfCurrentInstruction); + PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext)->make_macro(PPCREC_IML_MACRO_LEAVE, ppcImlGenContext->ppcAddressOfCurrentInstruction, 0, 0, IMLREG_INVALID); + IMLSegment* middleSeg = PPCIMLGen_CreateSplitSegmentAtEnd(*ppcImlGenContext, *ppcImlGenContext->currentBasicBlock); + middleSeg->SetLinkBranchTaken(nullptr); + middleSeg->SetLinkBranchNotTaken(nullptr); +} + +bool PPCRecompilerImlGen_MTSPR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + uint32 rD, spr1, spr2, spr; + PPC_OPC_TEMPL_XO(opcode, rD, spr1, spr2); + spr = spr1 | (spr2<<5); + IMLReg gprReg = _GetRegGPR(ppcImlGenContext, rD); + if (spr == SPR_CTR || spr == SPR_LR) + { + IMLReg sprReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + spr); + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, sprReg, gprReg); + } + else if (spr >= SPR_UGQR0 && spr <= SPR_UGQR7) + { + IMLReg sprReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + spr); + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, sprReg, gprReg); + ppcImlGenContext->tracking.modifiesGQR[spr - SPR_UGQR0] = true; + } + else + return false; + return true; +} + +bool PPCRecompilerImlGen_MFSPR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + uint32 rD, spr1, spr2, spr; + PPC_OPC_TEMPL_XO(opcode, rD, spr1, spr2); + spr = spr1 | (spr2<<5); + IMLReg gprReg = _GetRegGPR(ppcImlGenContext, rD); + if (spr == SPR_LR || spr == SPR_CTR) + { + IMLReg sprReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + spr); + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, gprReg, sprReg); + } + else if (spr >= SPR_UGQR0 && spr <= SPR_UGQR7) + { + IMLReg sprReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + spr); + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, gprReg, sprReg); + } + else + return false; + return true; +} + +ATTR_MS_ABI uint32 PPCRecompiler_GetTBL() +{ + return (uint32)coreinit::OSGetSystemTime(); +} + +ATTR_MS_ABI uint32 PPCRecompiler_GetTBU() +{ + return (uint32)(coreinit::OSGetSystemTime() >> 32); +} + +bool PPCRecompilerImlGen_MFTB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + uint32 rD, spr1, spr2, spr; + PPC_OPC_TEMPL_XO(opcode, rD, spr1, spr2); + spr = spr1 | (spr2<<5); + + if( spr == SPR_TBL || spr == SPR_TBU ) + { + IMLReg resultReg = _GetRegGPR(ppcImlGenContext, rD); + ppcImlGenContext->emitInst().make_call_imm(spr == SPR_TBL ? (uintptr_t)PPCRecompiler_GetTBL : (uintptr_t)PPCRecompiler_GetTBU, IMLREG_INVALID, IMLREG_INVALID, IMLREG_INVALID, resultReg); + return true; + } + return false; +} + +void PPCRecompilerImlGen_MCRF(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + uint32 crD, crS, b; + PPC_OPC_TEMPL_X(opcode, crD, crS, b); + cemu_assert_debug((crD&3) == 0); + cemu_assert_debug((crS&3) == 0); + crD >>= 2; + crS >>= 2; + for (sint32 i = 0; i<4; i++) + { + IMLReg regCrSrcBit = _GetRegCR(ppcImlGenContext, crS * 4 + i); + IMLReg regCrDstBit = _GetRegCR(ppcImlGenContext, crD * 4 + i); + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, regCrDstBit, regCrSrcBit); + } +} + +bool PPCRecompilerImlGen_MFCR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rD, rA, rB; + PPC_OPC_TEMPL_X(opcode, rD, rA, rB); + IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); + ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, regD, 0); + for (sint32 i = 0; i < 32; i++) + { + IMLReg regCrBit = _GetRegCR(ppcImlGenContext, i); + cemu_assert_debug(regCrBit.GetRegFormat() == IMLRegFormat::I32); // addition is only allowed between same-format regs + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_LEFT_SHIFT, regD, regD, 1); + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_ADD, regD, regD, regCrBit); + } + return true; +} + +bool PPCRecompilerImlGen_MTCRF(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + uint32 rS; + uint32 crMask; + PPC_OPC_TEMPL_XFX(opcode, rS, crMask); + IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); + IMLReg regTmp = _GetRegTemporary(ppcImlGenContext, 0); + uint32 crBitMask = ppc_MTCRFMaskToCRBitMask(crMask); + for (sint32 f = 0; f < 32; f++) + { + if(((crBitMask >> f) & 1) == 0) + continue; + IMLReg regCrBit = _GetRegCR(ppcImlGenContext, f); + cemu_assert_debug(regCrBit.GetRegFormat() == IMLRegFormat::I32); + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_RIGHT_SHIFT_U, regTmp, regS, (31-f)); + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_AND, regCrBit, regTmp, 1); + } + return true; +} + +void PPCRecompilerImlGen_CMP(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool isUnsigned) +{ + uint32 cr; + int rA, rB; + PPC_OPC_TEMPL_X(opcode, cr, rA, rB); + cr >>= 2; + + IMLReg gprRegisterA = _GetRegGPR(ppcImlGenContext, rA); + IMLReg gprRegisterB = _GetRegGPR(ppcImlGenContext, rB); + IMLReg regXerSO = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_XER_SO); + + IMLReg crBitRegLT = _GetRegCR(ppcImlGenContext, cr, Espresso::CR_BIT::CR_BIT_INDEX_LT); + IMLReg crBitRegGT = _GetRegCR(ppcImlGenContext, cr, Espresso::CR_BIT::CR_BIT_INDEX_GT); + IMLReg crBitRegEQ = _GetRegCR(ppcImlGenContext, cr, Espresso::CR_BIT::CR_BIT_INDEX_EQ); + IMLReg crBitRegSO = _GetRegCR(ppcImlGenContext, cr, Espresso::CR_BIT::CR_BIT_INDEX_SO); + + ppcImlGenContext->emitInst().make_compare(gprRegisterA, gprRegisterB, crBitRegLT, isUnsigned ? IMLCondition::UNSIGNED_LT : IMLCondition::SIGNED_LT); + ppcImlGenContext->emitInst().make_compare(gprRegisterA, gprRegisterB, crBitRegGT, isUnsigned ? IMLCondition::UNSIGNED_GT : IMLCondition::SIGNED_GT); + ppcImlGenContext->emitInst().make_compare(gprRegisterA, gprRegisterB, crBitRegEQ, IMLCondition::EQ); + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, crBitRegSO, regXerSO); +} + +bool PPCRecompilerImlGen_CMPI(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool isUnsigned) +{ + uint32 cr; + int rA; + uint32 imm; + if (isUnsigned) + { + PPC_OPC_TEMPL_D_UImm(opcode, cr, rA, imm); + } + else + { + PPC_OPC_TEMPL_D_SImm(opcode, cr, rA, imm); + } + cr >>= 2; + + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); + IMLReg regXerSO = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_XER_SO); + + IMLReg crBitRegLT = _GetRegCR(ppcImlGenContext, cr, Espresso::CR_BIT::CR_BIT_INDEX_LT); + IMLReg crBitRegGT = _GetRegCR(ppcImlGenContext, cr, Espresso::CR_BIT::CR_BIT_INDEX_GT); + IMLReg crBitRegEQ = _GetRegCR(ppcImlGenContext, cr, Espresso::CR_BIT::CR_BIT_INDEX_EQ); + IMLReg crBitRegSO = _GetRegCR(ppcImlGenContext, cr, Espresso::CR_BIT::CR_BIT_INDEX_SO); + + ppcImlGenContext->emitInst().make_compare_s32(regA, (sint32)imm, crBitRegLT, isUnsigned ? IMLCondition::UNSIGNED_LT : IMLCondition::SIGNED_LT); + ppcImlGenContext->emitInst().make_compare_s32(regA, (sint32)imm, crBitRegGT, isUnsigned ? IMLCondition::UNSIGNED_GT : IMLCondition::SIGNED_GT); + ppcImlGenContext->emitInst().make_compare_s32(regA, (sint32)imm, crBitRegEQ, IMLCondition::EQ); + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, crBitRegSO, regXerSO); + + return true; } bool PPCRecompilerImlGen_B(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) @@ -637,43 +524,26 @@ bool PPCRecompilerImlGen_B(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) if( opcode&PPC_OPC_LK ) { // function call - // check if function can be inlined - sint32 inlineFuncInstructionCount = 0; - if (PPCRecompiler_canInlineFunction(jumpAddressDest, &inlineFuncInstructionCount)) - { - // generate NOP iml instead of BL macro (this assures that segment PPC range remains intact) - PPCRecompilerImlGen_generateNewInstruction_noOp(ppcImlGenContext, NULL); - //cemuLog_log(LogType::Force, "Inline func 0x{:08x} at {:08x}", jumpAddressDest, ppcImlGenContext->ppcAddressOfCurrentInstruction); - uint32* prevInstructionPtr = ppcImlGenContext->currentInstruction; - ppcImlGenContext->currentInstruction = (uint32*)memory_getPointerFromVirtualOffset(jumpAddressDest); - PPCRecompiler_generateInlinedCode(ppcImlGenContext, jumpAddressDest, inlineFuncInstructionCount); - ppcImlGenContext->currentInstruction = prevInstructionPtr; - return true; - } - // generate funtion call instructions - PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_BL, ppcImlGenContext->ppcAddressOfCurrentInstruction, jumpAddressDest, ppcImlGenContext->cyclesSinceLastBranch); - PPCRecompilerImlGen_generateNewInstruction_ppcEnter(ppcImlGenContext, ppcImlGenContext->ppcAddressOfCurrentInstruction+4); + ppcImlGenContext->emitInst().make_macro(PPCREC_IML_MACRO_BL, ppcImlGenContext->ppcAddressOfCurrentInstruction, jumpAddressDest, ppcImlGenContext->cyclesSinceLastBranch, IMLREG_INVALID); return true; } // is jump destination within recompiled function? - if( jumpAddressDest >= ppcImlGenContext->functionRef->ppcAddress && jumpAddressDest < (ppcImlGenContext->functionRef->ppcAddress + ppcImlGenContext->functionRef->ppcSize) ) - { - // generate instruction - PPCRecompilerImlGen_generateNewInstruction_jump(ppcImlGenContext, NULL, jumpAddressDest); - } + if (ppcImlGenContext->boundaryTracker->ContainsAddress(jumpAddressDest)) + ppcImlGenContext->emitInst().make_jump(); else - { - // todo: Inline this jump destination if possible (in many cases it's a bunch of GPR/FPR store instructions + BLR) - PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_B_FAR, ppcImlGenContext->ppcAddressOfCurrentInstruction, jumpAddressDest, ppcImlGenContext->cyclesSinceLastBranch); - } + ppcImlGenContext->emitInst().make_macro(PPCREC_IML_MACRO_B_FAR, ppcImlGenContext->ppcAddressOfCurrentInstruction, jumpAddressDest, ppcImlGenContext->cyclesSinceLastBranch, IMLREG_INVALID); return true; } bool PPCRecompilerImlGen_BC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { + PPCIMLGen_AssertIfNotLastSegmentInstruction(*ppcImlGenContext); + uint32 BO, BI, BD; PPC_OPC_TEMPL_B(opcode, BO, BI, BD); + Espresso::BOField boField(BO); + uint32 crRegister = BI/4; uint32 crBit = BI%4; uint32 jumpCondition = 0; @@ -682,6 +552,10 @@ bool PPCRecompilerImlGen_BC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) bool decrementerMustBeZero = (BO&2)!=0; // bit set -> branch if CTR = 0, bit not set -> branch if CTR != 0 bool ignoreCondition = (BO&16)!=0; + IMLReg regCRBit; + if (!ignoreCondition) + regCRBit = _GetRegCR(ppcImlGenContext, crRegister, crBit); + uint32 jumpAddressDest = BD; if( (opcode&PPC_OPC_AA) == 0 ) { @@ -690,37 +564,15 @@ bool PPCRecompilerImlGen_BC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) if( opcode&PPC_OPC_LK ) { + if (useDecrementer) + return false; // conditional function calls are not supported if( ignoreCondition == false ) { - // generate jump condition - if( conditionMustBeTrue ) - { - if( crBit == 0 ) - jumpCondition = PPCREC_JUMP_CONDITION_GE; - else if( crBit == 1 ) - jumpCondition = PPCREC_JUMP_CONDITION_LE; - else if( crBit == 2 ) - jumpCondition = PPCREC_JUMP_CONDITION_NE; - else if( crBit == 3 ) - jumpCondition = PPCREC_JUMP_CONDITION_NSUMMARYOVERFLOW; - } - else - { - if( crBit == 0 ) - jumpCondition = PPCREC_JUMP_CONDITION_L; - else if( crBit == 1 ) - jumpCondition = PPCREC_JUMP_CONDITION_G; - else if( crBit == 2 ) - jumpCondition = PPCREC_JUMP_CONDITION_E; - else if( crBit == 3 ) - jumpCondition = PPCREC_JUMP_CONDITION_SUMMARYOVERFLOW; - } - // generate instruction - //PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, 0, 0); - PPCRecompilerImlGen_generateNewInstruction_conditionalJump(ppcImlGenContext, ppcImlGenContext->ppcAddressOfCurrentInstruction+4, jumpCondition, crRegister, crBit, !conditionMustBeTrue); - PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_BL, ppcImlGenContext->ppcAddressOfCurrentInstruction, jumpAddressDest, ppcImlGenContext->cyclesSinceLastBranch); - PPCRecompilerImlGen_generateNewInstruction_ppcEnter(ppcImlGenContext, ppcImlGenContext->ppcAddressOfCurrentInstruction+4); + PPCBasicBlockInfo* currentBasicBlock = ppcImlGenContext->currentBasicBlock; + IMLSegment* blSeg = PPCIMLGen_CreateNewSegmentAsBranchTarget(*ppcImlGenContext, *currentBasicBlock); + ppcImlGenContext->emitInst().make_conditional_jump(regCRBit, conditionMustBeTrue); + blSeg->AppendInstruction()->make_macro(PPCREC_IML_MACRO_BL, ppcImlGenContext->ppcAddressOfCurrentInstruction, jumpAddressDest, ppcImlGenContext->cyclesSinceLastBranch, IMLREG_INVALID); return true; } return false; @@ -730,12 +582,11 @@ bool PPCRecompilerImlGen_BC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { if( ignoreCondition == false ) return false; // not supported for the moment - uint32 ctrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0+SPR_CTR, false); - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_SUB, ctrRegister, 1, 0, false, false, PPCREC_CR_REG_TEMP, PPCREC_CR_MODE_ARITHMETIC); - if( decrementerMustBeZero ) - PPCRecompilerImlGen_generateNewInstruction_conditionalJump(ppcImlGenContext, jumpAddressDest, PPCREC_JUMP_CONDITION_E, PPCREC_CR_REG_TEMP, 0, false); - else - PPCRecompilerImlGen_generateNewInstruction_conditionalJump(ppcImlGenContext, jumpAddressDest, PPCREC_JUMP_CONDITION_NE, PPCREC_CR_REG_TEMP, 0, false); + IMLReg ctrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0+SPR_CTR); + IMLReg tmpBoolReg = _GetRegTemporaryS8(ppcImlGenContext, 1); + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_SUB, ctrRegister, ctrRegister, 1); + ppcImlGenContext->emitInst().make_compare_s32(ctrRegister, 0, tmpBoolReg, decrementerMustBeZero ? IMLCondition::EQ : IMLCondition::NEQ); + ppcImlGenContext->emitInst().make_conditional_jump(tmpBoolReg, true); return true; } else @@ -743,219 +594,90 @@ bool PPCRecompilerImlGen_BC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) if( ignoreCondition ) { // branch always, no condition and no decrementer - debugBreakpoint(); - crRegister = PPC_REC_INVALID_REGISTER; // not necessary but lets optimizer know we dont care for cr register on this instruction + // not supported + return false; } else { - // generate jump condition - if( conditionMustBeTrue ) - { - if( crBit == 0 ) - jumpCondition = PPCREC_JUMP_CONDITION_GE; - else if( crBit == 1 ) - jumpCondition = PPCREC_JUMP_CONDITION_LE; - else if( crBit == 2 ) - jumpCondition = PPCREC_JUMP_CONDITION_NE; - else if( crBit == 3 ) - jumpCondition = PPCREC_JUMP_CONDITION_NSUMMARYOVERFLOW; - } - else - { - if( crBit == 0 ) - jumpCondition = PPCREC_JUMP_CONDITION_L; - else if( crBit == 1 ) - jumpCondition = PPCREC_JUMP_CONDITION_G; - else if( crBit == 2 ) - jumpCondition = PPCREC_JUMP_CONDITION_E; - else if( crBit == 3 ) - jumpCondition = PPCREC_JUMP_CONDITION_SUMMARYOVERFLOW; - } - - if (jumpAddressDest >= ppcImlGenContext->functionRef->ppcAddress && jumpAddressDest < (ppcImlGenContext->functionRef->ppcAddress + ppcImlGenContext->functionRef->ppcSize)) + if (ppcImlGenContext->boundaryTracker->ContainsAddress(jumpAddressDest)) { // near jump - PPCRecompilerImlGen_generateNewInstruction_conditionalJump(ppcImlGenContext, jumpAddressDest, jumpCondition, crRegister, crBit, conditionMustBeTrue); + ppcImlGenContext->emitInst().make_conditional_jump(regCRBit, conditionMustBeTrue); } else { // far jump - PPCRecompilerImlGen_generateNewInstruction_conditionalJump(ppcImlGenContext, ppcImlGenContext->ppcAddressOfCurrentInstruction + 4, jumpCondition, crRegister, crBit, !conditionMustBeTrue); - PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_B_FAR, ppcImlGenContext->ppcAddressOfCurrentInstruction, jumpAddressDest, ppcImlGenContext->cyclesSinceLastBranch); - PPCRecompilerImlGen_generateNewInstruction_ppcEnter(ppcImlGenContext, ppcImlGenContext->ppcAddressOfCurrentInstruction + 4); + debug_printf("PPCRecompilerImlGen_BC(): Far jump not supported yet"); + return false; } } } return true; } -bool PPCRecompilerImlGen_BCLR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +// BCCTR or BCLR +bool PPCRecompilerImlGen_BCSPR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, uint32 sprReg) { - uint32 BO, BI, BD; - PPC_OPC_TEMPL_XL(opcode, BO, BI, BD); + PPCIMLGen_AssertIfNotLastSegmentInstruction(*ppcImlGenContext); + Espresso::BOField BO; + uint32 BI; + bool LK; + Espresso::decodeOp_BCSPR(opcode, BO, BI, LK); uint32 crRegister = BI/4; uint32 crBit = BI%4; - uint32 jumpCondition = 0; + IMLReg regCRBit; + if (!BO.conditionIgnore()) + regCRBit = _GetRegCR(ppcImlGenContext, crRegister, crBit); - bool conditionMustBeTrue = (BO&8)!=0; - bool useDecrementer = (BO&4)==0; // bit not set -> decrement - bool decrementerMustBeZero = (BO&2)!=0; // bit set -> branch if CTR = 0, bit not set -> branch if CTR != 0 - bool ignoreCondition = (BO&16)!=0; - bool saveLR = (opcode&PPC_OPC_LK)!=0; - // since we skip this instruction if the condition is true, we need to invert the logic - bool invertedConditionMustBeTrue = !conditionMustBeTrue; - if( useDecrementer ) + IMLReg branchDestReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + sprReg); + if (LK) { - cemu_assert_debug(false); - return false; // unsupported + if (sprReg == SPR_LR) + { + // if the branch target is LR, then preserve it in a temporary + cemu_assert_suspicious(); // this case needs testing + IMLReg tmpRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY); + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, tmpRegister, branchDestReg); + branchDestReg = tmpRegister; + } + IMLReg registerLR = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + SPR_LR); + ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, registerLR, ppcImlGenContext->ppcAddressOfCurrentInstruction + 4); + } + + if (!BO.decrementerIgnore()) + { + cemu_assert_unimplemented(); + return false; + } + else if (!BO.conditionIgnore()) + { + // no decrementer but CR check + cemu_assert_debug(ppcImlGenContext->currentBasicBlock->hasContinuedFlow); + cemu_assert_debug(!ppcImlGenContext->currentBasicBlock->hasBranchTarget); + PPCBasicBlockInfo* currentBasicBlock = ppcImlGenContext->currentBasicBlock; + IMLSegment* bctrSeg = PPCIMLGen_CreateNewSegmentAsBranchTarget(*ppcImlGenContext, *currentBasicBlock); + ppcImlGenContext->emitInst().make_conditional_jump(regCRBit, !BO.conditionInverted()); + bctrSeg->AppendInstruction()->make_macro(PPCREC_IML_MACRO_B_TO_REG, 0, 0, 0, branchDestReg); } else { - if( ignoreCondition ) - { - // store LR - if( saveLR ) - { - PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_BLRL, ppcImlGenContext->ppcAddressOfCurrentInstruction, 0, ppcImlGenContext->cyclesSinceLastBranch); - PPCRecompilerImlGen_generateNewInstruction_ppcEnter(ppcImlGenContext, ppcImlGenContext->ppcAddressOfCurrentInstruction+4); - } - else - { - // branch always, no condition and no decrementer - PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_BLR, ppcImlGenContext->ppcAddressOfCurrentInstruction, 0, ppcImlGenContext->cyclesSinceLastBranch); - } - } - else - { - // store LR - if( saveLR ) - { - uint32 registerLR = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_SPR0+SPR_LR); - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, registerLR, (ppcImlGenContext->ppcAddressOfCurrentInstruction+4)&0x7FFFFFFF, 0, false, false, PPC_REC_INVALID_REGISTER, 0); - } - // generate jump condition - if( invertedConditionMustBeTrue ) - { - if( crBit == 0 ) - jumpCondition = PPCREC_JUMP_CONDITION_L; - else if( crBit == 1 ) - jumpCondition = PPCREC_JUMP_CONDITION_G; - else if( crBit == 2 ) - jumpCondition = PPCREC_JUMP_CONDITION_E; - else if( crBit == 3 ) - jumpCondition = PPCREC_JUMP_CONDITION_SUMMARYOVERFLOW; - } - else - { - if( crBit == 0 ) - jumpCondition = PPCREC_JUMP_CONDITION_GE; - else if( crBit == 1 ) - jumpCondition = PPCREC_JUMP_CONDITION_LE; - else if( crBit == 2 ) - jumpCondition = PPCREC_JUMP_CONDITION_NE; - else if( crBit == 3 ) - jumpCondition = PPCREC_JUMP_CONDITION_NSUMMARYOVERFLOW; - } - // jump if BCLR condition NOT met (jump to jumpmark of next instruction, essentially skipping current instruction) - PPCRecompilerImlGen_generateNewInstruction_conditionalJump(ppcImlGenContext, ppcImlGenContext->ppcAddressOfCurrentInstruction+4, jumpCondition, crRegister, crBit, invertedConditionMustBeTrue); - PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_BLR, ppcImlGenContext->ppcAddressOfCurrentInstruction, 0, ppcImlGenContext->cyclesSinceLastBranch); - } - } - return true; -} - -bool PPCRecompilerImlGen_BCCTR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - uint32 BO, BI, BD; - PPC_OPC_TEMPL_XL(opcode, BO, BI, BD); - - uint32 crRegister = BI/4; - uint32 crBit = BI%4; - - uint32 jumpCondition = 0; - - bool conditionMustBeTrue = (BO&8)!=0; - bool useDecrementer = (BO&4)==0; // bit not set -> decrement - bool decrementerMustBeZero = (BO&2)!=0; // bit set -> branch if CTR = 0, bit not set -> branch if CTR != 0 - bool ignoreCondition = (BO&16)!=0; - bool saveLR = (opcode&PPC_OPC_LK)!=0; - // since we skip this instruction if the condition is true, we need to invert the logic - bool invertedConditionMustBeTrue = !conditionMustBeTrue; - if( useDecrementer ) - { - assert_dbg(); - // if added, dont forget inverted logic - debug_printf("Rec: BCLR unsupported decrementer\n"); - return false; // unsupported - } - else - { - if( ignoreCondition ) - { - // store LR - if( saveLR ) - { - uint32 registerLR = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_SPR0+SPR_LR); - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, registerLR, (ppcImlGenContext->ppcAddressOfCurrentInstruction+4)&0x7FFFFFFF, 0, false, false, PPC_REC_INVALID_REGISTER, 0); - PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_BCTRL, ppcImlGenContext->ppcAddressOfCurrentInstruction, 0, ppcImlGenContext->cyclesSinceLastBranch); - PPCRecompilerImlGen_generateNewInstruction_ppcEnter(ppcImlGenContext, ppcImlGenContext->ppcAddressOfCurrentInstruction+4); - } - else - { - // branch always, no condition and no decrementer - PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_BCTR, ppcImlGenContext->ppcAddressOfCurrentInstruction, 0, ppcImlGenContext->cyclesSinceLastBranch); - } - } - else - { - // store LR - if( saveLR ) - { - uint32 registerLR = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_SPR0+SPR_LR); - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, registerLR, (ppcImlGenContext->ppcAddressOfCurrentInstruction+4)&0x7FFFFFFF, 0, false, false, PPC_REC_INVALID_REGISTER, 0); - } - // generate jump condition - if( invertedConditionMustBeTrue ) - { - if( crBit == 0 ) - jumpCondition = PPCREC_JUMP_CONDITION_L; - else if( crBit == 1 ) - jumpCondition = PPCREC_JUMP_CONDITION_G; - else if( crBit == 2 ) - jumpCondition = PPCREC_JUMP_CONDITION_E; - else if( crBit == 3 ) - jumpCondition = PPCREC_JUMP_CONDITION_SUMMARYOVERFLOW; - } - else - { - if( crBit == 0 ) - jumpCondition = PPCREC_JUMP_CONDITION_GE; - else if( crBit == 1 ) - jumpCondition = PPCREC_JUMP_CONDITION_LE; - else if( crBit == 2 ) - jumpCondition = PPCREC_JUMP_CONDITION_NE; - else if( crBit == 3 ) - jumpCondition = PPCREC_JUMP_CONDITION_NSUMMARYOVERFLOW; - } - // jump if BCLR condition NOT met (jump to jumpmark of next instruction, essentially skipping current instruction) - PPCRecompilerImlGen_generateNewInstruction_conditionalJump(ppcImlGenContext, ppcImlGenContext->ppcAddressOfCurrentInstruction+4, jumpCondition, crRegister, crBit, invertedConditionMustBeTrue); - PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_BCTR, ppcImlGenContext->ppcAddressOfCurrentInstruction, 0, ppcImlGenContext->cyclesSinceLastBranch); - } + // branch always, no condition and no decrementer check + cemu_assert_debug(!ppcImlGenContext->currentBasicBlock->hasContinuedFlow); + cemu_assert_debug(!ppcImlGenContext->currentBasicBlock->hasBranchTarget); + ppcImlGenContext->emitInst().make_macro(PPCREC_IML_MACRO_B_TO_REG, 0, 0, 0, branchDestReg); } return true; } bool PPCRecompilerImlGen_ISYNC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { - // does not need to be translated return true; } bool PPCRecompilerImlGen_SYNC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { - // does not need to be translated return true; } @@ -963,102 +685,12 @@ bool PPCRecompilerImlGen_ADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode { sint32 rD, rA, rB; PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); - //hCPU->gpr[rD] = (int)hCPU->gpr[rA] + (int)hCPU->gpr[rB]; - uint32 registerRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 registerRB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); - uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); - if( opcode&PPC_OPC_RC ) - { - PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_ADD, registerRD, registerRA, registerRB, 0, PPCREC_CR_MODE_LOGICAL); - } - else - { - PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_ADD, registerRD, registerRA, registerRB); - } - return true; -} - -bool PPCRecompilerImlGen_ADDC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rD, rA, rB; - PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); - //hCPU->gpr[rD] = (int)hCPU->gpr[rA] + (int)hCPU->gpr[rB]; -> Update carry - uint32 registerRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 registerRB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); - uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); - if( opcode&PPC_OPC_RC ) - PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_ADD_UPDATE_CARRY, registerRD, registerRA, registerRB, 0, PPCREC_CR_MODE_LOGICAL); - else - PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_ADD_UPDATE_CARRY, registerRD, registerRA, registerRB); - return true; -} - -bool PPCRecompilerImlGen_ADDE(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rD, rA, rB; - PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); - // hCPU->gpr[rD] = hCPU->gpr[rA] + hCPU->gpr[rB] + ca; - uint32 registerRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 registerRB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); - uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); - if( opcode&PPC_OPC_RC ) - PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_ADD_CARRY_UPDATE_CARRY, registerRD, registerRB, registerRA, 0, PPCREC_CR_MODE_LOGICAL); - else - PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_ADD_CARRY_UPDATE_CARRY, registerRD, registerRB, registerRA); - return true; -} - -bool PPCRecompilerImlGen_ADDZE(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rD, rA, rB; - PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); - PPC_ASSERT(rB == 0); - //uint32 a = hCPU->gpr[rA]; - //uint32 ca = hCPU->xer_ca; - //hCPU->gpr[rD] = a + ca; - - uint32 registerRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); - // move rA to rD - if( registerRA != registerRD ) - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, registerRD, registerRA); - } - if( opcode&PPC_OPC_RC ) - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ADD_CARRY, registerRD, registerRD, 0, PPCREC_CR_MODE_LOGICAL); - } - else - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ADD_CARRY, registerRD, registerRD); - } - return true; -} - -bool PPCRecompilerImlGen_ADDME(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rD, rA, rB; - PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); - PPC_ASSERT(rB == 0); - //uint32 a = hCPU->gpr[rA]; - //uint32 ca = hCPU->xer_ca; - //hCPU->gpr[rD] = a + ca + -1; - - uint32 registerRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); - // move rA to rD - if( registerRA != registerRD ) - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, registerRD, registerRA); - } - if( opcode&PPC_OPC_RC ) - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ADD_CARRY_ME, registerRD, registerRD, 0, PPCREC_CR_MODE_LOGICAL); - } - else - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ADD_CARRY_ME, registerRD, registerRD); - } + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); + IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); + IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_ADD, regD, regA, regB); + if (opcode & PPC_OPC_RC) + PPCImlGen_UpdateCR0(ppcImlGenContext, regD); return true; } @@ -1067,22 +699,16 @@ bool PPCRecompilerImlGen_ADDI(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod sint32 rD, rA; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); - //hCPU->gpr[rD] = (rA ? (int)hCPU->gpr[rA] : 0) + (int)imm; - if( rA != 0 ) + IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); + if (rA != 0) { - uint32 registerRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - // check if rD is already loaded, else use new temporary register - uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); - PPCRecompilerImlGen_generateNewInstruction_r_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, registerRD, registerRA, imm); + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_ADD, regD, regA, imm); } else { - // rA not used, instruction is value assignment - // rD = imm - uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, registerRD, imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); + ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, regD, imm); } - // never updates any cr return true; } @@ -1091,49 +717,88 @@ bool PPCRecompilerImlGen_ADDIS(ppcImlGenContext_t* ppcImlGenContext, uint32 opco int rD, rA; uint32 imm; PPC_OPC_TEMPL_D_Shift16(opcode, rD, rA, imm); - if( rA != 0 ) + IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); + if (rA != 0) { - uint32 registerRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - // check if rD is already loaded, else use new temporary register - uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); - PPCRecompilerImlGen_generateNewInstruction_r_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, registerRD, registerRA, (sint32)imm); + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_ADD, regD, regA, (sint32)imm); } else { - // rA not used, instruction turns into simple value assignment - // rD = imm - uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, registerRD, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); + ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, regD, (sint32)imm); } - // never updates any cr return true; } -bool PPCRecompilerImlGen_ADDIC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +bool PPCRecompilerImlGen_ADDC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + // r = a + b -> update carry + sint32 rD, rA, rB; + PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); + IMLReg regRA = _GetRegGPR(ppcImlGenContext, rA); + IMLReg regRB = _GetRegGPR(ppcImlGenContext, rB); + IMLReg regRD = _GetRegGPR(ppcImlGenContext, rD); + IMLReg regCa = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_XER_CA); + ppcImlGenContext->emitInst().make_r_r_r_carry(PPCREC_IML_OP_ADD, regRD, regRA, regRB, regCa); + if (opcode & PPC_OPC_RC) + PPCImlGen_UpdateCR0(ppcImlGenContext, regRD); + return true; +} + +bool PPCRecompilerImlGen_ADDIC_(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool updateCR0) { sint32 rD, rA; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); - // rD = rA + imm; - uint32 registerRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - // check if rD is already loaded, else use new temporary register - uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); - PPCRecompilerImlGen_generateNewInstruction_r_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD_UPDATE_CARRY, registerRD, registerRA, imm); - // never updates any cr + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); + IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); + IMLReg regCa = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_XER_CA); + ppcImlGenContext->emitInst().make_r_r_s32_carry(PPCREC_IML_OP_ADD, regD, regA, (sint32)imm, regCa); + if(updateCR0) + PPCImlGen_UpdateCR0(ppcImlGenContext, regD); return true; } -bool PPCRecompilerImlGen_ADDIC_(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +bool PPCRecompilerImlGen_ADDE(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { - // this opcode is identical to ADDIC but additionally it updates CR0 - sint32 rD, rA; - uint32 imm; - PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); - // rD = rA + imm; - uint32 registerRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - // check if rD is already loaded, else use new temporary register - uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); - PPCRecompilerImlGen_generateNewInstruction_r_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD_UPDATE_CARRY, registerRD, registerRA, imm, 0, PPCREC_CR_MODE_LOGICAL); + // r = a + b + carry -> update carry + sint32 rD, rA, rB; + PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); + IMLReg regRA = _GetRegGPR(ppcImlGenContext, rA); + IMLReg regRB = _GetRegGPR(ppcImlGenContext, rB); + IMLReg regRD = _GetRegGPR(ppcImlGenContext, rD); + IMLReg regCa = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_XER_CA); + ppcImlGenContext->emitInst().make_r_r_r_carry(PPCREC_IML_OP_ADD_WITH_CARRY, regRD, regRA, regRB, regCa); + if (opcode & PPC_OPC_RC) + PPCImlGen_UpdateCR0(ppcImlGenContext, regRD); + return true; +} + +bool PPCRecompilerImlGen_ADDZE(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + // r = a + carry -> update carry + sint32 rD, rA, rB; + PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); + IMLReg regRA = _GetRegGPR(ppcImlGenContext, rA); + IMLReg regRD = _GetRegGPR(ppcImlGenContext, rD); + IMLReg regCa = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_XER_CA); + ppcImlGenContext->emitInst().make_r_r_s32_carry(PPCREC_IML_OP_ADD_WITH_CARRY, regRD, regRA, 0, regCa); + if (opcode & PPC_OPC_RC) + PPCImlGen_UpdateCR0(ppcImlGenContext, regRD); + return true; +} + +bool PPCRecompilerImlGen_ADDME(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + // r = a + 0xFFFFFFFF + carry -> update carry + sint32 rD, rA, rB; + PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); + IMLReg regRA = _GetRegGPR(ppcImlGenContext, rA); + IMLReg regRD = _GetRegGPR(ppcImlGenContext, rD); + IMLReg regCa = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_XER_CA); + ppcImlGenContext->emitInst().make_r_r_s32_carry(PPCREC_IML_OP_ADD_WITH_CARRY, regRD, regRA, -1, regCa); + if (opcode & PPC_OPC_RC) + PPCImlGen_UpdateCR0(ppcImlGenContext, regRD); return true; } @@ -1141,74 +806,79 @@ bool PPCRecompilerImlGen_SUBF(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod { sint32 rD, rA, rB; PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); - // hCPU->gpr[rD] = ~hCPU->gpr[rA] + hCPU->gpr[rB] + 1; - // rD = rB - rA - uint32 registerRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 registerRB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); - uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); - if( opcode&PPC_OPC_RC ) - PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_SUB, registerRD, registerRB, registerRA, 0, PPCREC_CR_MODE_LOGICAL); - else - PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_SUB, registerRD, registerRB, registerRA); + // rD = ~rA + rB + 1 + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); + IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); + IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_SUB, regD, regB, regA); + if ((opcode & PPC_OPC_RC)) + PPCImlGen_UpdateCR0(ppcImlGenContext, regD); return true; } bool PPCRecompilerImlGen_SUBFE(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { + // d = ~a + b + ca; sint32 rD, rA, rB; PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); - // hCPU->gpr[rD] = ~hCPU->gpr[rA] + hCPU->gpr[rB] + ca; - uint32 registerRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 registerRB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); - uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); - if( opcode&PPC_OPC_RC ) - PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_SUB_CARRY_UPDATE_CARRY, registerRD, registerRB, registerRA, 0, PPCREC_CR_MODE_LOGICAL); - else - PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_SUB_CARRY_UPDATE_CARRY, registerRD, registerRB, registerRA); + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); + IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); + IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); + IMLReg regTmp = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 0); + IMLReg regCa = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_XER_CA); + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_NOT, regTmp, regA); + ppcImlGenContext->emitInst().make_r_r_r_carry(PPCREC_IML_OP_ADD_WITH_CARRY, regD, regTmp, regB, regCa); + if (opcode & PPC_OPC_RC) + PPCImlGen_UpdateCR0(ppcImlGenContext, regD); return true; } bool PPCRecompilerImlGen_SUBFZE(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { + // d = ~a + ca; sint32 rD, rA, rB; PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); - if( rB != 0 ) - debugBreakpoint(); - uint32 registerRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); - if( opcode&PPC_OPC_RC ) - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_SUB_CARRY_UPDATE_CARRY, registerRD, registerRA, 0, PPCREC_CR_MODE_LOGICAL); - else - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_SUB_CARRY_UPDATE_CARRY, registerRD, registerRA); + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); + IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); + IMLReg regTmp = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 0); + IMLReg regCa = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_XER_CA); + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_NOT, regTmp, regA); + ppcImlGenContext->emitInst().make_r_r_s32_carry(PPCREC_IML_OP_ADD_WITH_CARRY, regD, regTmp, 0, regCa); + if (opcode & PPC_OPC_RC) + PPCImlGen_UpdateCR0(ppcImlGenContext, regD); return true; } bool PPCRecompilerImlGen_SUBFC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { + // d = ~a + b + 1; sint32 rD, rA, rB; PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); - // hCPU->gpr[rD] = ~hCPU->gpr[rA] + hCPU->gpr[rB] + 1; - // rD = rB - rA - uint32 registerRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 registerRB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); - uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); - PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_SUBFC, registerRD, registerRA, registerRB); - if (opcode & PPC_OPC_RC) - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_AND, registerRD, registerRD, 0, PPCREC_CR_MODE_LOGICAL); + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); + IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); + IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); + IMLReg regTmp = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 0); + IMLReg regCa = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_XER_CA); + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_NOT, regTmp, regA); + ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, regCa, 1); // set input carry to simulate offset of 1 + ppcImlGenContext->emitInst().make_r_r_r_carry(PPCREC_IML_OP_ADD_WITH_CARRY, regD, regTmp, regB, regCa); + if ((opcode & PPC_OPC_RC)) + PPCImlGen_UpdateCR0(ppcImlGenContext, regD); return true; } bool PPCRecompilerImlGen_SUBFIC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { + // d = ~a + imm + 1 sint32 rD, rA; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); - //uint32 a = hCPU->gpr[rA]; - //hCPU->gpr[rD] = ~a + imm + 1; - // cr0 is never affected - uint32 registerRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); - PPCRecompilerImlGen_generateNewInstruction_r_r_s32(ppcImlGenContext, PPCREC_IML_OP_SUBFC, registerRD, registerRA, imm); + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); + IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); + IMLReg regCa = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_XER_CA); + IMLReg regTmp = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 0); + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_NOT, regTmp, regA); + ppcImlGenContext->emitInst().make_r_r_s32_carry(PPCREC_IML_OP_ADD, regD, regTmp, (sint32)imm + 1, regCa); return true; } @@ -1217,10 +887,9 @@ bool PPCRecompilerImlGen_MULLI(ppcImlGenContext_t* ppcImlGenContext, uint32 opco int rD, rA; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); - // mulli instruction does not modify any flags - uint32 registerResult = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rD, false); - uint32 registerOperand = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - PPCRecompilerImlGen_generateNewInstruction_r_r_s32(ppcImlGenContext, PPCREC_IML_OP_MULTIPLY_SIGNED, registerResult, registerOperand, (sint32)imm); + IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_MULTIPLY_SIGNED, regD, regA, (sint32)imm); return true; } @@ -1228,18 +897,16 @@ bool PPCRecompilerImlGen_MULLW(ppcImlGenContext_t* ppcImlGenContext, uint32 opco { sint32 rD, rA, rB; PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); - //hCPU->gpr[rD] = hCPU->gpr[rA] * hCPU->gpr[rB]; - uint32 registerResult = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rD, false); - uint32 registerOperand1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 registerOperand2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); + IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); if (opcode & PPC_OPC_OE) { return false; } - if( opcode&PPC_OPC_RC ) - PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_MULTIPLY_SIGNED, registerResult, registerOperand1, registerOperand2, 0, PPCREC_CR_MODE_LOGICAL); - else - PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_MULTIPLY_SIGNED, registerResult, registerOperand1, registerOperand2); + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_MULTIPLY_SIGNED, regD, regA, regB); + if (opcode & PPC_OPC_RC) + PPCImlGen_UpdateCR0(ppcImlGenContext, regD); return true; } @@ -1247,14 +914,12 @@ bool PPCRecompilerImlGen_MULHW(ppcImlGenContext_t* ppcImlGenContext, uint32 opco { sint32 rD, rA, rB; PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); - //hCPU->gpr[rD] = ((sint64)(sint32)hCPU->gpr[rA] * (sint64)(sint32)hCPU->gpr[rB])>>32; - uint32 registerResult = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rD, false); - uint32 registerOperand1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 registerOperand2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); - if( opcode&PPC_OPC_RC ) - PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_MULTIPLY_HIGH_SIGNED, registerResult, registerOperand1, registerOperand2, 0, PPCREC_CR_MODE_LOGICAL); - else - PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_MULTIPLY_HIGH_SIGNED, registerResult, registerOperand1, registerOperand2); + IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); + IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_MULTIPLY_HIGH_SIGNED, regD, regA, regB); + if (opcode & PPC_OPC_RC) + PPCImlGen_UpdateCR0(ppcImlGenContext, regD); return true; } @@ -1262,14 +927,12 @@ bool PPCRecompilerImlGen_MULHWU(ppcImlGenContext_t* ppcImlGenContext, uint32 opc { sint32 rD, rA, rB; PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); - //hCPU->gpr[rD] = (hCPU->gpr[rA] * hCPU->gpr[rB])>>32; - uint32 registerResult = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rD, false); - uint32 registerOperand1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 registerOperand2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); - if( opcode&PPC_OPC_RC ) - PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_MULTIPLY_HIGH_UNSIGNED, registerResult, registerOperand1, registerOperand2, 0, PPCREC_CR_MODE_LOGICAL); - else - PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_MULTIPLY_HIGH_UNSIGNED, registerResult, registerOperand1, registerOperand2); + IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); + IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_MULTIPLY_HIGH_UNSIGNED, regD, regA, regB); + if (opcode & PPC_OPC_RC) + PPCImlGen_UpdateCR0(ppcImlGenContext, regD); return true; } @@ -1277,18 +940,12 @@ bool PPCRecompilerImlGen_DIVW(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod { sint32 rD, rA, rB; PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); - // hCPU->gpr[rD] = (sint32)a / (sint32)b; - uint32 registerResult = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rD, false); - uint32 registerOperand1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 registerOperand2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + IMLReg regR = _GetRegGPR(ppcImlGenContext, rD); + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); + IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_DIVIDE_SIGNED, regR, regA, regB); if (opcode & PPC_OPC_RC) - { - PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_DIVIDE_SIGNED, registerResult, registerOperand1, registerOperand2, 0, PPCREC_CR_MODE_ARITHMETIC); - } - else - { - PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_DIVIDE_SIGNED, registerResult, registerOperand1, registerOperand2); - } + PPCImlGen_UpdateCR0(ppcImlGenContext, regR); return true; } @@ -1296,84 +953,66 @@ bool PPCRecompilerImlGen_DIVWU(ppcImlGenContext_t* ppcImlGenContext, uint32 opco { sint32 rD, rA, rB; PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); - // hCPU->gpr[rD] = (uint32)a / (uint32)b; - uint32 registerResult = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rD, false); - uint32 registerOperand1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 registerOperand2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); + IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_DIVIDE_UNSIGNED, regD, regA, regB); if (opcode & PPC_OPC_RC) - { - PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_DIVIDE_UNSIGNED, registerResult, registerOperand1, registerOperand2, 0, PPCREC_CR_MODE_ARITHMETIC); - } - else - { - PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_DIVIDE_UNSIGNED, registerResult, registerOperand1, registerOperand2); - } + PPCImlGen_UpdateCR0(ppcImlGenContext, regD); return true; } bool PPCRecompilerImlGen_RLWINM(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { - int rS, rA, SH, MB, ME; + sint32 rS, rA, SH, MB, ME; PPC_OPC_TEMPL_M(opcode, rS, rA, SH, MB, ME); uint32 mask = ppc_mask(MB, ME); - //uint32 v = ppc_word_rotl(hCPU->gpr[rS], SH); - //hCPU->gpr[rA] = v & mask; - uint32 registerRS = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS, false); - uint32 registerRA = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - // handle special forms of RLWINM - if( SH == 0 && SH == (ME-SH) && MB == 0 ) - { - // CLRRWI - // todo - } - else if( ME == (31-SH) && MB == 0 ) + IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); + if( ME == (31-SH) && MB == 0 ) { // SLWI - if(opcode&PPC_OPC_RC) - PPCRecompilerImlGen_generateNewInstruction_r_r_s32(ppcImlGenContext, PPCREC_IML_OP_LEFT_SHIFT, registerRA, registerRS, SH, 0, PPCREC_CR_MODE_LOGICAL); - else - PPCRecompilerImlGen_generateNewInstruction_r_r_s32(ppcImlGenContext, PPCREC_IML_OP_LEFT_SHIFT, registerRA, registerRS, SH); - return true; + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_LEFT_SHIFT, regA, regS, SH); } else if( SH == (32-MB) && ME == 31 ) { // SRWI - if(opcode&PPC_OPC_RC) - PPCRecompilerImlGen_generateNewInstruction_r_r_s32(ppcImlGenContext, PPCREC_IML_OP_RIGHT_SHIFT, registerRA, registerRS, MB, 0, PPCREC_CR_MODE_LOGICAL); - else - PPCRecompilerImlGen_generateNewInstruction_r_r_s32(ppcImlGenContext, PPCREC_IML_OP_RIGHT_SHIFT, registerRA, registerRS, MB); - return true; - } - // general handler - if( registerRA != registerRS ) - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, registerRA, registerRS); - if( SH != 0 ) - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_LEFT_ROTATE, registerRA, SH, 0, false, false, PPC_REC_INVALID_REGISTER, 0); - if(opcode&PPC_OPC_RC) - { - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_AND, registerRA, (sint32)mask, 0, false, false, 0, PPCREC_CR_MODE_LOGICAL); + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_RIGHT_SHIFT_U, regA, regS, MB); } else { - if( mask != 0xFFFFFFFF ) - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_AND, registerRA, (sint32)mask, 0, false, false, PPC_REC_INVALID_REGISTER, 0); + // general handler + if (rA != rS) + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, regA, regS); + if (SH != 0) + ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_LEFT_ROTATE, regA, SH); + if (mask != 0xFFFFFFFF) + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_AND, regA, regA, (sint32)mask); } + if (opcode & PPC_OPC_RC) + PPCImlGen_UpdateCR0(ppcImlGenContext, regA); return true; } bool PPCRecompilerImlGen_RLWIMI(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { - int rS, rA, SH, MB, ME; + sint32 rS, rA, SH, MB, ME; PPC_OPC_TEMPL_M(opcode, rS, rA, SH, MB, ME); - - uint32 registerRS = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS, false); - uint32 registerRA = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - // pack RLWIMI parameters into single integer - uint32 vImm = MB|(ME<<8)|(SH<<16); - PPCRecompilerImlGen_generateNewInstruction_r_r_s32(ppcImlGenContext, PPCREC_IML_OP_RLWIMI, registerRA, registerRS, (sint32)vImm, PPC_REC_INVALID_REGISTER, 0); + IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); + IMLReg regR = _GetRegGPR(ppcImlGenContext, rA); + IMLReg regTmp = _GetRegTemporary(ppcImlGenContext, 0); + uint32 mask = ppc_mask(MB, ME); + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, regTmp, regS); + if (SH) + ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_LEFT_ROTATE, regTmp, SH); + if (mask != 0) + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_AND, regR, regR, (sint32)~mask); + if (mask != 0xFFFFFFFF) + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_AND, regTmp, regTmp, (sint32)mask); + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_OR, regR, regR, regTmp); if (opcode & PPC_OPC_RC) - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_AND, registerRA, registerRA, 0, PPCREC_CR_MODE_LOGICAL); + PPCImlGen_UpdateCR0(ppcImlGenContext, regR); return true; } @@ -1381,61 +1020,59 @@ bool PPCRecompilerImlGen_RLWNM(ppcImlGenContext_t* ppcImlGenContext, uint32 opco { sint32 rS, rA, rB, MB, ME; PPC_OPC_TEMPL_M(opcode, rS, rA, rB, MB, ME); - // uint32 v = ppc_word_rotl(hCPU->gpr[rS], hCPU->gpr[rB]); uint32 mask = ppc_mask(MB, ME); - // uint32 v = ppc_word_rotl(hCPU->gpr[rS], hCPU->gpr[rB]); - // hCPU->gpr[rA] = v & mask; - uint32 registerRS = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS, false); - uint32 registerRB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); - uint32 registerRA = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - - PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_LEFT_ROTATE, registerRA, registerRS, registerRB); + IMLReg regS = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); + IMLReg regB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); + IMLReg regA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_LEFT_ROTATE, regA, regS, regB); + if( mask != 0xFFFFFFFF ) + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_AND, regA, regA, (sint32)mask); if (opcode & PPC_OPC_RC) - { - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_AND, registerRA, (sint32)mask, 32, false, false, 0, PPCREC_CR_MODE_LOGICAL); - } - else - { - if( mask != 0xFFFFFFFF ) - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_AND, registerRA, (sint32)mask, 32, false, false, PPC_REC_INVALID_REGISTER, 0); - } + PPCImlGen_UpdateCR0(ppcImlGenContext, regA); return true; } bool PPCRecompilerImlGen_SRAW(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { + // unlike SRAWI, for SRAW the shift range is 0-63 (masked to 6 bits) + // but only shifts up to register bitwidth minus one are well defined in IML so this requires special handling for shifts >= 32 sint32 rS, rA, rB; PPC_OPC_TEMPL_X(opcode, rS, rA, rB); - //uint32 SH = hCPU->gpr[rB] & 0x3f; - //hCPU->gpr[rA] = hCPU->gpr[rS]; - //hCPU->xer_ca = 0; - //if (hCPU->gpr[rA] & 0x80000000) { - // uint32 ca = 0; - // for (uint32 i=0; i < SH; i++) { - // if (hCPU->gpr[rA] & 1) ca = 1; - // hCPU->gpr[rA] >>= 1; - // hCPU->gpr[rA] |= 0x80000000; - // } - // if (ca) hCPU->xer_ca = 1; - //} else { - // if (SH > 31) { - // hCPU->gpr[rA] = 0; - // } else { - // hCPU->gpr[rA] >>= SH; - // } - //} - //if (Opcode & PPC_OPC_RC) { - // // update cr0 flags - // ppc_update_cr0(hCPU, hCPU->gpr[rA]); - //} + IMLReg regS = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); + IMLReg regB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); + IMLReg regA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + IMLReg regCarry = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_XER_CA); - uint32 registerRS = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS, false); - uint32 registerRB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); - uint32 registerRA = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - if( (opcode&PPC_OPC_RC) != 0 ) - PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_SRAW, registerRA, registerRS, registerRB, 0, PPCREC_CR_MODE_LOGICAL); - else - PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_SRAW, registerRA, registerRS, registerRB); + IMLReg regTmpShiftAmount = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 0); + IMLReg regTmpCondBool = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 1); + IMLReg regTmp1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 2); + IMLReg regTmp2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 3); + + // load masked shift factor into temporary register + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_AND, regTmpShiftAmount, regB, 0x3F); + ppcImlGenContext->emitInst().make_compare_s32(regTmpShiftAmount, 32, regTmpCondBool, IMLCondition::UNSIGNED_GT); + ppcImlGenContext->emitInst().make_conditional_jump(regTmpCondBool, true); + + PPCIMLGen_CreateSegmentBranchedPath(*ppcImlGenContext, *ppcImlGenContext->currentBasicBlock, + [&](ppcImlGenContext_t& genCtx) + { + /* branch taken */ + genCtx.emitInst().make_r_r_r(PPCREC_IML_OP_RIGHT_SHIFT_S, regA, regS, regTmpShiftAmount); + genCtx.emitInst().make_compare_s32(regA, 0, regCarry, IMLCondition::NEQ); // if the sign bit is still set it also means it was shifted out and we can set carry + }, + [&](ppcImlGenContext_t& genCtx) + { + /* branch not taken, shift size below 32 */ + genCtx.emitInst().make_r_r_s32(PPCREC_IML_OP_RIGHT_SHIFT_S, regTmp1, regS, 31); // signMask = input >> 31 (arithmetic shift) + genCtx.emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, regTmp2, 1); // shiftMask = ((1<<SH)-1) + genCtx.emitInst().make_r_r_r(PPCREC_IML_OP_LEFT_SHIFT, regTmp2, regTmp2, regTmpShiftAmount); + genCtx.emitInst().make_r_r_s32(PPCREC_IML_OP_SUB, regTmp2, regTmp2, 1); + genCtx.emitInst().make_r_r_r(PPCREC_IML_OP_AND, regTmp1, regTmp1, regTmp2); // signMask & shiftMask & input + genCtx.emitInst().make_r_r_r(PPCREC_IML_OP_AND, regTmp1, regTmp1, regS); + genCtx.emitInst().make_compare_s32(regTmp1, 0, regCarry, IMLCondition::NEQ); + genCtx.emitInst().make_r_r_r(PPCREC_IML_OP_RIGHT_SHIFT_S, regA, regS, regTmpShiftAmount); + } + ); return true; } @@ -1445,12 +1082,21 @@ bool PPCRecompilerImlGen_SRAWI(ppcImlGenContext_t* ppcImlGenContext, uint32 opco uint32 SH; PPC_OPC_TEMPL_X(opcode, rS, rA, SH); cemu_assert_debug(SH < 32); - uint32 registerRS = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS, false); - uint32 registerRA = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - if( opcode&PPC_OPC_RC ) - PPCRecompilerImlGen_generateNewInstruction_r_r_s32(ppcImlGenContext, PPCREC_IML_OP_SRAW, registerRA, registerRS, (sint32)SH, 0, PPCREC_CR_MODE_LOGICAL); - else - PPCRecompilerImlGen_generateNewInstruction_r_r_s32(ppcImlGenContext, PPCREC_IML_OP_SRAW, registerRA, registerRS, (sint32)SH); + if (SH == 0) + return false; // becomes a no-op (unless RC bit is set) but also sets ca bit to 0? + IMLReg regS = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rS); + IMLReg regA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rA); + IMLReg regCarry = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_XER_CA); + IMLReg regTmp = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 0); + // calculate CA first + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_RIGHT_SHIFT_S, regTmp, regS, 31); // signMask = input >> 31 (arithmetic shift) + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_AND, regTmp, regTmp, regS); // testValue = input & signMask & ((1<<SH)-1) + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_AND, regTmp, regTmp, ((1 << SH) - 1)); + ppcImlGenContext->emitInst().make_compare_s32(regTmp, 0, regCarry, IMLCondition::NEQ); // ca = (testValue != 0) + // do the actual shift + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_RIGHT_SHIFT_S, regA, regS, (sint32)SH); + if (opcode & PPC_OPC_RC) + PPCImlGen_UpdateCR0(ppcImlGenContext, regA); return true; } @@ -1459,17 +1105,12 @@ bool PPCRecompilerImlGen_SLW(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode int rS, rA, rB; PPC_OPC_TEMPL_X(opcode, rS, rA, rB); - uint32 registerRS = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS, false); - uint32 registerRB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); - uint32 registerRA = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - if (opcode & PPC_OPC_RC) - { - PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_SLW, registerRA, registerRS, registerRB, 0, PPCREC_CR_MODE_LOGICAL); - } - else - { - PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_SLW, registerRA, registerRS, registerRB, PPC_REC_INVALID_REGISTER, 0); - } + IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); + IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_SLW, regA, regS, regB); + if ((opcode & PPC_OPC_RC)) + PPCImlGen_UpdateCR0(ppcImlGenContext, regA); return true; } @@ -1477,37 +1118,24 @@ bool PPCRecompilerImlGen_SRW(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode { int rS, rA, rB; PPC_OPC_TEMPL_X(opcode, rS, rA, rB); - - uint32 registerRS = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS, false); - uint32 registerRB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); - uint32 registerRA = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); + IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_SRW, regA, regS, regB); if (opcode & PPC_OPC_RC) - { - PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_SRW, registerRA, registerRS, registerRB, 0, PPCREC_CR_MODE_LOGICAL); - } - else - { - PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_SRW, registerRA, registerRS, registerRB, PPC_REC_INVALID_REGISTER, 0); - } + PPCImlGen_UpdateCR0(ppcImlGenContext, regA); return true; } - bool PPCRecompilerImlGen_EXTSH(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { int rS, rA, rB; PPC_OPC_TEMPL_X(opcode, rS, rA, rB); - PPC_ASSERT(rB==0); - uint32 registerRS = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS, false); - uint32 registerRA = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - if ( opcode&PPC_OPC_RC ) - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN_S16_TO_S32, registerRA, registerRS, 0, PPCREC_CR_MODE_ARITHMETIC); - } - else - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN_S16_TO_S32, registerRA, registerRS); - } + IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN_S16_TO_S32, regA, regS); + if (opcode & PPC_OPC_RC) + PPCImlGen_UpdateCR0(ppcImlGenContext, regA); return true; } @@ -1515,16 +1143,11 @@ bool PPCRecompilerImlGen_EXTSB(ppcImlGenContext_t* ppcImlGenContext, uint32 opco { sint32 rS, rA, rB; PPC_OPC_TEMPL_X(opcode, rS, rA, rB); - uint32 registerRS = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS, false); - uint32 registerRA = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - if ( opcode&PPC_OPC_RC ) - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN_S8_TO_S32, registerRA, registerRS, 0, PPCREC_CR_MODE_ARITHMETIC); - } - else - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN_S8_TO_S32, registerRA, registerRS); - } + IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN_S8_TO_S32, regA, regS); + if ((opcode & PPC_OPC_RC)) + PPCImlGen_UpdateCR0(ppcImlGenContext, regA); return true; } @@ -1532,30 +1155,11 @@ bool PPCRecompilerImlGen_CNTLZW(ppcImlGenContext_t* ppcImlGenContext, uint32 opc { sint32 rS, rA, rB; PPC_OPC_TEMPL_X(opcode, rS, rA, rB); - PPC_ASSERT(rB==0); - if( opcode&PPC_OPC_RC ) - { - return false; - } - uint32 registerRS = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS, false); - uint32 registerRA = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_CNTLZW, registerRA, registerRS); - - //uint32 n=0; - //uint32 x=0x80000000; - //uint32 v=hCPU->gpr[rS]; - //while (!(v & x)) { - // n++; - // if (n==32) break; - // x>>=1; - //} - //hCPU->gpr[rA] = n; - //if (Opcode & PPC_OPC_RC) { - // // update cr0 flags - // ppc_update_cr0(hCPU, hCPU->gpr[rA]); - //} - - + IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_CNTLZW, regA, regS); + if ((opcode & PPC_OPC_RC)) + PPCImlGen_UpdateCR0(ppcImlGenContext, regA); return true; } @@ -1563,438 +1167,124 @@ bool PPCRecompilerImlGen_NEG(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode { sint32 rD, rA, rB; PPC_OPC_TEMPL_XO(opcode, rD, rA, rB); - PPC_ASSERT(rB == 0); - //hCPU->gpr[rD] = -((signed int)hCPU->gpr[rA]); - //if (Opcode & PPC_OPC_RC) { - // // update cr0 flags - // ppc_update_cr0(hCPU, hCPU->gpr[rD]); - //} - uint32 registerRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 registerRD = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rD); - if( opcode&PPC_OPC_RC ) - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_NEG, registerRD, registerRA, 0, PPCREC_CR_MODE_ARITHMETIC); - } - else - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_NEG, registerRD, registerRA); - } + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); + IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_NEG, regD, regA); + if (opcode & PPC_OPC_RC) + PPCImlGen_UpdateCR0(ppcImlGenContext, regD); return true; } -void PPCRecompilerImlGen_LWZ(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +bool PPCRecompilerImlGen_LOAD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, uint32 bitWidth, bool signExtend, bool isBigEndian, bool updateAddrReg) { int rA, rD; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); - if( rA == 0 ) - { - // special form where gpr is ignored and only imm is used - PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); - return; - } - // load memory gpr into register - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - // check if destination register is already loaded - uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); - if( destinationRegister == PPC_REC_INVALID_REGISTER ) - destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new register - // load half - PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegister, imm, 32, false, true); -} - -void PPCRecompilerImlGen_LWZU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - int rA, rD; - uint32 imm; - PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); - if( rA == 0 ) - { - // special form where gpr is ignored and only imm is used - PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); - return; - } - // load memory gpr into register - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - // add imm to memory register - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, gprRegister, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); - // check if destination register is already loaded - uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); - if( destinationRegister == PPC_REC_INVALID_REGISTER ) - destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new register - // load half - PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegister, 0, 32, false, true); -} - -void PPCRecompilerImlGen_LHA(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - int rA, rD; - uint32 imm; - PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); - if( rA == 0 ) - { - // special form where gpr is ignored and only imm is used - PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); - return; - } - // load memory gpr into register - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - // check if destination register is already loaded - uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); - if( destinationRegister == PPC_REC_INVALID_REGISTER ) - destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new temporary register - // load half - PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegister, imm, 16, true, true); -} - -void PPCRecompilerImlGen_LHAU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rA, rD; - uint32 imm; - PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); - if( rA == 0 ) - { - // special form where gpr is ignored and only imm is used - PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); - return; - } - // load memory gpr into register - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - // add imm to memory register - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, gprRegister, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); - // check if destination register is already loaded - uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); - if( destinationRegister == PPC_REC_INVALID_REGISTER ) - destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new temporary register - // load half - PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegister, 0, 16, true, true); -} - -void PPCRecompilerImlGen_LHZ(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rA, rD; - uint32 imm; - PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); - if( rA == 0 ) - { - // special form where gpr is ignored and only imm is used - // note: Darksiders 2 has this instruction form but it is never executed. - PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); - return; - } - // load memory gpr into register - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - // check if destination register is already loaded - uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); - if( destinationRegister == PPC_REC_INVALID_REGISTER ) - destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new temporary register - // load half - PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegister, imm, 16, false, true); -} - -void PPCRecompilerImlGen_LHZU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rA, rD; - uint32 imm; - PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); - if( rA == 0 ) - { - // special form where gpr is ignored and only imm is used - PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); - return; - } - // load memory gpr into register - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - // add imm to memory register - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, gprRegister, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); - // check if destination register is already loaded - uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); - if( destinationRegister == PPC_REC_INVALID_REGISTER ) - destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new temporary register - // load half - PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegister, 0, 16, false, true); -} - -void PPCRecompilerImlGen_LBZ(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - int rA, rD; - uint32 imm; - PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); - if( rA == 0 ) - { - // special form where gpr is ignored and only imm is used - PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); - return; - } - // load memory gpr into register - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - // check if destination register is already loaded - uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); - if( destinationRegister == PPC_REC_INVALID_REGISTER ) - destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new register - // load byte - PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegister, imm, 8, false, true); -} - -void PPCRecompilerImlGen_LBZU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - int rA, rD; - uint32 imm; - PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); - if( rA == 0 ) - { - // special form where gpr is ignored and only imm is used - PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); - return; - } - // load memory gpr into register - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - // add imm to memory register - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, gprRegister, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); - // check if destination register is already loaded - uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); - if( destinationRegister == PPC_REC_INVALID_REGISTER ) - destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new register - // load byte - PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegister, 0, 8, false, true); -} - -bool PPCRecompilerImlGen_LWZX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rA, rD, rB; - PPC_OPC_TEMPL_X(opcode, rD, rA, rB); - if( rA == 0 ) - { - return false; - } - // hCPU->gpr[rD] = memory_readU8((rA?hCPU->gpr[rA]:0)+hCPU->gpr[rB]); - // load memory rA and rB into register - uint32 gprRegisterA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); - // check if destination register is already loaded - uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); - if( destinationRegister == PPC_REC_INVALID_REGISTER ) - destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new register - // load word - PPCRecompilerImlGen_generateNewInstruction_r_memory_indexed(ppcImlGenContext, destinationRegister, gprRegisterA, gprRegisterB, 32, false, true); - return true; -} - -bool PPCRecompilerImlGen_LWZUX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rA, rD, rB; - PPC_OPC_TEMPL_X(opcode, rD, rA, rB); - if( rA == 0 ) - { - return false; - } - // load memory rA and rB into register - uint32 gprRegisterA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); - // check if destination register is already loaded - uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); - if( destinationRegister == PPC_REC_INVALID_REGISTER ) - destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new register - // add rB to rA - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ADD, gprRegisterA, gprRegisterB); - // load word - PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegisterA, 0, 32, false, true); - return true; -} - -bool PPCRecompilerImlGen_LWBRX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rA, rD, rB; - PPC_OPC_TEMPL_X(opcode, rD, rA, rB); - // load memory rA and rB into register - uint32 gprRegisterA = 0; - if( rA ) - gprRegisterA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rA, false); - uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rB, false); - // check if destination register is already loaded - uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0 + rD); - if (destinationRegister == PPC_REC_INVALID_REGISTER) - destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0 + rD); // else just create new register - // load word - if( rA ) - PPCRecompilerImlGen_generateNewInstruction_r_memory_indexed(ppcImlGenContext, destinationRegister, gprRegisterA, gprRegisterB, 32, false, false); - else - PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegisterB, 0, 32, false, false); - return true; -} - -bool PPCRecompilerImlGen_LHAX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rA, rD, rB; - PPC_OPC_TEMPL_X(opcode, rD, rA, rB); - if( rA == 0 ) - { - // special form where gpr is ignored and only imm is used - PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); - return true; - } - // load memory rA and rB into register - uint32 gprRegisterA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); - // check if destination register is already loaded - uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); - if( destinationRegister == PPC_REC_INVALID_REGISTER ) - destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new register - // load half word - PPCRecompilerImlGen_generateNewInstruction_r_memory_indexed(ppcImlGenContext, destinationRegister, gprRegisterA, gprRegisterB, 16, true, true); - return true; -} - -bool PPCRecompilerImlGen_LHAUX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rA, rD, rB; - PPC_OPC_TEMPL_X(opcode, rD, rA, rB); - if( rA == 0 ) - { - // special form where gpr is ignored and only imm is used - PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); - return true; - } - // load memory rA and rB into register - uint32 gprRegisterA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); - // check if destination register is already loaded - uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); - if( destinationRegister == PPC_REC_INVALID_REGISTER ) - destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new register - // add rB to rA - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ADD, gprRegisterA, gprRegisterB); - // load half word - PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegisterA, 0, 16, true, true); - return true; -} - -bool PPCRecompilerImlGen_LHZX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rA, rD, rB; - PPC_OPC_TEMPL_X(opcode, rD, rA, rB); - if( rA == 0 ) - { - // special form where gpr is ignored and only imm is used - PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); - return true; - } - // load memory rA and rB into register - uint32 gprRegisterA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); - // check if destination register is already loaded - uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); - if( destinationRegister == PPC_REC_INVALID_REGISTER ) - destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new register - // load half word - PPCRecompilerImlGen_generateNewInstruction_r_memory_indexed(ppcImlGenContext, destinationRegister, gprRegisterA, gprRegisterB, 16, false, true); - return true; -} - -bool PPCRecompilerImlGen_LHZUX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rA, rD, rB; - PPC_OPC_TEMPL_X(opcode, rD, rA, rB); - if( rA == 0 ) - { - // special form where gpr is ignored and only imm is used - PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); - return true; - } - // load memory rA and rB into register - uint32 gprRegisterA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); - // check if destination register is already loaded - uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); - if( destinationRegister == PPC_REC_INVALID_REGISTER ) - destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new register - // add rB to rA - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ADD, gprRegisterA, gprRegisterB); - // load hald word - PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegisterA, 0, 16, false, true); - return true; -} - -void PPCRecompilerImlGen_LHBRX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rA, rD, rB; - PPC_OPC_TEMPL_X(opcode, rD, rA, rB); - // load memory rA and rB into register - uint32 gprRegisterA = rA != 0 ? PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rA, false) : 0; - uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rB, false); - // check if destination register is already loaded - uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0 + rD); - if (destinationRegister == PPC_REC_INVALID_REGISTER) - destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0 + rD); // else just create new register - // load half word (little-endian) - if (rA == 0) - PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegisterB, 0, 16, false, false); - else - PPCRecompilerImlGen_generateNewInstruction_r_memory_indexed(ppcImlGenContext, destinationRegister, gprRegisterA, gprRegisterB, 16, false, false); -} - -bool PPCRecompilerImlGen_LBZX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rA, rD, rB; - PPC_OPC_TEMPL_X(opcode, rD, rA, rB); - if( rA == 0 ) - { - // special case where rA is ignored and only rB is used - return false; - } - // hCPU->gpr[rD] = memory_readU8((rA?hCPU->gpr[rA]:0)+hCPU->gpr[rB]); - // load memory rA and rB into register - uint32 gprRegisterA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); - // check if destination register is already loaded - uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); - if( destinationRegister == PPC_REC_INVALID_REGISTER ) - destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new register - // load byte - PPCRecompilerImlGen_generateNewInstruction_r_memory_indexed(ppcImlGenContext, destinationRegister, gprRegisterA, gprRegisterB, 8, false, true); - return true; -} - -bool PPCRecompilerImlGen_LBZUX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rA, rD, rB; - PPC_OPC_TEMPL_X(opcode, rD, rA, rB); + IMLReg regMemAddr; if (rA == 0) { - // special form where gpr is ignored and only imm is used - PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); - return true; + if (updateAddrReg) + return false; // invalid instruction form + regMemAddr = _GetRegTemporary(ppcImlGenContext, 0); + ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, regMemAddr, 0); } - // load memory rA and rB into register - uint32 gprRegisterA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rA, false); - uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rB, false); - // check if destination register is already loaded - uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0 + rD); - if (destinationRegister == PPC_REC_INVALID_REGISTER) - destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0 + rD); // else just create new register - // add rB to rA - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ADD, gprRegisterA, gprRegisterB); - // load byte - PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegisterA, 0, 8, false, true); + else + { + if (updateAddrReg && rA == rD) + return false; // invalid instruction form + regMemAddr = _GetRegGPR(ppcImlGenContext, rA); + } + if (updateAddrReg) + { + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_ADD, regMemAddr, regMemAddr, (sint32)imm); + imm = 0; + } + IMLReg regDst = _GetRegGPR(ppcImlGenContext, rD); + ppcImlGenContext->emitInst().make_r_memory(regDst, regMemAddr, (sint32)imm, bitWidth, signExtend, isBigEndian); return true; } -bool PPCRecompilerImlGen_LWARX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +void PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, uint32 bitWidth, bool signExtend, bool isBigEndian, bool updateAddrReg) { + // if rA == rD, then the EA wont be stored to rA. We could set updateAddrReg to false in such cases but the end result is the same since the loaded value would overwrite rA sint32 rA, rD, rB; PPC_OPC_TEMPL_X(opcode, rD, rA, rB); - // load memory rA and rB into register - uint32 gprRegisterA = rA != 0?PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false):0; - uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); - // check if destination register is already loaded - uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); - if( destinationRegister == PPC_REC_INVALID_REGISTER ) - destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new register - // load word - if( rA != 0 ) - PPCRecompilerImlGen_generateNewInstruction_r_memory_indexed(ppcImlGenContext, destinationRegister, gprRegisterA, gprRegisterB, PPC_REC_LOAD_LWARX_MARKER, false, true); + updateAddrReg = updateAddrReg && (rA != 0); + IMLReg regA = rA != 0 ? _GetRegGPR(ppcImlGenContext, rA) : IMLREG_INVALID; + IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); + IMLReg regDst = _GetRegGPR(ppcImlGenContext, rD); + if (updateAddrReg) + { + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_ADD, regA, regA, regB); + // use single register addressing + regB = regA; + regA = IMLREG_INVALID; + } + if(regA.IsValid()) + PPCRecompilerImlGen_generateNewInstruction_r_memory_indexed(ppcImlGenContext, regDst, regA, regB, bitWidth, signExtend, isBigEndian); else - PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegisterB, 0, PPC_REC_LOAD_LWARX_MARKER, false, true); + ppcImlGenContext->emitInst().make_r_memory(regDst, regB, 0, bitWidth, signExtend, isBigEndian); +} + +bool PPCRecompilerImlGen_STORE(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, uint32 bitWidth, bool isBigEndian, bool updateAddrReg) +{ + int rA, rD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); + IMLReg regA; + if (rA != 0) + { + regA = _GetRegGPR(ppcImlGenContext, rA); + } + else + { + if (updateAddrReg) + return false; // invalid instruction form + regA = _GetRegTemporary(ppcImlGenContext, 0); + ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, regA, 0); + } + IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); + if (updateAddrReg) + { + if (rD == rA) + { + // make sure to keep source data intact + regD = _GetRegTemporary(ppcImlGenContext, 0); + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, regD, regA); + } + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_ADD, regA, regA, (sint32)imm); + imm = 0; + } + ppcImlGenContext->emitInst().make_memory_r(regD, regA, (sint32)imm, bitWidth, isBigEndian); + return true; +} + +bool PPCRecompilerImlGen_STORE_INDEXED(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, uint32 bitWidth, bool isBigEndian, bool updateAddrReg) +{ + sint32 rA, rS, rB; + PPC_OPC_TEMPL_X(opcode, rS, rA, rB); + IMLReg regA = rA != 0 ? _GetRegGPR(ppcImlGenContext, rA) : IMLREG_INVALID; + IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); + IMLReg regSrc = _GetRegGPR(ppcImlGenContext, rS); + if (updateAddrReg) + { + if(rA == 0) + return false; // invalid instruction form + if (regSrc == regA) + { + // make sure to keep source data intact + regSrc = _GetRegTemporary(ppcImlGenContext, 0); + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, regSrc, regA); + } + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_ADD, regA, regA, regB); + // use single register addressing + regB = regA; + regA = IMLREG_INVALID; + } + if (regA.IsInvalid()) + ppcImlGenContext->emitInst().make_memory_r(regSrc, regB, 0, bitWidth, isBigEndian); + else + PPCRecompilerImlGen_generateNewInstruction_memory_r_indexed(ppcImlGenContext, regSrc, regA, regB, bitWidth, false, isBigEndian); return true; } @@ -2003,257 +1293,33 @@ void PPCRecompilerImlGen_LMW(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode sint32 rD, rA; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); - //uint32 ea = (rA ? hCPU->gpr[rA] : 0) + imm; + cemu_assert_debug(rA != 0); sint32 index = 0; - while( rD <= 31 ) + while (rD <= 31) { - // load memory gpr into register - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - // check if destination register is already loaded - uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); - if( destinationRegister == PPC_REC_INVALID_REGISTER ) - destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new register + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); + IMLReg regD = _GetRegGPR(ppcImlGenContext, rD); // load word - PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegister, imm+index*4, 32, false, true); + ppcImlGenContext->emitInst().make_r_memory(regD, regA, (sint32)imm + index * 4, 32, false, true); // next rD++; index++; } } -void PPCRecompilerImlGen_STW(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - int rA, rD; - uint32 imm; - PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); - if( rA == 0 ) - { - // special form where gpr is ignored and only imm is used - // note: Darksiders 2 has this instruction form but it is never executed. - //PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); - return; - } - // load memory gpr into register - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - // load source register - uint32 sourceRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rD, false); // can be the same as gprRegister - // store word - PPCRecompilerImlGen_generateNewInstruction_memory_r(ppcImlGenContext, sourceRegister, gprRegister, imm, 32, true); -} - -void PPCRecompilerImlGen_STWU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - int rA, rD; - uint32 imm; - PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); - if( rA == 0 ) - { - // special form where gpr is ignored and only imm is used - PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); - return; - } - // store&update instructions where rD==rA store the register contents without added imm, therefore we need to handle it differently - // get memory gpr register - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - // get source register - uint32 sourceRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rD, false); // can be the same as gprRegister - // add imm to memory register early if possible - if( rD != rA ) - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, gprRegister, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); - // store word - PPCRecompilerImlGen_generateNewInstruction_memory_r(ppcImlGenContext, sourceRegister, gprRegister, (rD==rA)?imm:0, 32, true); - // add imm to memory register late if we couldn't do it early - if( rD == rA ) - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, gprRegister, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); -} - -void PPCRecompilerImlGen_STH(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - int rA, rD; - uint32 imm; - PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); - if( rA == 0 ) - { - // special form where gpr is ignored and only imm is used - PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); - return; - } - // load memory gpr into register - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - // load source register - uint32 sourceRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rD, false); // can be the same as gprRegister - // load half - PPCRecompilerImlGen_generateNewInstruction_memory_r(ppcImlGenContext, sourceRegister, gprRegister, imm, 16, true); -} - -void PPCRecompilerImlGen_STHU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - int rA, rD; - uint32 imm; - PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); - if( rA == 0 ) - { - // special form where gpr is ignored and only imm is used - PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); - return; - } - // get memory gpr register - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - // get source register - uint32 sourceRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rD, false); // can be the same as gprRegister - // add imm to memory register early if possible - if( rD != rA ) - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, gprRegister, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); - // store word - PPCRecompilerImlGen_generateNewInstruction_memory_r(ppcImlGenContext, sourceRegister, gprRegister, (rD==rA)?imm:0, 16, true); - // add imm to memory register late if we couldn't do it early - if( rD == rA ) - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, gprRegister, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); -} - -void PPCRecompilerImlGen_STB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - int rA, rS; - uint32 imm; - PPC_OPC_TEMPL_D_SImm(opcode, rS, rA, imm); - if( rA == 0 ) - { - // special form where gpr is ignored and only imm is used - PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); - return; - } - // load memory gpr into register - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - // load source register - uint32 sourceRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS, false); // can be the same as gprRegister - // store byte - PPCRecompilerImlGen_generateNewInstruction_memory_r(ppcImlGenContext, sourceRegister, gprRegister, imm, 8, true); -} - -void PPCRecompilerImlGen_STBU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - int rA, rD; - uint32 imm; - PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); - if( rA == 0 ) - { - // special form where gpr is ignored and only imm is used - PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_DEBUGBREAK, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->ppcAddressOfCurrentInstruction, ppcImlGenContext->cyclesSinceLastBranch); - return; - } - // get memory gpr register - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - // get source register - uint32 sourceRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rD, false); // can be the same as gprRegister - // add imm to memory register early if possible - if( rD != rA ) - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, gprRegister, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); - // store byte - PPCRecompilerImlGen_generateNewInstruction_memory_r(ppcImlGenContext, sourceRegister, gprRegister, (rD==rA)?imm:0, 8, true); - // add imm to memory register late if we couldn't do it early - if( rD == rA ) - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, gprRegister, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); -} - -// generic indexed store (STWX, STHX, STBX, STWUX. If bitReversed == true -> STHBRX) -bool PPCRecompilerImlGen_STORE_INDEXED(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, uint32 storeBitWidth, bool byteReversed = false) -{ - sint32 rA, rS, rB; - PPC_OPC_TEMPL_X(opcode, rS, rA, rB); - // prepare registers - uint32 gprRegisterA; - if(rA != 0) - gprRegisterA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rA, false); - uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); - uint32 destinationRegister = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); - // store word - if (rA == 0) - { - PPCRecompilerImlGen_generateNewInstruction_memory_r(ppcImlGenContext, destinationRegister, gprRegisterB, 0, storeBitWidth, !byteReversed); - } - else - PPCRecompilerImlGen_generateNewInstruction_memory_r_indexed(ppcImlGenContext, destinationRegister, gprRegisterA, gprRegisterB, storeBitWidth, false, !byteReversed); - return true; -} - -bool PPCRecompilerImlGen_STORE_INDEXED_UPDATE(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, uint32 storeBitWidth) -{ - sint32 rA, rS, rB; - PPC_OPC_TEMPL_X(opcode, rS, rA, rB); - if( rA == 0 ) - { - // not supported - return false; - } - if( rS == rA || rS == rB ) - { - // prepare registers - uint32 gprRegisterA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); - uint32 destinationRegister = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); - // store word - PPCRecompilerImlGen_generateNewInstruction_memory_r_indexed(ppcImlGenContext, destinationRegister, gprRegisterA, gprRegisterB, storeBitWidth, false, true); - // update EA after store - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ADD, gprRegisterA, gprRegisterB); - return true; - } - // prepare registers - uint32 gprRegisterA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); - uint32 sourceRegister = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); - // update EA - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ADD, gprRegisterA, gprRegisterB); - // store word - PPCRecompilerImlGen_generateNewInstruction_memory_r(ppcImlGenContext, sourceRegister, gprRegisterA, 0, storeBitWidth, true); - return true; -} - -bool PPCRecompilerImlGen_STWCX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rA, rS, rB; - PPC_OPC_TEMPL_X(opcode, rS, rA, rB); - // prepare registers - uint32 gprRegisterA = rA!=0?PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false):0; - uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); - uint32 destinationRegister = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); - // store word - if( rA != 0 ) - PPCRecompilerImlGen_generateNewInstruction_memory_r_indexed(ppcImlGenContext, destinationRegister, gprRegisterA, gprRegisterB, PPC_REC_STORE_STWCX_MARKER, false, true); - else - PPCRecompilerImlGen_generateNewInstruction_memory_r(ppcImlGenContext, destinationRegister, gprRegisterB, 0, PPC_REC_STORE_STWCX_MARKER, true); - return true; -} - -bool PPCRecompilerImlGen_STWBRX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rA, rS, rB; - PPC_OPC_TEMPL_X(opcode, rS, rA, rB); - // prepare registers - uint32 gprRegisterA = rA!=0?PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false):0; - uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); - uint32 destinationRegister = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); - // store word - if( rA != 0 ) - PPCRecompilerImlGen_generateNewInstruction_memory_r_indexed(ppcImlGenContext, destinationRegister, gprRegisterA, gprRegisterB, 32, false, false); - else - PPCRecompilerImlGen_generateNewInstruction_memory_r(ppcImlGenContext, destinationRegister, gprRegisterB, 0, 32, false); - return true; -} - void PPCRecompilerImlGen_STMW(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 rS, rA; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, rS, rA, imm); + cemu_assert_debug(rA != 0); sint32 index = 0; while( rS <= 31 ) { - // load memory gpr into register - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - // load source register - uint32 sourceRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS, false); // can be the same as gprRegister + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); + IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); // store word - PPCRecompilerImlGen_generateNewInstruction_memory_r(ppcImlGenContext, sourceRegister, gprRegister, imm+index*4, 32, true); + ppcImlGenContext->emitInst().make_memory_r(regS, regA, (sint32)imm + index * 4, 32, true); // next rS++; index++; @@ -2266,70 +1332,43 @@ bool PPCRecompilerImlGen_LSWI(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod PPC_OPC_TEMPL_X(opcode, rD, rA, nb); if( nb == 0 ) nb = 32; - if( nb == 4 ) + + if (rA == 0) { - // if nb == 4 this instruction immitates LWZ - if( rA == 0 ) - { -#ifdef CEMU_DEBUG_ASSERT - assert_dbg(); // special form where gpr is ignored and only imm is used -#endif - return false; - } - // load memory gpr into register - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - // check if destination register is already loaded - uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); - if( destinationRegister == PPC_REC_INVALID_REGISTER ) - destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new register - // load half - PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegister, 0, 32, false, true); - return true; + cemu_assert_unimplemented(); // special form where gpr is ignored and EA is 0 + return false; } - else if( nb == 2 ) + + // potential optimization: On x86 unaligned access is allowed and we could handle the case nb==4 with a single memory read, and nb==2 with a memory read and shift + + IMLReg memReg = _GetRegGPR(ppcImlGenContext, rA); + IMLReg regTmp = _GetRegTemporary(ppcImlGenContext, 0); + uint32 memOffset = 0; + while (nb > 0) { - // if nb == 2 this instruction immitates a LHZ but the result is shifted left by 16 bits - if( rA == 0 ) - { -#ifdef CEMU_DEBUG_ASSERT - assert_dbg(); // special form where gpr is ignored and only imm is used -#endif + if (rD == rA) return false; - } - // load memory gpr into register - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - // check if destination register is already loaded - uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); - if( destinationRegister == PPC_REC_INVALID_REGISTER ) - destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new register - // load half - PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegister, 0, 16, false, true); - // shift - PPCRecompilerImlGen_generateNewInstruction_r_r_s32(ppcImlGenContext, PPCREC_IML_OP_LEFT_SHIFT, destinationRegister, destinationRegister, 16); - return true; - } - else if( nb == 3 ) - { - // if nb == 3 this instruction loads a 3-byte big-endian and the result is shifted left by 8 bits - if( rA == 0 ) + cemu_assert(rD < 32); + IMLReg regDst = _GetRegGPR(ppcImlGenContext, rD); + // load bytes one-by-one + for (sint32 b = 0; b < 4; b++) { -#ifdef CEMU_DEBUG_ASSERT - assert_dbg(); // special form where gpr is ignored and only imm is used -#endif - return false; + ppcImlGenContext->emitInst().make_r_memory(regTmp, memReg, memOffset + b, 8, false, false); + sint32 shiftAmount = (3 - b) * 8; + if(shiftAmount) + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_LEFT_SHIFT, regTmp, regTmp, shiftAmount); + if(b == 0) + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, regDst, regTmp); + else + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_OR, regDst, regDst, regTmp); + nb--; + if (nb == 0) + break; } - // load memory gpr into register - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - // check if destination register is already loaded - uint32 destinationRegister = PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, PPCREC_NAME_R0+rD); - if( destinationRegister == PPC_REC_INVALID_REGISTER ) - destinationRegister = PPCRecompilerImlGen_getAndLockFreeTemporaryGPR(ppcImlGenContext, PPCREC_NAME_R0+rD); // else just create new register - // load half - PPCRecompilerImlGen_generateNewInstruction_r_memory(ppcImlGenContext, destinationRegister, gprRegister, 0, PPC_REC_STORE_LSWI_3, false, true); - return true; + memOffset += 4; + rD++; } - debug_printf("PPCRecompilerImlGen_LSWI(): Unsupported nb value %d\n", nb); - return false; + return true; } bool PPCRecompilerImlGen_STSWI(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) @@ -2338,38 +1377,111 @@ bool PPCRecompilerImlGen_STSWI(ppcImlGenContext_t* ppcImlGenContext, uint32 opco PPC_OPC_TEMPL_X(opcode, rS, rA, nb); if( nb == 0 ) nb = 32; - if( nb == 4 ) + + IMLReg regMem = _GetRegGPR(ppcImlGenContext, rA); + IMLReg regTmp = _GetRegTemporary(ppcImlGenContext, 0); + uint32 memOffset = 0; + while (nb > 0) { - // load memory gpr into register - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - // load source register - uint32 sourceRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS, false); // can be the same as gprRegister - // store word - PPCRecompilerImlGen_generateNewInstruction_memory_r(ppcImlGenContext, sourceRegister, gprRegister, 0, 32, true); - return true; + if (rS == rA) + return false; + cemu_assert(rS < 32); + IMLReg regSrc = _GetRegGPR(ppcImlGenContext, rS); + // store bytes one-by-one + for (sint32 b = 0; b < 4; b++) + { + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, regTmp, regSrc); + sint32 shiftAmount = (3 - b) * 8; + if (shiftAmount) + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_RIGHT_SHIFT_U, regTmp, regTmp, shiftAmount); + ppcImlGenContext->emitInst().make_memory_r(regTmp, regMem, memOffset + b, 8, false); + nb--; + if (nb == 0) + break; + } + memOffset += 4; + rS++; } - else if( nb == 2 ) - { - // load memory gpr into register - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - // load source register - uint32 sourceRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS, false); // can be the same as gprRegister - // store half-word (shifted << 16) - PPCRecompilerImlGen_generateNewInstruction_memory_r(ppcImlGenContext, sourceRegister, gprRegister, 0, PPC_REC_STORE_STSWI_2, false); - return true; - } - else if( nb == 3 ) - { - // load memory gpr into register - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - // load source register - uint32 sourceRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS, false); // can be the same as gprRegister - // store 3-byte-word (shifted << 8) - PPCRecompilerImlGen_generateNewInstruction_memory_r(ppcImlGenContext, sourceRegister, gprRegister, 0, PPC_REC_STORE_STSWI_3, false); - return true; - } - debug_printf("PPCRecompilerImlGen_STSWI(): Unsupported nb value %d\n", nb); - return false; + return true; +} + +bool PPCRecompilerImlGen_LWARX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rA, rD, rB; + PPC_OPC_TEMPL_X(opcode, rD, rA, rB); + + IMLReg regA = rA != 0 ? PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rA) : IMLREG_INVALID; + IMLReg regB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rB); + IMLReg regD = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rD); + IMLReg regMemResEA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_CPU_MEMRES_EA); + IMLReg regMemResVal = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_CPU_MEMRES_VAL); + // calculate EA + if (regA.IsValid()) + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_ADD, regMemResEA, regA, regB); + else + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, regMemResEA, regB); + // load word + ppcImlGenContext->emitInst().make_r_memory(regD, regMemResEA, 0, 32, false, true); + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, regMemResVal, regD); + return true; +} + +bool PPCRecompilerImlGen_STWCX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +{ + sint32 rA, rS, rB; + PPC_OPC_TEMPL_X(opcode, rS, rA, rB); + IMLReg regA = rA != 0 ? PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rA) : IMLREG_INVALID; + IMLReg regB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rB); + IMLReg regData = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rS); + IMLReg regTmpDataBE = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 2); + IMLReg regTmpCompareBE = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 3); + // calculate EA + IMLReg regCalcEA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY); + if (regA.IsValid()) + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_ADD, regCalcEA, regA, regB); + else + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, regCalcEA, regB); + // get CR bit regs and set LT, GT and SO immediately + IMLReg regCrLT = _GetRegCR(ppcImlGenContext, 0, Espresso::CR_BIT_INDEX_LT); + IMLReg regCrGT = _GetRegCR(ppcImlGenContext, 0, Espresso::CR_BIT_INDEX_GT); + IMLReg regCrEQ = _GetRegCR(ppcImlGenContext, 0, Espresso::CR_BIT_INDEX_EQ); + IMLReg regCrSO = _GetRegCR(ppcImlGenContext, 0, Espresso::CR_BIT_INDEX_SO); + IMLReg regXerSO = _GetRegCR(ppcImlGenContext, 0, Espresso::CR_BIT_INDEX_SO); + ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, regCrLT, 0); + ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, regCrGT, 0); + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, regCrSO, regXerSO); + // get regs for reservation address and value + IMLReg regMemResEA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_CPU_MEMRES_EA); + IMLReg regMemResVal = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_CPU_MEMRES_VAL); + // compare calculated EA with reservation + IMLReg regTmpBool = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 1); + ppcImlGenContext->emitInst().make_compare(regCalcEA, regMemResEA, regTmpBool, IMLCondition::EQ); + ppcImlGenContext->emitInst().make_conditional_jump(regTmpBool, true); + + PPCIMLGen_CreateSegmentBranchedPath(*ppcImlGenContext, *ppcImlGenContext->currentBasicBlock, + [&](ppcImlGenContext_t& genCtx) + { + /* branch taken, EA matching */ + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ENDIAN_SWAP, regTmpDataBE, regData); + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ENDIAN_SWAP, regTmpCompareBE, regMemResVal); + ppcImlGenContext->emitInst().make_atomic_cmp_store(regMemResEA, regTmpCompareBE, regTmpDataBE, regCrEQ); + }, + [&](ppcImlGenContext_t& genCtx) + { + /* branch not taken, EA mismatching */ + ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, regCrEQ, 0); + } + ); + + // reset reservation + // I found contradictory information of whether the reservation is cleared in all cases, so unit testing would be required + // Most sources state that it is cleared on successful store. They don't explicitly mention what happens on failure + // "The PowerPC 600 series, part 7: Atomic memory access and cache coherency" states that it is always cleared + // There may also be different behavior between individual PPC architectures + ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, regMemResEA, 0); + ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, regMemResVal, 0); + + return true; } bool PPCRecompilerImlGen_DCBZ(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) @@ -2378,92 +1490,39 @@ bool PPCRecompilerImlGen_DCBZ(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod rA = (opcode>>16)&0x1F; rB = (opcode>>11)&0x1F; // prepare registers - uint32 gprRegisterA = rA!=0?PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false):0; - uint32 gprRegisterB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); - // store - if( rA != 0 ) - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_DCBZ, gprRegisterA, gprRegisterB); + IMLReg regA = rA!=0?PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA):IMLREG_INVALID; + IMLReg regB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); + // load zero into a temporary register + IMLReg regZero = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 0); + ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, regZero, 0); + // prepare EA and align it to cacheline + IMLReg regMemResEA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 1); + if(rA != 0) + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_ADD, regMemResEA, regA, regB); else - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_DCBZ, gprRegisterB, gprRegisterB); + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, regMemResEA, regB); + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_AND, regMemResEA, regMemResEA, ~31); + // zero out the cacheline + for(sint32 i = 0; i < 32; i += 4) + ppcImlGenContext->emitInst().make_memory_r(regZero, regMemResEA, i, 32, false); return true; } -bool PPCRecompilerImlGen_OR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +bool PPCRecompilerImlGen_OR_NOR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool complementResult) { int rS, rA, rB; PPC_OPC_TEMPL_X(opcode, rS, rA, rB); - // check for MR mnemonic - if( rS == rB ) - { - // simple register copy - if( rA != rS ) // check if no-op - { - sint32 gprSourceReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); - sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - if( opcode&PPC_OPC_RC ) - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprDestReg, gprSourceReg, 0, PPCREC_CR_MODE_LOGICAL); - } - else - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprDestReg, gprSourceReg); - } - } - else - { - if( opcode&PPC_OPC_RC ) - { - // no effect but CR is updated - sint32 gprSourceReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprSourceReg, gprSourceReg, 0, PPCREC_CR_MODE_LOGICAL); - } - else - { - // no-op - } - } - } + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); + IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); + IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); + if(rS == rB) // check for MR mnemonic + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, regA, regS); else - { - // rA = rS | rA - sint32 gprSource1Reg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); - sint32 gprSource2Reg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); - sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - if( gprSource1Reg == gprDestReg || gprSource2Reg == gprDestReg ) - { - // make sure we don't overwrite rS or rA - if( gprSource1Reg == gprDestReg ) - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_OR, gprDestReg, gprSource2Reg); - } - else - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_OR, gprDestReg, gprSource1Reg); - } - if( opcode&PPC_OPC_RC ) - { - // fixme: merge CR update into OR instruction above - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_AND, gprDestReg, gprDestReg, 0, PPCREC_CR_MODE_LOGICAL); - } - } - else - { - // rA = rS - if( gprDestReg != gprSource1Reg ) - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprDestReg, gprSource1Reg); - } - // rA |= rB - if( opcode&PPC_OPC_RC ) - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_OR, gprDestReg, gprSource2Reg, 0, PPCREC_CR_MODE_LOGICAL); - } - else - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_OR, gprDestReg, gprSource2Reg); - } - } - } + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_OR, regA, regS, regB); + if(complementResult) + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_NOT, regA, regA); + if (opcode & PPC_OPC_RC) + PPCImlGen_UpdateCR0(ppcImlGenContext, regA); return true; } @@ -2471,151 +1530,33 @@ bool PPCRecompilerImlGen_ORC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode { sint32 rS, rA, rB; PPC_OPC_TEMPL_X(opcode, rS, rA, rB); - // hCPU->gpr[rA] = hCPU->gpr[rS] | ~hCPU->gpr[rB]; - sint32 gprSource1Reg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); - sint32 gprSource2Reg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); - sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - if( opcode&PPC_OPC_RC ) - PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_ORC, gprDestReg, gprSource1Reg, gprSource2Reg, 0, PPCREC_CR_MODE_LOGICAL); - else - PPCRecompilerImlGen_generateNewInstruction_r_r_r(ppcImlGenContext, PPCREC_IML_OP_ORC, gprDestReg, gprSource1Reg, gprSource2Reg); + // rA = rS | ~rB; + IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); + IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); + IMLReg regTmp = _GetRegTemporary(ppcImlGenContext, 0); + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_NOT, regTmp, regB); + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_OR, regA, regS, regTmp); + if (opcode & PPC_OPC_RC) + PPCImlGen_UpdateCR0(ppcImlGenContext, regA); return true; } -bool PPCRecompilerImlGen_NOR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +bool PPCRecompilerImlGen_AND_NAND(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool complementResult) { int rS, rA, rB; PPC_OPC_TEMPL_X(opcode, rS, rA, rB); - //hCPU->gpr[rA] = ~(hCPU->gpr[rS] | hCPU->gpr[rB]); - // check for NOT mnemonic - if( rS == rB ) - { - // simple register copy with NOT - sint32 gprSourceReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); - sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - if( gprDestReg != gprSourceReg ) - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprDestReg, gprSourceReg); - if( opcode&PPC_OPC_RC ) - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_NOT, gprDestReg, gprDestReg, 0, PPCREC_CR_MODE_ARITHMETIC); - } - else - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_NOT, gprDestReg, gprDestReg); - } - } + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); + IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); + IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); + if (regS == regB) + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_ASSIGN, regA, regS); else - { - // rA = rS | rA - sint32 gprSource1Reg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); - sint32 gprSource2Reg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); - sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - if( gprSource1Reg == gprDestReg || gprSource2Reg == gprDestReg ) - { - // make sure we don't overwrite rS or rA - if( gprSource1Reg == gprDestReg ) - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_OR, gprDestReg, gprSource2Reg); - } - else - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_OR, gprDestReg, gprSource1Reg); - } - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_NOT, gprDestReg, gprDestReg); - if( opcode&PPC_OPC_RC ) - { - // fixme: merge CR update into OR instruction above - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_AND, gprDestReg, gprDestReg, 0, PPCREC_CR_MODE_LOGICAL); - } - } - else - { - // rA = rS - if( gprDestReg != gprSource1Reg ) - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprDestReg, gprSource1Reg); - } - // rA |= rB - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_OR, gprDestReg, gprSource2Reg); - if( opcode&PPC_OPC_RC ) - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_NOT, gprDestReg, gprDestReg, 0, PPCREC_CR_MODE_ARITHMETIC); - } - else - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_NOT, gprDestReg, gprDestReg); - } - } - } - return true; -} - -bool PPCRecompilerImlGen_AND(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rS, rA, rB; - PPC_OPC_TEMPL_X(opcode, rS, rA, rB); - // check for MR mnemonic - if( rS == rB ) - { - // simple register copy - if( rA != rS ) // check if no-op - { - sint32 gprSourceReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); - sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - if( opcode&PPC_OPC_RC ) - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprDestReg, gprSourceReg, 0, PPCREC_CR_MODE_LOGICAL); - } - else - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprDestReg, gprSourceReg); - } - } - else - { - cemu_assert_unimplemented(); // no-op -> verify this case - } - } - else - { - // rA = rS & rA - sint32 gprSource1Reg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); - sint32 gprSource2Reg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); - sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - if( gprSource1Reg == gprDestReg || gprSource2Reg == gprDestReg ) - { - // make sure we don't overwrite rS or rA - if( gprSource1Reg == gprDestReg ) - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_AND, gprDestReg, gprSource2Reg); - } - else - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_AND, gprDestReg, gprSource1Reg); - } - if( opcode&PPC_OPC_RC ) - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_AND, gprDestReg, gprDestReg, 0, PPCREC_CR_MODE_LOGICAL); - } - } - else - { - // rA = rS - if( gprDestReg != gprSource1Reg ) - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprDestReg, gprSource1Reg); - } - // rA &= rB - if( opcode&PPC_OPC_RC ) - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_AND, gprDestReg, gprSource2Reg, 0, PPCREC_CR_MODE_LOGICAL); - } - else - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_AND, gprDestReg, gprSource2Reg); - } - } - } + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_AND, regA, regS, regB); + if (complementResult) + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_NOT, regA, regA); + if (opcode & PPC_OPC_RC) + PPCImlGen_UpdateCR0(ppcImlGenContext, regA); return true; } @@ -2623,277 +1564,101 @@ bool PPCRecompilerImlGen_ANDC(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod { sint32 rS, rA, rB; PPC_OPC_TEMPL_X(opcode, rS, rA, rB); - //hCPU->gpr[rA] = hCPU->gpr[rS] & ~hCPU->gpr[rB]; - //if (Opcode & PPC_OPC_RC) { - if( rS == rB ) - { - // result is always 0 -> replace with XOR rA,rA - sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - if( opcode&PPC_OPC_RC ) - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_XOR, gprDestReg, gprDestReg, 0, PPCREC_CR_MODE_LOGICAL); - } - else - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_XOR, gprDestReg, gprDestReg); - } - } - else if( rA == rB ) - { - // rB already in rA, therefore we complement rA first and then AND it with rS - sint32 gprRS = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); - sint32 gprRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - // rA = ~rA - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_NOT, gprRA, gprRA); - // rA &= rS - if( opcode&PPC_OPC_RC ) - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_AND, gprRA, gprRS, 0, PPCREC_CR_MODE_LOGICAL); - } - else - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_AND, gprRA, gprRS); - } - } - else - { - // a & (~b) is the same as ~((~a) | b) - sint32 gprRA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - sint32 gprRB = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); - sint32 gprRS = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); - // move rS to rA (if required) - if( gprRA != gprRS ) - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprRA, gprRS); - } - // rS already in rA, therefore we complement rS first and then OR it with rB - // rA = ~rA - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_NOT, gprRA, gprRA); - // rA |= rB - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_OR, gprRA, gprRB); - // rA = ~rA - if( opcode&PPC_OPC_RC ) - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_NOT, gprRA, gprRA, 0, PPCREC_CR_MODE_LOGICAL); - } - else - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_NOT, gprRA, gprRA); - } - } + // rA = rS & ~rB; + IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); + IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); + IMLReg regTmp = _GetRegTemporary(ppcImlGenContext, 0); + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_NOT, regTmp, regB); + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_AND, regA, regS, regTmp); + if (opcode & PPC_OPC_RC) + PPCImlGen_UpdateCR0(ppcImlGenContext, regA); return true; } -void PPCRecompilerImlGen_ANDI(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rS, rA; - uint32 imm; - PPC_OPC_TEMPL_D_UImm(opcode, rS, rA, imm); - // ANDI. always sets cr0 flags - sint32 gprSourceReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); - sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - // rA = rS - if( gprDestReg != gprSourceReg ) - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprDestReg, gprSourceReg); - // rA &= imm32 - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_AND, gprDestReg, (sint32)imm, 0, false, false, 0, PPCREC_CR_MODE_LOGICAL); -} - -void PPCRecompilerImlGen_ANDIS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rS, rA; - uint32 imm; - PPC_OPC_TEMPL_D_Shift16(opcode, rS, rA, imm); - // ANDI. always sets cr0 flags - sint32 gprSourceReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); - sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - // rA = rS - if( gprDestReg != gprSourceReg ) - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprDestReg, gprSourceReg); - // rA &= imm32 - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_AND, gprDestReg, (sint32)imm, 0, false, false, 0, PPCREC_CR_MODE_LOGICAL); -} - -bool PPCRecompilerImlGen_XOR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +bool PPCRecompilerImlGen_XOR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool complementResult) { sint32 rS, rA, rB; PPC_OPC_TEMPL_X(opcode, rS, rA, rB); + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); if( rS == rB ) { - // xor register with itself - sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - if( opcode&PPC_OPC_RC ) - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_XOR, gprDestReg, gprDestReg, 0, PPCREC_CR_MODE_LOGICAL); - } - else - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_XOR, gprDestReg, gprDestReg); - } + ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, regA, 0); } else { - // rA = rS ^ rA - sint32 gprSource1Reg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); - sint32 gprSource2Reg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); - sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - if( gprSource1Reg == gprDestReg || gprSource2Reg == gprDestReg ) - { - // make sure we don't overwrite rS or rA - if( gprSource1Reg == gprDestReg ) - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_XOR, gprDestReg, gprSource2Reg); - } - else - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_XOR, gprDestReg, gprSource1Reg); - } - if( opcode&PPC_OPC_RC ) - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_AND, gprDestReg, gprDestReg, 0, PPCREC_CR_MODE_LOGICAL); - } - } - else - { - // rA = rS - if( gprDestReg != gprSource1Reg ) - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprDestReg, gprSource1Reg); - } - // rA ^= rB - if( opcode&PPC_OPC_RC ) - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_XOR, gprDestReg, gprSource2Reg, 0, PPCREC_CR_MODE_LOGICAL); - } - else - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_XOR, gprDestReg, gprSource2Reg); - } - } + IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); + IMLReg regB = _GetRegGPR(ppcImlGenContext, rB); + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_XOR, regA, regS, regB); } + if (complementResult) + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_NOT, regA, regA); + if (opcode & PPC_OPC_RC) + PPCImlGen_UpdateCR0(ppcImlGenContext, regA); return true; } - -bool PPCRecompilerImlGen_EQV(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +void PPCRecompilerImlGen_ANDI_ANDIS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool isShifted) { - sint32 rS, rA, rB; - PPC_OPC_TEMPL_X(opcode, rS, rA, rB); - if( rS == rB ) + sint32 rS, rA; + uint32 imm; + if (isShifted) { - // xor register with itself, then invert - sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_XOR, gprDestReg, gprDestReg); - if( opcode&PPC_OPC_RC ) - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_NOT, gprDestReg, gprDestReg, 0, PPCREC_CR_MODE_LOGICAL); - else - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_NOT, gprDestReg, gprDestReg); + PPC_OPC_TEMPL_D_Shift16(opcode, rS, rA, imm); } else { - // rA = ~(rS ^ rA) - sint32 gprSource1Reg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); - sint32 gprSource2Reg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); - sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - if( gprSource1Reg == gprDestReg || gprSource2Reg == gprDestReg ) - { - // make sure we don't overwrite rS or rA - if( gprSource1Reg == gprDestReg ) - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_XOR, gprDestReg, gprSource2Reg); - } - else - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_XOR, gprDestReg, gprSource1Reg); - } - } - else - { - // rA = rS - if( gprDestReg != gprSource1Reg ) - { - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprDestReg, gprSource1Reg); - } - // rA ^= rB - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_XOR, gprDestReg, gprSource2Reg); - } - if( opcode&PPC_OPC_RC ) - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_NOT, gprDestReg, gprDestReg, 0, PPCREC_CR_MODE_LOGICAL); - else - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_NOT, gprDestReg, gprDestReg); + PPC_OPC_TEMPL_D_UImm(opcode, rS, rA, imm); } - return true; + IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_AND, regA, regS, (sint32)imm); + // ANDI/ANDIS always updates cr0 + PPCImlGen_UpdateCR0(ppcImlGenContext, regA); } -void PPCRecompilerImlGen_ORI(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +void PPCRecompilerImlGen_ORI_ORIS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool isShifted) { sint32 rS, rA; uint32 imm; - PPC_OPC_TEMPL_D_UImm(opcode, rS, rA, imm); - // ORI does not set cr0 flags - //hCPU->gpr[rA] = hCPU->gpr[rS] | imm; - sint32 gprSourceReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); - sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - // rA = rS - if( gprDestReg != gprSourceReg ) - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprDestReg, gprSourceReg); - // rA |= imm32 - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_OR, gprDestReg, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); + if (isShifted) + { + PPC_OPC_TEMPL_D_Shift16(opcode, rS, rA, imm); + } + else + { + PPC_OPC_TEMPL_D_UImm(opcode, rS, rA, imm); + } + IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_OR, regA, regS, (sint32)imm); } -void PPCRecompilerImlGen_ORIS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +void PPCRecompilerImlGen_XORI_XORIS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool isShifted) { sint32 rS, rA; uint32 imm; - PPC_OPC_TEMPL_D_Shift16(opcode, rS, rA, imm); - // ORI does not set cr0 flags - //hCPU->gpr[rA] = hCPU->gpr[rS] | imm; - sint32 gprSourceReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); - sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - // rA = rS - if( gprDestReg != gprSourceReg ) - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprDestReg, gprSourceReg); - // rA |= imm32 - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_OR, gprDestReg, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); -} - -void PPCRecompilerImlGen_XORI(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rS, rA; - uint32 imm; - PPC_OPC_TEMPL_D_UImm(opcode, rS, rA, imm); - //hCPU->gpr[rA] = hCPU->gpr[rS] ^ imm; - // XORI does not set cr0 flags - sint32 gprSourceReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); - sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - // rA = rS - if( gprDestReg != gprSourceReg ) - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprDestReg, gprSourceReg); - // rA |= imm32 - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_XOR, gprDestReg, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); -} - -void PPCRecompilerImlGen_XORIS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rS, rA; - uint32 imm; - PPC_OPC_TEMPL_D_Shift16(opcode, rS, rA, imm); - //hCPU->gpr[rA] = hCPU->gpr[rS] ^ imm; - // XORIS does not set cr0 flags - sint32 gprSourceReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rS); - sint32 gprDestReg = PPCRecompilerImlGen_loadOverwriteRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - // rA = rS - if( gprDestReg != gprSourceReg ) - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ASSIGN, gprDestReg, gprSourceReg); - // rA |= imm32 - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_XOR, gprDestReg, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); + if (isShifted) + { + PPC_OPC_TEMPL_D_Shift16(opcode, rS, rA, imm); + } + else + { + PPC_OPC_TEMPL_D_UImm(opcode, rS, rA, imm); + } + IMLReg regS = _GetRegGPR(ppcImlGenContext, rS); + IMLReg regA = _GetRegGPR(ppcImlGenContext, rA); + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_XOR, regA, regS, (sint32)imm); } bool PPCRecompilerImlGen_CROR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { int crD, crA, crB; PPC_OPC_TEMPL_X(opcode, crD, crA, crB); - PPCRecompilerImlGen_generateNewInstruction_cr(ppcImlGenContext, PPCREC_IML_OP_CR_OR, crD, crA, crB); + IMLReg regCrA = _GetRegCR(ppcImlGenContext, crA); + IMLReg regCrB = _GetRegCR(ppcImlGenContext, crB); + IMLReg regCrR = _GetRegCR(ppcImlGenContext, crD); + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_OR, regCrR, regCrA, regCrB); return true; } @@ -2901,7 +1666,12 @@ bool PPCRecompilerImlGen_CRORC(ppcImlGenContext_t* ppcImlGenContext, uint32 opco { int crD, crA, crB; PPC_OPC_TEMPL_X(opcode, crD, crA, crB); - PPCRecompilerImlGen_generateNewInstruction_cr(ppcImlGenContext, PPCREC_IML_OP_CR_ORC, crD, crA, crB); + IMLReg regCrA = _GetRegCR(ppcImlGenContext, crA); + IMLReg regCrB = _GetRegCR(ppcImlGenContext, crB); + IMLReg regCrR = _GetRegCR(ppcImlGenContext, crD); + IMLReg regTmp = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY); + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_XOR, regTmp, regCrB, 1); // invert crB + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_OR, regCrR, regCrA, regTmp); return true; } @@ -2909,7 +1679,10 @@ bool PPCRecompilerImlGen_CRAND(ppcImlGenContext_t* ppcImlGenContext, uint32 opco { int crD, crA, crB; PPC_OPC_TEMPL_X(opcode, crD, crA, crB); - PPCRecompilerImlGen_generateNewInstruction_cr(ppcImlGenContext, PPCREC_IML_OP_CR_AND, crD, crA, crB); + IMLReg regCrA = _GetRegCR(ppcImlGenContext, crA); + IMLReg regCrB = _GetRegCR(ppcImlGenContext, crB); + IMLReg regCrR = _GetRegCR(ppcImlGenContext, crD); + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_AND, regCrR, regCrA, regCrB); return true; } @@ -2917,7 +1690,12 @@ bool PPCRecompilerImlGen_CRANDC(ppcImlGenContext_t* ppcImlGenContext, uint32 opc { int crD, crA, crB; PPC_OPC_TEMPL_X(opcode, crD, crA, crB); - PPCRecompilerImlGen_generateNewInstruction_cr(ppcImlGenContext, PPCREC_IML_OP_CR_ANDC, crD, crA, crB); + IMLReg regCrA = _GetRegCR(ppcImlGenContext, crA); + IMLReg regCrB = _GetRegCR(ppcImlGenContext, crB); + IMLReg regCrR = _GetRegCR(ppcImlGenContext, crD); + IMLReg regTmp = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY); + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_XOR, regTmp, regCrB, 1); // invert crB + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_AND, regCrR, regCrA, regTmp); return true; } @@ -2925,17 +1703,15 @@ bool PPCRecompilerImlGen_CRXOR(ppcImlGenContext_t* ppcImlGenContext, uint32 opco { int crD, crA, crB; PPC_OPC_TEMPL_X(opcode, crD, crA, crB); - if (crA == crB) + IMLReg regCrA = _GetRegCR(ppcImlGenContext, crA); + IMLReg regCrB = _GetRegCR(ppcImlGenContext, crB); + IMLReg regCrR = _GetRegCR(ppcImlGenContext, crD); + if (regCrA == regCrB) { - // both operands equal, clear bit in crD - // PPC's assert() uses this to pass a parameter to OSPanic - PPCRecompilerImlGen_generateNewInstruction_cr(ppcImlGenContext, PPCREC_IML_OP_CR_CLEAR, crD, 0, 0); + ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, regCrR, 0); return true; } - else - { - return false; - } + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_XOR, regCrR, regCrA, regCrB); return true; } @@ -2943,23 +1719,24 @@ bool PPCRecompilerImlGen_CREQV(ppcImlGenContext_t* ppcImlGenContext, uint32 opco { int crD, crA, crB; PPC_OPC_TEMPL_X(opcode, crD, crA, crB); - if (crA == crB) + IMLReg regCrA = _GetRegCR(ppcImlGenContext, crA); + IMLReg regCrB = _GetRegCR(ppcImlGenContext, crB); + IMLReg regCrR = _GetRegCR(ppcImlGenContext, crD); + if (regCrA == regCrB) { - // both operands equal, set bit in crD - PPCRecompilerImlGen_generateNewInstruction_cr(ppcImlGenContext, PPCREC_IML_OP_CR_SET, crD, 0, 0); + ppcImlGenContext->emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, regCrR, 1); return true; } - else - { - return false; - } + IMLReg regTmp = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY); + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_XOR, regTmp, regCrB, 1); // invert crB + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_XOR, regCrR, regCrA, regTmp); return true; } bool PPCRecompilerImlGen_HLE(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { uint32 hleFuncId = opcode&0xFFFF; - PPCRecompilerImlGen_generateNewInstruction_macro(ppcImlGenContext, PPCREC_IML_MACRO_HLE, ppcImlGenContext->ppcAddressOfCurrentInstruction, hleFuncId, 0); + ppcImlGenContext->emitInst().make_macro(PPCREC_IML_MACRO_HLE, ppcImlGenContext->ppcAddressOfCurrentInstruction, hleFuncId, 0, IMLREG_INVALID); return true; } @@ -2970,12 +1747,6 @@ uint32 PPCRecompiler_iterateCurrentInstruction(ppcImlGenContext_t* ppcImlGenCont return v; } -uint32 PPCRecompiler_getInstructionByOffset(ppcImlGenContext_t* ppcImlGenContext, uint32 offset) -{ - uint32 v = CPU_swapEndianU32(*(ppcImlGenContext->currentInstruction + offset/4)); - return v; -} - uint32 PPCRecompiler_getCurrentInstruction(ppcImlGenContext_t* ppcImlGenContext) { uint32 v = CPU_swapEndianU32(*(ppcImlGenContext->currentInstruction)); @@ -2988,480 +1759,10 @@ uint32 PPCRecompiler_getPreviousInstruction(ppcImlGenContext_t* ppcImlGenContext return v; } -char _tempOpcodename[32]; - -const char* PPCRecompiler_getOpcodeDebugName(PPCRecImlInstruction_t* iml) -{ - uint32 op = iml->operation; - if (op == PPCREC_IML_OP_ASSIGN) - return "MOV"; - else if (op == PPCREC_IML_OP_ADD) - return "ADD"; - else if (op == PPCREC_IML_OP_SUB) - return "SUB"; - else if (op == PPCREC_IML_OP_ADD_CARRY_UPDATE_CARRY) - return "ADDCSC"; - else if (op == PPCREC_IML_OP_OR) - return "OR"; - else if (op == PPCREC_IML_OP_AND) - return "AND"; - else if (op == PPCREC_IML_OP_XOR) - return "XOR"; - else if (op == PPCREC_IML_OP_LEFT_SHIFT) - return "LSH"; - else if (op == PPCREC_IML_OP_RIGHT_SHIFT) - return "RSH"; - else if (op == PPCREC_IML_OP_MULTIPLY_SIGNED) - return "MULS"; - else if (op == PPCREC_IML_OP_DIVIDE_SIGNED) - return "DIVS"; - - sprintf(_tempOpcodename, "OP0%02x_T%d", iml->operation, iml->type); - return _tempOpcodename; -} - -void PPCRecDebug_addRegisterParam(StringBuf& strOutput, sint32 virtualRegister, bool isLast = false) -{ - if (isLast) - { - if (virtualRegister < 10) - strOutput.addFmt("t{} ", virtualRegister); - else - strOutput.addFmt("t{}", virtualRegister); - return; - } - if (virtualRegister < 10) - strOutput.addFmt("t{} , ", virtualRegister); - else - strOutput.addFmt("t{}, ", virtualRegister); -} - -void PPCRecDebug_addS32Param(StringBuf& strOutput, sint32 val, bool isLast = false) -{ - if (isLast) - { - strOutput.addFmt("0x{:08x}", val); - return; - } - strOutput.addFmt("0x{:08x}, ", val); -} - -void PPCRecompilerDebug_printLivenessRangeInfo(StringBuf& currentLineText, PPCRecImlSegment_t* imlSegment, sint32 offset) -{ - // pad to 70 characters - sint32 index = currentLineText.getLen(); - while (index < 70) - { - debug_printf(" "); - index++; - } - raLivenessSubrange_t* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; - while (subrangeItr) - { - if (offset == subrangeItr->start.index) - { - if (false)//subrange->isDirtied && i == subrange->becomesDirtyAtIndex.index) - { - debug_printf("*%-2d", subrangeItr->range->virtualRegister); - } - else - { - debug_printf("|%-2d", subrangeItr->range->virtualRegister); - } - } - else if (false)//subrange->isDirtied && i == subrange->becomesDirtyAtIndex.index ) - { - debug_printf("* "); - } - else if (offset >= subrangeItr->start.index && offset < subrangeItr->end.index) - { - debug_printf("| "); - } - else - { - debug_printf(" "); - } - index += 3; - // next - subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; - } -} - -void PPCRecompiler_dumpIMLSegment(PPCRecImlSegment_t* imlSegment, sint32 segmentIndex, bool printLivenessRangeInfo) -{ - StringBuf strOutput(1024); - - strOutput.addFmt("SEGMENT 0x{:04x} 0x{:08x} PPC 0x{:08x} - 0x{:08x} Loop-depth {}", segmentIndex, imlSegment->ppcAddress, imlSegment->ppcAddrMin, imlSegment->ppcAddrMax, imlSegment->loopDepth); - if (imlSegment->isEnterable) - { - strOutput.addFmt(" ENTERABLE (0x{:08x})", imlSegment->enterPPCAddress); - } - else if( imlSegment->isJumpDestination ) - { - strOutput.addFmt(" JUMP-DEST (0x{:08x})", imlSegment->jumpDestinationPPCAddress); - } - - debug_printf("%s\n", strOutput.c_str()); - - strOutput.reset(); - strOutput.addFmt("SEGMENT NAME 0x{:016x}", (uintptr_t)imlSegment); - debug_printf("%s", strOutput.c_str()); - - if (printLivenessRangeInfo) - { - PPCRecompilerDebug_printLivenessRangeInfo(strOutput, imlSegment, RA_INTER_RANGE_START); - } - debug_printf("\n"); - - sint32 lineOffsetParameters = 18; - - for(sint32 i=0; i<imlSegment->imlListCount; i++) - { - // don't log NOP instructions unless they have an associated PPC address - if(imlSegment->imlList[i].type == PPCREC_IML_TYPE_NO_OP && imlSegment->imlList[i].associatedPPCAddress == MPTR_NULL) - continue; - strOutput.reset(); - strOutput.addFmt("{:08x} ", imlSegment->imlList[i].associatedPPCAddress); - if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_R_NAME || imlSegment->imlList[i].type == PPCREC_IML_TYPE_NAME_R) - { - if(imlSegment->imlList[i].type == PPCREC_IML_TYPE_R_NAME) - strOutput.add("LD_NAME"); - else - strOutput.add("ST_NAME"); - while ((sint32)strOutput.getLen() < lineOffsetParameters) - strOutput.add(" "); - - PPCRecDebug_addRegisterParam(strOutput, imlSegment->imlList[i].op_r_name.registerIndex); - - strOutput.addFmt("name_{} (", imlSegment->imlList[i].op_r_name.registerIndex, imlSegment->imlList[i].op_r_name.name); - if( imlSegment->imlList[i].op_r_name.name >= PPCREC_NAME_R0 && imlSegment->imlList[i].op_r_name.name < (PPCREC_NAME_R0+999) ) - { - strOutput.addFmt("r{}", imlSegment->imlList[i].op_r_name.name-PPCREC_NAME_R0); - } - else if( imlSegment->imlList[i].op_r_name.name >= PPCREC_NAME_SPR0 && imlSegment->imlList[i].op_r_name.name < (PPCREC_NAME_SPR0+999) ) - { - strOutput.addFmt("spr{}", imlSegment->imlList[i].op_r_name.name-PPCREC_NAME_SPR0); - } - else - strOutput.add("ukn"); - strOutput.add(")"); - } - else if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_R_R ) - { - strOutput.addFmt("{}", PPCRecompiler_getOpcodeDebugName(imlSegment->imlList+i)); - while ((sint32)strOutput.getLen() < lineOffsetParameters) - strOutput.add(" "); - PPCRecDebug_addRegisterParam(strOutput, imlSegment->imlList[i].op_r_r.registerResult); - PPCRecDebug_addRegisterParam(strOutput, imlSegment->imlList[i].op_r_r.registerA, true); - - if( imlSegment->imlList[i].crRegister != PPC_REC_INVALID_REGISTER ) - { - strOutput.addFmt(" -> CR{}", imlSegment->imlList[i].crRegister); - } - } - else if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_R_R_R ) - { - strOutput.addFmt("{}", PPCRecompiler_getOpcodeDebugName(imlSegment->imlList + i)); - while ((sint32)strOutput.getLen() < lineOffsetParameters) - strOutput.add(" "); - PPCRecDebug_addRegisterParam(strOutput, imlSegment->imlList[i].op_r_r_r.registerResult); - PPCRecDebug_addRegisterParam(strOutput, imlSegment->imlList[i].op_r_r_r.registerA); - PPCRecDebug_addRegisterParam(strOutput, imlSegment->imlList[i].op_r_r_r.registerB, true); - if( imlSegment->imlList[i].crRegister != PPC_REC_INVALID_REGISTER ) - { - strOutput.addFmt(" -> CR{}", imlSegment->imlList[i].crRegister); - } - } - else if (imlSegment->imlList[i].type == PPCREC_IML_TYPE_R_R_S32) - { - strOutput.addFmt("{}", PPCRecompiler_getOpcodeDebugName(imlSegment->imlList + i)); - while ((sint32)strOutput.getLen() < lineOffsetParameters) - strOutput.add(" "); - - PPCRecDebug_addRegisterParam(strOutput, imlSegment->imlList[i].op_r_r_s32.registerResult); - PPCRecDebug_addRegisterParam(strOutput, imlSegment->imlList[i].op_r_r_s32.registerA); - PPCRecDebug_addS32Param(strOutput, imlSegment->imlList[i].op_r_r_s32.immS32, true); - - if (imlSegment->imlList[i].crRegister != PPC_REC_INVALID_REGISTER) - { - strOutput.addFmt(" -> CR{}", imlSegment->imlList[i].crRegister); - } - } - else if (imlSegment->imlList[i].type == PPCREC_IML_TYPE_R_S32) - { - strOutput.addFmt("{}", PPCRecompiler_getOpcodeDebugName(imlSegment->imlList + i)); - while ((sint32)strOutput.getLen() < lineOffsetParameters) - strOutput.add(" "); - - PPCRecDebug_addRegisterParam(strOutput, imlSegment->imlList[i].op_r_immS32.registerIndex); - PPCRecDebug_addS32Param(strOutput, imlSegment->imlList[i].op_r_immS32.immS32, true); - - if (imlSegment->imlList[i].crRegister != PPC_REC_INVALID_REGISTER) - { - strOutput.addFmt(" -> CR{}", imlSegment->imlList[i].crRegister); - } - } - else if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_JUMPMARK ) - { - strOutput.addFmt("jm_{:08x}:", imlSegment->imlList[i].op_jumpmark.address); - } - else if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_PPC_ENTER ) - { - strOutput.addFmt("ppcEnter_{:08x}:", imlSegment->imlList[i].op_ppcEnter.ppcAddress); - } - else if(imlSegment->imlList[i].type == PPCREC_IML_TYPE_LOAD || imlSegment->imlList[i].type == PPCREC_IML_TYPE_STORE || - imlSegment->imlList[i].type == PPCREC_IML_TYPE_LOAD_INDEXED || imlSegment->imlList[i].type == PPCREC_IML_TYPE_STORE_INDEXED ) - { - if(imlSegment->imlList[i].type == PPCREC_IML_TYPE_LOAD || imlSegment->imlList[i].type == PPCREC_IML_TYPE_LOAD_INDEXED) - strOutput.add("LD_"); - else - strOutput.add("ST_"); - - if (imlSegment->imlList[i].op_storeLoad.flags2.signExtend) - strOutput.add("S"); - else - strOutput.add("U"); - strOutput.addFmt("{}", imlSegment->imlList[i].op_storeLoad.copyWidth); - - while ((sint32)strOutput.getLen() < lineOffsetParameters) - strOutput.add(" "); - - PPCRecDebug_addRegisterParam(strOutput, imlSegment->imlList[i].op_storeLoad.registerData); - - if(imlSegment->imlList[i].type == PPCREC_IML_TYPE_LOAD_INDEXED || imlSegment->imlList[i].type == PPCREC_IML_TYPE_STORE_INDEXED) - strOutput.addFmt("[t{}+t{}]", imlSegment->imlList[i].op_storeLoad.registerMem, imlSegment->imlList[i].op_storeLoad.registerMem2); - else - strOutput.addFmt("[t{}+{}]", imlSegment->imlList[i].op_storeLoad.registerMem, imlSegment->imlList[i].op_storeLoad.immS32); - } - else if (imlSegment->imlList[i].type == PPCREC_IML_TYPE_MEM2MEM) - { - strOutput.addFmt("{} [t{}+{}] = [t{}+{}]", imlSegment->imlList[i].op_mem2mem.copyWidth, imlSegment->imlList[i].op_mem2mem.dst.registerMem, imlSegment->imlList[i].op_mem2mem.dst.immS32, imlSegment->imlList[i].op_mem2mem.src.registerMem, imlSegment->imlList[i].op_mem2mem.src.immS32); - } - else if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_CJUMP ) - { - if (imlSegment->imlList[i].op_conditionalJump.condition == PPCREC_JUMP_CONDITION_E) - strOutput.add("JE"); - else if (imlSegment->imlList[i].op_conditionalJump.condition == PPCREC_JUMP_CONDITION_NE) - strOutput.add("JNE"); - else if (imlSegment->imlList[i].op_conditionalJump.condition == PPCREC_JUMP_CONDITION_G) - strOutput.add("JG"); - else if (imlSegment->imlList[i].op_conditionalJump.condition == PPCREC_JUMP_CONDITION_GE) - strOutput.add("JGE"); - else if (imlSegment->imlList[i].op_conditionalJump.condition == PPCREC_JUMP_CONDITION_L) - strOutput.add("JL"); - else if (imlSegment->imlList[i].op_conditionalJump.condition == PPCREC_JUMP_CONDITION_LE) - strOutput.add("JLE"); - else if (imlSegment->imlList[i].op_conditionalJump.condition == PPCREC_JUMP_CONDITION_NONE) - strOutput.add("JALW"); // jump always - else - cemu_assert_unimplemented(); - strOutput.addFmt(" jm_{:08x} (cr{})", imlSegment->imlList[i].op_conditionalJump.jumpmarkAddress, imlSegment->imlList[i].crRegister); - } - else if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_NO_OP ) - { - strOutput.add("NOP"); - } - else if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_MACRO ) - { - if( imlSegment->imlList[i].operation == PPCREC_IML_MACRO_BLR ) - { - strOutput.addFmt("MACRO BLR 0x{:08x} cycles (depr): {}", imlSegment->imlList[i].op_macro.param, (sint32)imlSegment->imlList[i].op_macro.paramU16); - } - else if( imlSegment->imlList[i].operation == PPCREC_IML_MACRO_BLRL ) - { - strOutput.addFmt("MACRO BLRL 0x{:08x} cycles (depr): {}", imlSegment->imlList[i].op_macro.param, (sint32)imlSegment->imlList[i].op_macro.paramU16); - } - else if( imlSegment->imlList[i].operation == PPCREC_IML_MACRO_BCTR ) - { - strOutput.addFmt("MACRO BCTR 0x{:08x} cycles (depr): {}", imlSegment->imlList[i].op_macro.param, (sint32)imlSegment->imlList[i].op_macro.paramU16); - } - else if( imlSegment->imlList[i].operation == PPCREC_IML_MACRO_BCTRL ) - { - strOutput.addFmt("MACRO BCTRL 0x{:08x} cycles (depr): {}", imlSegment->imlList[i].op_macro.param, (sint32)imlSegment->imlList[i].op_macro.paramU16); - } - else if( imlSegment->imlList[i].operation == PPCREC_IML_MACRO_BL ) - { - strOutput.addFmt("MACRO BL 0x{:08x} -> 0x{:08x} cycles (depr): {}", imlSegment->imlList[i].op_macro.param, imlSegment->imlList[i].op_macro.param2, (sint32)imlSegment->imlList[i].op_macro.paramU16); - } - else if( imlSegment->imlList[i].operation == PPCREC_IML_MACRO_B_FAR ) - { - strOutput.addFmt("MACRO B_FAR 0x{:08x} -> 0x{:08x} cycles (depr): {}", imlSegment->imlList[i].op_macro.param, imlSegment->imlList[i].op_macro.param2, (sint32)imlSegment->imlList[i].op_macro.paramU16); - } - else if( imlSegment->imlList[i].operation == PPCREC_IML_MACRO_LEAVE ) - { - strOutput.addFmt("MACRO LEAVE ppc: 0x{:08x}", imlSegment->imlList[i].op_macro.param); - } - else if( imlSegment->imlList[i].operation == PPCREC_IML_MACRO_HLE ) - { - strOutput.addFmt("MACRO HLE ppcAddr: 0x{:08x} funcId: 0x{:08x}", imlSegment->imlList[i].op_macro.param, imlSegment->imlList[i].op_macro.param2); - } - else if( imlSegment->imlList[i].operation == PPCREC_IML_MACRO_MFTB ) - { - strOutput.addFmt("MACRO MFTB ppcAddr: 0x{:08x} sprId: 0x{:08x}", imlSegment->imlList[i].op_macro.param, imlSegment->imlList[i].op_macro.param2); - } - else if( imlSegment->imlList[i].operation == PPCREC_IML_MACRO_COUNT_CYCLES ) - { - strOutput.addFmt("MACRO COUNT_CYCLES cycles: {}", imlSegment->imlList[i].op_macro.param); - } - else - { - strOutput.addFmt("MACRO ukn operation {}", imlSegment->imlList[i].operation); - } - } - else if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_FPR_R_NAME ) - { - strOutput.addFmt("fpr_t{} = name_{} (", imlSegment->imlList[i].op_r_name.registerIndex, imlSegment->imlList[i].op_r_name.name); - if( imlSegment->imlList[i].op_r_name.name >= PPCREC_NAME_FPR0 && imlSegment->imlList[i].op_r_name.name < (PPCREC_NAME_FPR0+999) ) - { - strOutput.addFmt("fpr{}", imlSegment->imlList[i].op_r_name.name-PPCREC_NAME_FPR0); - } - else if( imlSegment->imlList[i].op_r_name.name >= PPCREC_NAME_TEMPORARY_FPR0 && imlSegment->imlList[i].op_r_name.name < (PPCREC_NAME_TEMPORARY_FPR0+999) ) - { - strOutput.addFmt("tempFpr{}", imlSegment->imlList[i].op_r_name.name-PPCREC_NAME_TEMPORARY_FPR0); - } - else - strOutput.add("ukn"); - strOutput.add(")"); - } - else if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_FPR_NAME_R ) - { - strOutput.addFmt("name_{} (", imlSegment->imlList[i].op_r_name.name); - if( imlSegment->imlList[i].op_r_name.name >= PPCREC_NAME_FPR0 && imlSegment->imlList[i].op_r_name.name < (PPCREC_NAME_FPR0+999) ) - { - strOutput.addFmt("fpr{}", imlSegment->imlList[i].op_r_name.name-PPCREC_NAME_FPR0); - } - else if( imlSegment->imlList[i].op_r_name.name >= PPCREC_NAME_TEMPORARY_FPR0 && imlSegment->imlList[i].op_r_name.name < (PPCREC_NAME_TEMPORARY_FPR0+999) ) - { - strOutput.addFmt("tempFpr{}", imlSegment->imlList[i].op_r_name.name-PPCREC_NAME_TEMPORARY_FPR0); - } - else - strOutput.add("ukn"); - strOutput.addFmt(") = fpr_t{}", imlSegment->imlList[i].op_r_name.registerIndex); - } - else if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_FPR_LOAD ) - { - strOutput.addFmt("fpr_t{} = ", imlSegment->imlList[i].op_storeLoad.registerData); - if( imlSegment->imlList[i].op_storeLoad.flags2.signExtend ) - strOutput.add("S"); - else - strOutput.add("U"); - strOutput.addFmt("{} [t{}+{}] mode {}", imlSegment->imlList[i].op_storeLoad.copyWidth / 8, imlSegment->imlList[i].op_storeLoad.registerMem, imlSegment->imlList[i].op_storeLoad.immS32, imlSegment->imlList[i].op_storeLoad.mode); - if (imlSegment->imlList[i].op_storeLoad.flags2.notExpanded) - { - strOutput.addFmt(" <No expand>"); - } - } - else if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_FPR_STORE ) - { - if( imlSegment->imlList[i].op_storeLoad.flags2.signExtend ) - strOutput.add("S"); - else - strOutput.add("U"); - strOutput.addFmt("{} [t{}+{}]", imlSegment->imlList[i].op_storeLoad.copyWidth/8, imlSegment->imlList[i].op_storeLoad.registerMem, imlSegment->imlList[i].op_storeLoad.immS32); - strOutput.addFmt("= fpr_t{} mode {}\n", imlSegment->imlList[i].op_storeLoad.registerData, imlSegment->imlList[i].op_storeLoad.mode); - } - else if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_FPR_R_R ) - { - strOutput.addFmt("{:-6} ", PPCRecompiler_getOpcodeDebugName(&imlSegment->imlList[i])); - strOutput.addFmt("fpr{:02d}, fpr{:02d}", imlSegment->imlList[i].op_fpr_r_r.registerResult, imlSegment->imlList[i].op_fpr_r_r.registerOperand); - } - else if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_FPR_R_R_R_R ) - { - strOutput.addFmt("{:-6} ", PPCRecompiler_getOpcodeDebugName(&imlSegment->imlList[i])); - strOutput.addFmt("fpr{:02d}, fpr{:02d}, fpr{:02d}, fpr{:02d}", imlSegment->imlList[i].op_fpr_r_r_r_r.registerResult, imlSegment->imlList[i].op_fpr_r_r_r_r.registerOperandA, imlSegment->imlList[i].op_fpr_r_r_r_r.registerOperandB, imlSegment->imlList[i].op_fpr_r_r_r_r.registerOperandC); - } - else if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_FPR_R_R_R ) - { - strOutput.addFmt("{:-6} ", PPCRecompiler_getOpcodeDebugName(&imlSegment->imlList[i])); - strOutput.addFmt("fpr{:02d}, fpr{:02d}, fpr{:02d}", imlSegment->imlList[i].op_fpr_r_r_r.registerResult, imlSegment->imlList[i].op_fpr_r_r_r.registerOperandA, imlSegment->imlList[i].op_fpr_r_r_r.registerOperandB); - } - else if (imlSegment->imlList[i].type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK) - { - strOutput.addFmt("CYCLE_CHECK jm_{:08x}\n", imlSegment->imlList[i].op_conditionalJump.jumpmarkAddress); - } - else if (imlSegment->imlList[i].type == PPCREC_IML_TYPE_CONDITIONAL_R_S32) - { - strOutput.addFmt("t{} ", imlSegment->imlList[i].op_conditional_r_s32.registerIndex); - bool displayAsHex = false; - if (imlSegment->imlList[i].operation == PPCREC_IML_OP_ASSIGN) - { - displayAsHex = true; - strOutput.add("="); - } - else - strOutput.addFmt("(unknown operation CONDITIONAL_R_S32 {})", imlSegment->imlList[i].operation); - if (displayAsHex) - strOutput.addFmt(" 0x{:x}", imlSegment->imlList[i].op_conditional_r_s32.immS32); - else - strOutput.addFmt(" {}", imlSegment->imlList[i].op_conditional_r_s32.immS32); - strOutput.add(" (conditional)"); - if (imlSegment->imlList[i].crRegister != PPC_REC_INVALID_REGISTER) - { - strOutput.addFmt(" -> and update CR{}", imlSegment->imlList[i].crRegister); - } - } - else - { - strOutput.addFmt("Unknown iml type {}", imlSegment->imlList[i].type); - } - debug_printf("%s", strOutput.c_str()); - if (printLivenessRangeInfo) - { - PPCRecompilerDebug_printLivenessRangeInfo(strOutput, imlSegment, i); - } - debug_printf("\n"); - } - // all ranges - if (printLivenessRangeInfo) - { - debug_printf("Ranges-VirtReg "); - raLivenessSubrange_t* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; - while(subrangeItr) - { - debug_printf("v%-2d", subrangeItr->range->virtualRegister); - subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; - } - debug_printf("\n"); - debug_printf("Ranges-PhysReg "); - subrangeItr = imlSegment->raInfo.linkedList_allSubranges; - while (subrangeItr) - { - debug_printf("p%-2d", subrangeItr->range->physicalRegister); - subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; - } - debug_printf("\n"); - } - // branch info - debug_printf("Links from: "); - for (sint32 i = 0; i < imlSegment->list_prevSegments.size(); i++) - { - if (i) - debug_printf(", "); - debug_printf("%p", (void*)imlSegment->list_prevSegments[i]); - } - debug_printf("\n"); - debug_printf("Links to: "); - if (imlSegment->nextSegmentBranchNotTaken) - debug_printf("%p (no branch), ", (void*)imlSegment->nextSegmentBranchNotTaken); - if (imlSegment->nextSegmentBranchTaken) - debug_printf("%p (branch)", (void*)imlSegment->nextSegmentBranchTaken); - debug_printf("\n"); -} - -void PPCRecompiler_dumpIML(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext) -{ - for(sint32 f=0; f<ppcImlGenContext->segmentListCount; f++) - { - PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[f]; - PPCRecompiler_dumpIMLSegment(imlSegment, f); - debug_printf("\n"); - } -} - -void PPCRecompilerIml_setSegmentPoint(ppcRecompilerSegmentPoint_t* segmentPoint, PPCRecImlSegment_t* imlSegment, sint32 index) +void PPCRecompilerIml_setSegmentPoint(IMLSegmentPoint* segmentPoint, IMLSegment* imlSegment, sint32 index) { segmentPoint->imlSegment = imlSegment; - segmentPoint->index = index; + segmentPoint->SetInstructionIndex(index); if (imlSegment->segmentPointList) imlSegment->segmentPointList->prev = segmentPoint; segmentPoint->prev = nullptr; @@ -3469,7 +1770,7 @@ void PPCRecompilerIml_setSegmentPoint(ppcRecompilerSegmentPoint_t* segmentPoint, imlSegment->segmentPointList = segmentPoint; } -void PPCRecompilerIml_removeSegmentPoint(ppcRecompilerSegmentPoint_t* segmentPoint) +void PPCRecompilerIml_removeSegmentPoint(IMLSegmentPoint* segmentPoint) { if (segmentPoint->prev) segmentPoint->prev->next = segmentPoint->next; @@ -3481,147 +1782,60 @@ void PPCRecompilerIml_removeSegmentPoint(ppcRecompilerSegmentPoint_t* segmentPoi /* * Insert multiple no-op instructions -* Warning: Can invalidate any previous instruction structs from the same segment +* Warning: Can invalidate any previous instruction pointers from the same segment */ -void PPCRecompiler_pushBackIMLInstructions(PPCRecImlSegment_t* imlSegment, sint32 index, sint32 shiftBackCount) +void PPCRecompiler_pushBackIMLInstructions(IMLSegment* imlSegment, sint32 index, sint32 shiftBackCount) { - cemu_assert(index >= 0 && index <= imlSegment->imlListCount); + cemu_assert_debug(index >= 0 && index <= imlSegment->imlList.size()); + + imlSegment->imlList.insert(imlSegment->imlList.begin() + index, shiftBackCount, {}); + + memset(imlSegment->imlList.data() + index, 0, sizeof(IMLInstruction) * shiftBackCount); - if (imlSegment->imlListCount + shiftBackCount > imlSegment->imlListSize) - { - sint32 newSize = imlSegment->imlListCount + shiftBackCount + std::max(2, imlSegment->imlListSize/2); - imlSegment->imlList = (PPCRecImlInstruction_t*)realloc(imlSegment->imlList, sizeof(PPCRecImlInstruction_t)*newSize); - imlSegment->imlListSize = newSize; - } - for (sint32 i = (sint32)imlSegment->imlListCount - 1; i >= index; i--) - { - memcpy(imlSegment->imlList + (i + shiftBackCount), imlSegment->imlList + i, sizeof(PPCRecImlInstruction_t)); - } // fill empty space with NOP instructions for (sint32 i = 0; i < shiftBackCount; i++) { imlSegment->imlList[index + i].type = PPCREC_IML_TYPE_NONE; } - imlSegment->imlListCount += shiftBackCount; + // update position of segment points if (imlSegment->segmentPointList) { - ppcRecompilerSegmentPoint_t* segmentPoint = imlSegment->segmentPointList; + IMLSegmentPoint* segmentPoint = imlSegment->segmentPointList; while (segmentPoint) { - if (segmentPoint->index != RA_INTER_RANGE_START && segmentPoint->index != RA_INTER_RANGE_END) - { - if (segmentPoint->index >= index) - segmentPoint->index += shiftBackCount; - } - // next + segmentPoint->ShiftIfAfter(index, shiftBackCount); segmentPoint = segmentPoint->next; } } } -/* -* Insert and return new instruction at index -* Warning: Can invalidate any previous instruction structs from the same segment -*/ -PPCRecImlInstruction_t* PPCRecompiler_insertInstruction(PPCRecImlSegment_t* imlSegment, sint32 index) +IMLInstruction* PPCRecompiler_insertInstruction(IMLSegment* imlSegment, sint32 index) { PPCRecompiler_pushBackIMLInstructions(imlSegment, index, 1); - return imlSegment->imlList + index; + return imlSegment->imlList.data() + index; } -/* -* Append and return new instruction at the end of the segment -* Warning: Can invalidate any previous instruction structs from the same segment -*/ -PPCRecImlInstruction_t* PPCRecompiler_appendInstruction(PPCRecImlSegment_t* imlSegment) +IMLInstruction* PPCRecompiler_appendInstruction(IMLSegment* imlSegment) { - sint32 index = imlSegment->imlListCount; - if (index >= imlSegment->imlListSize) - { - sint32 newSize = index+1; - imlSegment->imlList = (PPCRecImlInstruction_t*)realloc(imlSegment->imlList, sizeof(PPCRecImlInstruction_t)*newSize); - imlSegment->imlListSize = newSize; - } - imlSegment->imlListCount++; - memset(imlSegment->imlList + index, 0, sizeof(PPCRecImlInstruction_t)); - return imlSegment->imlList + index; + size_t index = imlSegment->imlList.size(); + imlSegment->imlList.emplace_back(); + memset(imlSegment->imlList.data() + index, 0, sizeof(IMLInstruction)); + return imlSegment->imlList.data() + index; +} + +IMLSegment* PPCRecompilerIml_appendSegment(ppcImlGenContext_t* ppcImlGenContext) +{ + IMLSegment* segment = new IMLSegment(); + ppcImlGenContext->segmentList2.emplace_back(segment); + return segment; } void PPCRecompilerIml_insertSegments(ppcImlGenContext_t* ppcImlGenContext, sint32 index, sint32 count) { - if( (ppcImlGenContext->segmentListCount+count) > ppcImlGenContext->segmentListSize ) - { - // allocate space for more segments - ppcImlGenContext->segmentListSize += count; - ppcImlGenContext->segmentList = (PPCRecImlSegment_t**)realloc(ppcImlGenContext->segmentList, ppcImlGenContext->segmentListSize*sizeof(PPCRecImlSegment_t*)); - } - for(sint32 i=(sint32)ppcImlGenContext->segmentListCount-1; i>=index; i--) - { - memcpy(ppcImlGenContext->segmentList+(i+count), ppcImlGenContext->segmentList+i, sizeof(PPCRecImlSegment_t*)); - } - ppcImlGenContext->segmentListCount += count; - for(sint32 i=0; i<count; i++) - { - //memset(ppcImlGenContext->segmentList+index+i, 0x00, sizeof(PPCRecImlSegment_t*)); - ppcImlGenContext->segmentList[index+i] = (PPCRecImlSegment_t*)malloc(sizeof(PPCRecImlSegment_t)); - memset(ppcImlGenContext->segmentList[index+i], 0x00, sizeof(PPCRecImlSegment_t)); - ppcImlGenContext->segmentList[index + i]->list_prevSegments = std::vector<PPCRecImlSegment_t*>(); - } -} - -/* - * Allocate and init a new iml instruction segment - */ -PPCRecImlSegment_t* PPCRecompiler_generateImlSegment(ppcImlGenContext_t* ppcImlGenContext) -{ - if( ppcImlGenContext->segmentListCount >= ppcImlGenContext->segmentListSize ) - { - // allocate space for more segments - ppcImlGenContext->segmentListSize *= 2; - ppcImlGenContext->segmentList = (PPCRecImlSegment_t**)realloc(ppcImlGenContext->segmentList, ppcImlGenContext->segmentListSize*sizeof(PPCRecImlSegment_t*)); - } - PPCRecImlSegment_t* ppcRecSegment = new PPCRecImlSegment_t(); - ppcImlGenContext->segmentList[ppcImlGenContext->segmentListCount] = ppcRecSegment; - ppcImlGenContext->segmentListCount++; - return ppcRecSegment; -} - -void PPCRecompiler_freeContext(ppcImlGenContext_t* ppcImlGenContext) -{ - if (ppcImlGenContext->imlList) - { - free(ppcImlGenContext->imlList); - ppcImlGenContext->imlList = nullptr; - } - for(sint32 i=0; i<ppcImlGenContext->segmentListCount; i++) - { - free(ppcImlGenContext->segmentList[i]->imlList); - delete ppcImlGenContext->segmentList[i]; - } - ppcImlGenContext->segmentListCount = 0; - if (ppcImlGenContext->segmentList) - { - free(ppcImlGenContext->segmentList); - ppcImlGenContext->segmentList = nullptr; - } -} - -bool PPCRecompiler_isSuffixInstruction(PPCRecImlInstruction_t* iml) -{ - if (iml->type == PPCREC_IML_TYPE_MACRO && (iml->operation == PPCREC_IML_MACRO_BLR || iml->operation == PPCREC_IML_MACRO_BCTR) || - iml->type == PPCREC_IML_TYPE_MACRO && iml->operation == PPCREC_IML_MACRO_BL || - iml->type == PPCREC_IML_TYPE_MACRO && iml->operation == PPCREC_IML_MACRO_B_FAR || - iml->type == PPCREC_IML_TYPE_MACRO && iml->operation == PPCREC_IML_MACRO_BLRL || - iml->type == PPCREC_IML_TYPE_MACRO && iml->operation == PPCREC_IML_MACRO_BCTRL || - iml->type == PPCREC_IML_TYPE_MACRO && iml->operation == PPCREC_IML_MACRO_LEAVE || - iml->type == PPCREC_IML_TYPE_MACRO && iml->operation == PPCREC_IML_MACRO_HLE || - iml->type == PPCREC_IML_TYPE_MACRO && iml->operation == PPCREC_IML_MACRO_MFTB || - iml->type == PPCREC_IML_TYPE_PPC_ENTER || - iml->type == PPCREC_IML_TYPE_CJUMP || - iml->type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK) - return true; - return false; + ppcImlGenContext->segmentList2.insert(ppcImlGenContext->segmentList2.begin() + index, count, nullptr); + for (sint32 i = 0; i < count; i++) + ppcImlGenContext->segmentList2[index + i] = new IMLSegment(); } bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) @@ -3643,15 +1857,18 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) switch (PPC_getBits(opcode, 25, 5)) { case 0: - PPCRecompilerImlGen_PS_CMPU0(ppcImlGenContext, opcode); + if( !PPCRecompilerImlGen_PS_CMPU0(ppcImlGenContext, opcode) ) + unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 1: - PPCRecompilerImlGen_PS_CMPO0(ppcImlGenContext, opcode); + if( !PPCRecompilerImlGen_PS_CMPO0(ppcImlGenContext, opcode) ) + unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 2: - PPCRecompilerImlGen_PS_CMPU1(ppcImlGenContext, opcode); + if( !PPCRecompilerImlGen_PS_CMPU1(ppcImlGenContext, opcode) ) + unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; default: @@ -3804,20 +2021,23 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) PPCRecompilerImlGen_MULLI(ppcImlGenContext, opcode); break; case 8: // SUBFIC - PPCRecompilerImlGen_SUBFIC(ppcImlGenContext, opcode); + if (!PPCRecompilerImlGen_SUBFIC(ppcImlGenContext, opcode)) + unsupportedInstructionFound = true; break; case 10: // CMPLI - PPCRecompilerImlGen_CMPLI(ppcImlGenContext, opcode); + if (!PPCRecompilerImlGen_CMPI(ppcImlGenContext, opcode, true)) + unsupportedInstructionFound = true; break; case 11: // CMPI - PPCRecompilerImlGen_CMPI(ppcImlGenContext, opcode); + if (!PPCRecompilerImlGen_CMPI(ppcImlGenContext, opcode, false)) + unsupportedInstructionFound = true; break; case 12: // ADDIC - if (PPCRecompilerImlGen_ADDIC(ppcImlGenContext, opcode) == false) + if (PPCRecompilerImlGen_ADDIC_(ppcImlGenContext, opcode, false) == false) unsupportedInstructionFound = true; break; case 13: // ADDIC. - if (PPCRecompilerImlGen_ADDIC_(ppcImlGenContext, opcode) == false) + if (PPCRecompilerImlGen_ADDIC_(ppcImlGenContext, opcode, true) == false) unsupportedInstructionFound = true; break; case 14: // ADDI @@ -3849,8 +2069,11 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) case 19: // opcode category 19 switch (PPC_getBits(opcode, 30, 10)) { - case 16: - if (PPCRecompilerImlGen_BCLR(ppcImlGenContext, opcode) == false) + case 0: + PPCRecompilerImlGen_MCRF(ppcImlGenContext, opcode); + break; + case 16: // BCLR + if (PPCRecompilerImlGen_BCSPR(ppcImlGenContext, opcode, SPR_LR) == false) unsupportedInstructionFound = true; break; case 129: @@ -3881,8 +2104,8 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) if (PPCRecompilerImlGen_CROR(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; - case 528: - if (PPCRecompilerImlGen_BCCTR(ppcImlGenContext, opcode) == false) + case 528: // BCCTR + if (PPCRecompilerImlGen_BCSPR(ppcImlGenContext, opcode, SPR_CTR) == false) unsupportedInstructionFound = true; break; default: @@ -3902,37 +2125,34 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) if (PPCRecompilerImlGen_RLWNM(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; - case 24: - PPCRecompilerImlGen_ORI(ppcImlGenContext, opcode); + case 24: // ORI + PPCRecompilerImlGen_ORI_ORIS(ppcImlGenContext, opcode, false); break; - case 25: - PPCRecompilerImlGen_ORIS(ppcImlGenContext, opcode); + case 25: // ORIS + PPCRecompilerImlGen_ORI_ORIS(ppcImlGenContext, opcode, true); break; - case 26: - PPCRecompilerImlGen_XORI(ppcImlGenContext, opcode); + case 26: // XORI + PPCRecompilerImlGen_XORI_XORIS(ppcImlGenContext, opcode, false); break; - case 27: - PPCRecompilerImlGen_XORIS(ppcImlGenContext, opcode); + case 27: // XORIS + PPCRecompilerImlGen_XORI_XORIS(ppcImlGenContext, opcode, true); break; - case 28: - PPCRecompilerImlGen_ANDI(ppcImlGenContext, opcode); + case 28: // ANDI + PPCRecompilerImlGen_ANDI_ANDIS(ppcImlGenContext, opcode, false); break; - case 29: - PPCRecompilerImlGen_ANDIS(ppcImlGenContext, opcode); + case 29: // ANDIS + PPCRecompilerImlGen_ANDI_ANDIS(ppcImlGenContext, opcode, true); break; case 31: // opcode category switch (PPC_getBits(opcode, 30, 10)) { case 0: - PPCRecompilerImlGen_CMP(ppcImlGenContext, opcode); + PPCRecompilerImlGen_CMP(ppcImlGenContext, opcode, false); break; case 4: PPCRecompilerImlGen_TW(ppcImlGenContext, opcode); break; case 8: - // todo: Check if we can optimize this pattern: - // SUBFC + SUBFE - // SUBFC if (PPCRecompilerImlGen_SUBFC(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; @@ -3952,9 +2172,8 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) if (PPCRecompilerImlGen_LWARX(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; - case 23: - if (PPCRecompilerImlGen_LWZX(ppcImlGenContext, opcode) == false) - unsupportedInstructionFound = true; + case 23: // LWZX + PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 32, false, true, false); break; case 24: if (PPCRecompilerImlGen_SLW(ppcImlGenContext, opcode) == false) @@ -3964,12 +2183,12 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) if (PPCRecompilerImlGen_CNTLZW(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; - case 28: - if (PPCRecompilerImlGen_AND(ppcImlGenContext, opcode) == false) + case 28: // AND + if (!PPCRecompilerImlGen_AND_NAND(ppcImlGenContext, opcode, false)) unsupportedInstructionFound = true; break; case 32: - PPCRecompilerImlGen_CMPL(ppcImlGenContext, opcode); + PPCRecompilerImlGen_CMP(ppcImlGenContext, opcode, true); // CMPL break; case 40: if (PPCRecompilerImlGen_SUBF(ppcImlGenContext, opcode) == false) @@ -3978,12 +2197,11 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) case 54: // DBCST - Generates no code break; - case 55: - if (PPCRecompilerImlGen_LWZUX(ppcImlGenContext, opcode) == false) - unsupportedInstructionFound = true; + case 55: // LWZUX + PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 32, false, true, true); break; - case 60: - if (PPCRecompilerImlGen_ANDC(ppcImlGenContext, opcode) == false) + case 60: // ANDC + if (!PPCRecompilerImlGen_ANDC(ppcImlGenContext, opcode)) unsupportedInstructionFound = true; break; case 75: @@ -3993,20 +2211,18 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) case 86: // DCBF -> No-Op break; - case 87: - if (PPCRecompilerImlGen_LBZX(ppcImlGenContext, opcode) == false) - unsupportedInstructionFound = true; + case 87: // LBZX + PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 8, false, true, false); break; case 104: if (PPCRecompilerImlGen_NEG(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; - case 119: - if (PPCRecompilerImlGen_LBZUX(ppcImlGenContext, opcode) == false) - unsupportedInstructionFound = true; + case 119: // LBZUX + PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 8, false, true, true); break; - case 124: - if (PPCRecompilerImlGen_NOR(ppcImlGenContext, opcode) == false) + case 124: // NOR + if (!PPCRecompilerImlGen_OR_NOR(ppcImlGenContext, opcode, true)) unsupportedInstructionFound = true; break; case 136: @@ -4018,19 +2234,20 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) unsupportedInstructionFound = true; break; case 144: - PPCRecompilerImlGen_MTCRF(ppcImlGenContext, opcode); + if( !PPCRecompilerImlGen_MTCRF(ppcImlGenContext, opcode)) + unsupportedInstructionFound = true; break; case 150: - if (PPCRecompilerImlGen_STWCX(ppcImlGenContext, opcode) == false) + if (!PPCRecompilerImlGen_STWCX(ppcImlGenContext, opcode)) unsupportedInstructionFound = true; break; - case 151: - if (PPCRecompilerImlGen_STORE_INDEXED(ppcImlGenContext, opcode, 32) == false) + case 151: // STWX + if (!PPCRecompilerImlGen_STORE_INDEXED(ppcImlGenContext, opcode, 32, true, false)) unsupportedInstructionFound = true; break; - case 183: - if (PPCRecompilerImlGen_STORE_INDEXED_UPDATE(ppcImlGenContext, opcode, 32) == false) - unsupportedInstructionFound = true; + case 183: // STWUX + if (!PPCRecompilerImlGen_STORE_INDEXED(ppcImlGenContext, opcode, 32, true, true)) + unsupportedInstructionFound = true; break; case 200: if (PPCRecompilerImlGen_SUBFZE(ppcImlGenContext, opcode) == false) @@ -4040,8 +2257,8 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) if (PPCRecompilerImlGen_ADDZE(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; - case 215: - if (PPCRecompilerImlGen_STORE_INDEXED(ppcImlGenContext, opcode, 8) == false) + case 215: // STBX + if (!PPCRecompilerImlGen_STORE_INDEXED(ppcImlGenContext, opcode, 8, true, false)) unsupportedInstructionFound = true; break; case 234: @@ -4052,59 +2269,56 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) if (PPCRecompilerImlGen_MULLW(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; - case 247: - if (PPCRecompilerImlGen_STORE_INDEXED_UPDATE(ppcImlGenContext, opcode, 8) == false) + case 247: // STBUX + if (!PPCRecompilerImlGen_STORE_INDEXED(ppcImlGenContext, opcode, 8, true, true)) unsupportedInstructionFound = true; break; case 266: if (PPCRecompilerImlGen_ADD(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; - case 279: - if (PPCRecompilerImlGen_LHZX(ppcImlGenContext, opcode) == false) + case 279: // LHZX + PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 16, false, true, false); + break; + case 284: // EQV (alias to NXOR) + if (!PPCRecompilerImlGen_XOR(ppcImlGenContext, opcode, true)) unsupportedInstructionFound = true; break; - case 284: - PPCRecompilerImlGen_EQV(ppcImlGenContext, opcode); + case 311: // LHZUX + PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 16, false, true, true); break; - case 311: - if (PPCRecompilerImlGen_LHZUX(ppcImlGenContext, opcode) == false) - unsupportedInstructionFound = true; - break; - case 316: - if (PPCRecompilerImlGen_XOR(ppcImlGenContext, opcode) == false) + case 316: // XOR + if (!PPCRecompilerImlGen_XOR(ppcImlGenContext, opcode, false)) unsupportedInstructionFound = true; break; case 339: if (PPCRecompilerImlGen_MFSPR(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; - case 343: - if (PPCRecompilerImlGen_LHAX(ppcImlGenContext, opcode) == false) - unsupportedInstructionFound = true; + case 343: // LHAX + PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 16, true, true, false); break; case 371: if (PPCRecompilerImlGen_MFTB(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; - case 375: - if (PPCRecompilerImlGen_LHAUX(ppcImlGenContext, opcode) == false) - unsupportedInstructionFound = true; + case 375: // LHAUX + PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 16, true, true, true); break; - case 407: - if (PPCRecompilerImlGen_STORE_INDEXED(ppcImlGenContext, opcode, 16) == false) + case 407: // STHX + if (!PPCRecompilerImlGen_STORE_INDEXED(ppcImlGenContext, opcode, 16, true, false)) unsupportedInstructionFound = true; break; case 412: if (PPCRecompilerImlGen_ORC(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; - case 439: - if (PPCRecompilerImlGen_STORE_INDEXED_UPDATE(ppcImlGenContext, opcode, 16) == false) + case 439: // STHUX + if (!PPCRecompilerImlGen_STORE_INDEXED(ppcImlGenContext, opcode, 16, true, true)) unsupportedInstructionFound = true; break; - case 444: - if (PPCRecompilerImlGen_OR(ppcImlGenContext, opcode) == false) + case 444: // OR + if (!PPCRecompilerImlGen_OR_NOR(ppcImlGenContext, opcode, false)) unsupportedInstructionFound = true; break; case 459: @@ -4114,14 +2328,16 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) if (PPCRecompilerImlGen_MTSPR(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; + case 476: // NAND + if (!PPCRecompilerImlGen_AND_NAND(ppcImlGenContext, opcode, true)) + unsupportedInstructionFound = true; + break; case 491: if (PPCRecompilerImlGen_DIVW(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; - case 534: - if (PPCRecompilerImlGen_LWBRX(ppcImlGenContext, opcode) == false) - unsupportedInstructionFound = true; - ppcImlGenContext->hasFPUInstruction = true; + case 534: // LWBRX + PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 32, false, false, false); break; case 535: if (PPCRecompilerImlGen_LFSX(ppcImlGenContext, opcode) == false) @@ -4154,8 +2370,8 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; - case 662: - if (PPCRecompilerImlGen_STWBRX(ppcImlGenContext, opcode) == false) + case 662: // STWBRX + if (!PPCRecompilerImlGen_STORE_INDEXED(ppcImlGenContext, opcode, 32, false, false)) unsupportedInstructionFound = true; break; case 663: @@ -4174,8 +2390,8 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) if (PPCRecompilerImlGen_STFDX(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; - case 790: - PPCRecompilerImlGen_LHBRX(ppcImlGenContext, opcode); + case 790: // LHBRX + PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 16, false, false, false); break; case 792: if (PPCRecompilerImlGen_SRAW(ppcImlGenContext, opcode) == false) @@ -4186,7 +2402,7 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) unsupportedInstructionFound = true; break; case 918: // STHBRX - if (PPCRecompilerImlGen_STORE_INDEXED(ppcImlGenContext, opcode, 16, true) == false) + if (!PPCRecompilerImlGen_STORE_INDEXED(ppcImlGenContext, opcode, 16, false, true)) unsupportedInstructionFound = true; break; case 922: @@ -4210,47 +2426,61 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) break; } break; - case 32: - PPCRecompilerImlGen_LWZ(ppcImlGenContext, opcode); + case 32: // LWZ + if(!PPCRecompilerImlGen_LOAD(ppcImlGenContext, opcode, 32, false, true, false)) + unsupportedInstructionFound = true; break; - case 33: - PPCRecompilerImlGen_LWZU(ppcImlGenContext, opcode); + case 33: // LWZU + if (!PPCRecompilerImlGen_LOAD(ppcImlGenContext, opcode, 32, false, true, true)) + unsupportedInstructionFound = true; break; - case 34: - PPCRecompilerImlGen_LBZ(ppcImlGenContext, opcode); + case 34: // LBZ + if (!PPCRecompilerImlGen_LOAD(ppcImlGenContext, opcode, 8, false, true, false)) + unsupportedInstructionFound = true; break; - case 35: - PPCRecompilerImlGen_LBZU(ppcImlGenContext, opcode); + case 35: // LBZU + if (!PPCRecompilerImlGen_LOAD(ppcImlGenContext, opcode, 8, false, true, true)) + unsupportedInstructionFound = true; break; - case 36: - PPCRecompilerImlGen_STW(ppcImlGenContext, opcode); + case 36: // STW + if(!PPCRecompilerImlGen_STORE(ppcImlGenContext, opcode, 32, true, false)) + unsupportedInstructionFound = true; break; - case 37: - PPCRecompilerImlGen_STWU(ppcImlGenContext, opcode); + case 37: // STWU + if (!PPCRecompilerImlGen_STORE(ppcImlGenContext, opcode, 32, true, true)) + unsupportedInstructionFound = true; break; - case 38: - PPCRecompilerImlGen_STB(ppcImlGenContext, opcode); + case 38: // STB + if (!PPCRecompilerImlGen_STORE(ppcImlGenContext, opcode, 8, true, false)) + unsupportedInstructionFound = true; break; - case 39: - PPCRecompilerImlGen_STBU(ppcImlGenContext, opcode); + case 39: // STBU + if (!PPCRecompilerImlGen_STORE(ppcImlGenContext, opcode, 8, true, true)) + unsupportedInstructionFound = true; break; - case 40: - PPCRecompilerImlGen_LHZ(ppcImlGenContext, opcode); + case 40: // LHZ + if (!PPCRecompilerImlGen_LOAD(ppcImlGenContext, opcode, 16, false, true, false)) + unsupportedInstructionFound = true; break; - case 41: - PPCRecompilerImlGen_LHZU(ppcImlGenContext, opcode); + case 41: // LHZU + if (!PPCRecompilerImlGen_LOAD(ppcImlGenContext, opcode, 16, false, true, true)) + unsupportedInstructionFound = true; break; - case 42: - PPCRecompilerImlGen_LHA(ppcImlGenContext, opcode); + case 42: // LHA + if (!PPCRecompilerImlGen_LOAD(ppcImlGenContext, opcode, 16, true, true, false)) + unsupportedInstructionFound = true; break; - case 43: - PPCRecompilerImlGen_LHAU(ppcImlGenContext, opcode); + case 43: // LHAU + if (!PPCRecompilerImlGen_LOAD(ppcImlGenContext, opcode, 16, true, true, true)) + unsupportedInstructionFound = true; break; - case 44: - PPCRecompilerImlGen_STH(ppcImlGenContext, opcode); + case 44: // STH + if (!PPCRecompilerImlGen_STORE(ppcImlGenContext, opcode, 16, true, false)) + unsupportedInstructionFound = true; break; - case 45: - PPCRecompilerImlGen_STHU(ppcImlGenContext, opcode); + case 45: // STHU + if (!PPCRecompilerImlGen_STORE(ppcImlGenContext, opcode, 16, true, true)) + unsupportedInstructionFound = true; break; case 46: PPCRecompilerImlGen_LMW(ppcImlGenContext, opcode); @@ -4471,556 +2701,483 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) return unsupportedInstructionFound; } -bool PPCRecompiler_generateIntermediateCode(ppcImlGenContext_t& ppcImlGenContext, PPCRecFunction_t* ppcRecFunc, std::set<uint32>& entryAddresses) +// returns false if code flow is not interrupted +// continueDefaultPath: Controls if +bool PPCRecompiler_CheckIfInstructionEndsSegment(PPCFunctionBoundaryTracker& boundaryTracker, uint32 instructionAddress, uint32 opcode, bool& makeNextInstEnterable, bool& continueDefaultPath, bool& hasBranchTarget, uint32& branchTarget) { - //ppcImlGenContext_t ppcImlGenContext = { 0 }; - ppcImlGenContext.functionRef = ppcRecFunc; - // add entire range + hasBranchTarget = false; + branchTarget = 0xFFFFFFFF; + makeNextInstEnterable = false; + continueDefaultPath = false; + switch (Espresso::GetPrimaryOpcode(opcode)) + { + case Espresso::PrimaryOpcode::VIRTUAL_HLE: + { + makeNextInstEnterable = true; + hasBranchTarget = false; + continueDefaultPath = false; + return true; + } + case Espresso::PrimaryOpcode::BC: + { + uint32 BD, BI; + Espresso::BOField BO; + bool AA, LK; + Espresso::decodeOp_BC(opcode, BD, BO, BI, AA, LK); + if (!LK) + { + hasBranchTarget = true; + branchTarget = (AA ? BD : BD) + instructionAddress; + if (!boundaryTracker.ContainsAddress(branchTarget)) + hasBranchTarget = false; // far jump + } + makeNextInstEnterable = LK; + continueDefaultPath = true; + return true; + } + case Espresso::PrimaryOpcode::B: + { + uint32 LI; + bool AA, LK; + Espresso::decodeOp_B(opcode, LI, AA, LK); + if (!LK) + { + hasBranchTarget = true; + branchTarget = AA ? LI : LI + instructionAddress; + if (!boundaryTracker.ContainsAddress(branchTarget)) + hasBranchTarget = false; // far jump + } + makeNextInstEnterable = LK; + continueDefaultPath = false; + return true; + } + case Espresso::PrimaryOpcode::GROUP_19: + switch (Espresso::GetGroup19Opcode(opcode)) + { + case Espresso::Opcode19::BCLR: + case Espresso::Opcode19::BCCTR: + { + Espresso::BOField BO; + uint32 BI; + bool LK; + Espresso::decodeOp_BCSPR(opcode, BO, BI, LK); + continueDefaultPath = !BO.conditionIgnore() || !BO.decrementerIgnore(); // if branch is always taken then there is no continued path + makeNextInstEnterable = Espresso::DecodeLK(opcode); + return true; + } + default: + break; + } + break; + case Espresso::PrimaryOpcode::GROUP_31: + switch (Espresso::GetGroup31Opcode(opcode)) + { + default: + break; + } + break; + default: + break; + } + return false; +} + +void PPCRecompiler_DetermineBasicBlockRange(std::vector<PPCBasicBlockInfo>& basicBlockList, PPCFunctionBoundaryTracker& boundaryTracker, uint32 ppcStart, uint32 ppcEnd, const std::set<uint32>& combinedBranchTargets, const std::set<uint32>& entryAddresses) +{ + cemu_assert_debug(ppcStart <= ppcEnd); + + uint32 currentAddr = ppcStart; + + PPCBasicBlockInfo* curBlockInfo = &basicBlockList.emplace_back(currentAddr, entryAddresses); + + uint32 basicBlockStart = currentAddr; + while (currentAddr <= ppcEnd) + { + curBlockInfo->lastAddress = currentAddr; + uint32 opcode = memory_readU32(currentAddr); + bool nextInstIsEnterable = false; + bool hasBranchTarget = false; + bool hasContinuedFlow = false; + uint32 branchTarget = 0; + if (PPCRecompiler_CheckIfInstructionEndsSegment(boundaryTracker, currentAddr, opcode, nextInstIsEnterable, hasContinuedFlow, hasBranchTarget, branchTarget)) + { + curBlockInfo->hasBranchTarget = hasBranchTarget; + curBlockInfo->branchTarget = branchTarget; + curBlockInfo->hasContinuedFlow = hasContinuedFlow; + // start new basic block, except if this is the last instruction + if (currentAddr >= ppcEnd) + break; + curBlockInfo = &basicBlockList.emplace_back(currentAddr + 4, entryAddresses); + curBlockInfo->isEnterable = curBlockInfo->isEnterable || nextInstIsEnterable; + currentAddr += 4; + continue; + } + currentAddr += 4; + if (currentAddr <= ppcEnd) + { + if (combinedBranchTargets.find(currentAddr) != combinedBranchTargets.end()) + { + // instruction is branch target, start new basic block + curBlockInfo = &basicBlockList.emplace_back(currentAddr, entryAddresses); + } + } + + } +} + +std::vector<PPCBasicBlockInfo> PPCRecompiler_DetermineBasicBlockRange(PPCFunctionBoundaryTracker& boundaryTracker, const std::set<uint32>& entryAddresses) +{ + cemu_assert(!entryAddresses.empty()); + std::vector<PPCBasicBlockInfo> basicBlockList; + + const std::set<uint32> branchTargets = boundaryTracker.GetBranchTargets(); + auto funcRanges = boundaryTracker.GetRanges(); + + std::set<uint32> combinedBranchTargets = branchTargets; + combinedBranchTargets.insert(entryAddresses.begin(), entryAddresses.end()); + + for (auto& funcRangeIt : funcRanges) + PPCRecompiler_DetermineBasicBlockRange(basicBlockList, boundaryTracker, funcRangeIt.startAddress, funcRangeIt.startAddress + funcRangeIt.length - 4, combinedBranchTargets, entryAddresses); + + // mark all segments that start at entryAddresses as enterable (debug code for verification, can be removed) + size_t numMarkedEnterable = 0; + for (auto& basicBlockIt : basicBlockList) + { + if (entryAddresses.find(basicBlockIt.startAddress) != entryAddresses.end()) + { + cemu_assert_debug(basicBlockIt.isEnterable); + numMarkedEnterable++; + } + } + cemu_assert_debug(numMarkedEnterable == entryAddresses.size()); + + // todo - inline BL, currently this is done in the instruction handler of BL but this will mean that instruction cycle increasing is ignored + + return basicBlockList; +} + +bool PPCIMLGen_FillBasicBlock(ppcImlGenContext_t& ppcImlGenContext, PPCBasicBlockInfo& basicBlockInfo) +{ + ppcImlGenContext.currentOutputSegment = basicBlockInfo.GetSegmentForInstructionAppend(); + ppcImlGenContext.currentInstruction = (uint32*)(memory_base + basicBlockInfo.startAddress); + + uint32* firstCurrentInstruction = ppcImlGenContext.currentInstruction; + uint32* endCurrentInstruction = (uint32*)(memory_base + basicBlockInfo.lastAddress); + + while (ppcImlGenContext.currentInstruction <= endCurrentInstruction) + { + uint32 addressOfCurrentInstruction = (uint32)((uint8*)ppcImlGenContext.currentInstruction - memory_base); + ppcImlGenContext.ppcAddressOfCurrentInstruction = addressOfCurrentInstruction; + + if (PPCRecompiler_decodePPCInstruction(&ppcImlGenContext)) + { + cemuLog_logDebug(LogType::Force, "PPCRecompiler: Unsupported instruction at 0x{:08x}", addressOfCurrentInstruction); + ppcImlGenContext.currentOutputSegment = nullptr; + return false; + } + } + ppcImlGenContext.currentOutputSegment = nullptr; + return true; +} + +// returns split segment from which the continued segment is available via seg->GetBranchNotTaken() +IMLSegment* PPCIMLGen_CreateSplitSegmentAtEnd(ppcImlGenContext_t& ppcImlGenContext, PPCBasicBlockInfo& basicBlockInfo) +{ + IMLSegment* writeSegment = basicBlockInfo.GetSegmentForInstructionAppend(); + + IMLSegment* continuedSegment = ppcImlGenContext.InsertSegment(ppcImlGenContext.GetSegmentIndex(writeSegment) + 1); + + continuedSegment->SetLinkBranchTaken(writeSegment->GetBranchTaken()); + continuedSegment->SetLinkBranchNotTaken(writeSegment->GetBranchNotTaken()); + + writeSegment->SetLinkBranchNotTaken(continuedSegment); + writeSegment->SetLinkBranchTaken(nullptr); + + if (ppcImlGenContext.currentOutputSegment == writeSegment) + ppcImlGenContext.currentOutputSegment = continuedSegment; + + cemu_assert_debug(basicBlockInfo.appendSegment == writeSegment); + basicBlockInfo.appendSegment = continuedSegment; + + return writeSegment; +} + +// generates a new segment and sets it as branch target for the current write segment. Returns the created segment +IMLSegment* PPCIMLGen_CreateNewSegmentAsBranchTarget(ppcImlGenContext_t& ppcImlGenContext, PPCBasicBlockInfo& basicBlockInfo) +{ + IMLSegment* writeSegment = basicBlockInfo.GetSegmentForInstructionAppend(); + IMLSegment* branchTargetSegment = ppcImlGenContext.NewSegment(); + cemu_assert_debug(!writeSegment->GetBranchTaken()); // must not have a target already + writeSegment->SetLinkBranchTaken(branchTargetSegment); + return branchTargetSegment; +} + +// verify that current instruction is the last instruction of the active basic block +void PPCIMLGen_AssertIfNotLastSegmentInstruction(ppcImlGenContext_t& ppcImlGenContext) +{ + cemu_assert_debug(ppcImlGenContext.currentBasicBlock->lastAddress == ppcImlGenContext.ppcAddressOfCurrentInstruction); +} + +bool PPCRecompiler_IsBasicBlockATightFiniteLoop(IMLSegment* imlSegment, PPCBasicBlockInfo& basicBlockInfo) +{ + // if we detect a finite loop we can skip generating the cycle check + // currently we only check for BDNZ loops since thats reasonably safe to rely on + // however there are other forms of loops that can be classified as finite, + // but detecting those involves analyzing PPC code and we dont have the infrastructure for that (e.g. IML has CheckRegisterUsage but we dont have an equivalent for PPC code) + + // base criteria, must jump to beginning of same segment + if (imlSegment->nextSegmentBranchTaken != imlSegment) + return false; + + uint32 opcode = *(uint32be*)(memory_base + basicBlockInfo.lastAddress); + if (Espresso::GetPrimaryOpcode(opcode) != Espresso::PrimaryOpcode::BC) + return false; + uint32 BO, BI, BD; + PPC_OPC_TEMPL_B(opcode, BO, BI, BD); + Espresso::BOField boField(BO); + if(!boField.conditionIgnore() || boField.branchAlways()) + return false; + if(boField.decrementerIgnore()) + return false; + return true; +} + +void PPCRecompiler_HandleCycleCheckCount(ppcImlGenContext_t& ppcImlGenContext, PPCBasicBlockInfo& basicBlockInfo) +{ + IMLSegment* imlSegment = basicBlockInfo.GetFirstSegmentInChain(); + if (!basicBlockInfo.hasBranchTarget) + return; + if (basicBlockInfo.branchTarget > basicBlockInfo.startAddress) + return; + + if (PPCRecompiler_IsBasicBlockATightFiniteLoop(imlSegment, basicBlockInfo)) + return; + + // make the segment enterable so execution can return after passing a check + basicBlockInfo.GetFirstSegmentInChain()->SetEnterable(basicBlockInfo.startAddress); + + IMLSegment* splitSeg = PPCIMLGen_CreateSplitSegmentAtEnd(ppcImlGenContext, basicBlockInfo); + splitSeg->AppendInstruction()->make_cjump_cycle_check(); + + IMLSegment* exitSegment = ppcImlGenContext.NewSegment(); + splitSeg->SetLinkBranchTaken(exitSegment); + + exitSegment->AppendInstruction()->make_macro(PPCREC_IML_MACRO_LEAVE, basicBlockInfo.startAddress, 0, 0, IMLREG_INVALID); + + cemu_assert_debug(splitSeg->nextSegmentBranchNotTaken); + // let the IML optimizer and RA know that the original segment should be used during analysis for dead code elimination + exitSegment->SetNextSegmentForOverwriteHints(splitSeg->nextSegmentBranchNotTaken); +} + +void PPCRecompiler_SetSegmentsUncertainFlow(ppcImlGenContext_t& ppcImlGenContext) +{ + for (IMLSegment* segIt : ppcImlGenContext.segmentList2) + { + // handle empty segment + if (segIt->imlList.empty()) + { + cemu_assert_debug(segIt->GetBranchNotTaken()); + continue; + } + // check last instruction of segment + IMLInstruction* imlInstruction = segIt->GetLastInstruction(); + if (imlInstruction->type == PPCREC_IML_TYPE_MACRO) + { + auto macroType = imlInstruction->operation; + switch (macroType) + { + case PPCREC_IML_MACRO_B_TO_REG: + case PPCREC_IML_MACRO_BL: + case PPCREC_IML_MACRO_B_FAR: + case PPCREC_IML_MACRO_HLE: + case PPCREC_IML_MACRO_LEAVE: + segIt->nextSegmentIsUncertain = true; + break; + case PPCREC_IML_MACRO_DEBUGBREAK: + case PPCREC_IML_MACRO_COUNT_CYCLES: + break; + default: + cemu_assert_unimplemented(); + } + } + } +} + +bool PPCRecompiler_GenerateIML(ppcImlGenContext_t& ppcImlGenContext, PPCFunctionBoundaryTracker& boundaryTracker, std::set<uint32>& entryAddresses) +{ + std::vector<PPCBasicBlockInfo> basicBlockList = PPCRecompiler_DetermineBasicBlockRange(boundaryTracker, entryAddresses); + + // create segments + std::unordered_map<uint32, PPCBasicBlockInfo*> addrToBB; + ppcImlGenContext.segmentList2.resize(basicBlockList.size()); + for (size_t i = 0; i < basicBlockList.size(); i++) + { + PPCBasicBlockInfo& basicBlockInfo = basicBlockList[i]; + IMLSegment* seg = new IMLSegment(); + seg->ppcAddress = basicBlockInfo.startAddress; + if(basicBlockInfo.isEnterable) + seg->SetEnterable(basicBlockInfo.startAddress); + ppcImlGenContext.segmentList2[i] = seg; + cemu_assert_debug(addrToBB.find(basicBlockInfo.startAddress) == addrToBB.end()); + basicBlockInfo.SetInitialSegment(seg); + addrToBB.emplace(basicBlockInfo.startAddress, &basicBlockInfo); + } + // link segments + for (size_t i = 0; i < basicBlockList.size(); i++) + { + PPCBasicBlockInfo& bbInfo = basicBlockList[i]; + cemu_assert_debug(bbInfo.GetFirstSegmentInChain() == bbInfo.GetSegmentForInstructionAppend()); + IMLSegment* seg = ppcImlGenContext.segmentList2[i]; + if (bbInfo.hasBranchTarget) + { + PPCBasicBlockInfo* targetBB = addrToBB[bbInfo.branchTarget]; + cemu_assert_debug(targetBB); + IMLSegment_SetLinkBranchTaken(seg, targetBB->GetFirstSegmentInChain()); + } + if (bbInfo.hasContinuedFlow) + { + PPCBasicBlockInfo* targetBB = addrToBB[bbInfo.lastAddress + 4]; + if (!targetBB) + { + cemuLog_log(LogType::Recompiler, "Recompiler was unable to link segment [0x{:08x}-0x{:08x}] to 0x{:08x}", bbInfo.startAddress, bbInfo.lastAddress, bbInfo.lastAddress + 4); + return false; + } + cemu_assert_debug(targetBB); + IMLSegment_SetLinkBranchNotTaken(seg, targetBB->GetFirstSegmentInChain()); + } + } + // we assume that all unreachable segments are potentially enterable + // todo - mark them as such + + + // generate cycle counters + // in theory we could generate these as part of FillBasicBlock() but in the future we might use more complex logic to emit fewer operations + for (size_t i = 0; i < basicBlockList.size(); i++) + { + PPCBasicBlockInfo& basicBlockInfo = basicBlockList[i]; + IMLSegment* seg = basicBlockInfo.GetSegmentForInstructionAppend(); + + uint32 ppcInstructionCount = (basicBlockInfo.lastAddress - basicBlockInfo.startAddress + 4) / 4; + cemu_assert_debug(ppcInstructionCount > 0); + + PPCRecompiler_pushBackIMLInstructions(seg, 0, 1); + seg->imlList[0].type = PPCREC_IML_TYPE_MACRO; + seg->imlList[0].operation = PPCREC_IML_MACRO_COUNT_CYCLES; + seg->imlList[0].op_macro.param = ppcInstructionCount; + } + + // generate cycle check instructions + // note: Introduces new segments + for (size_t i = 0; i < basicBlockList.size(); i++) + { + PPCBasicBlockInfo& basicBlockInfo = basicBlockList[i]; + PPCRecompiler_HandleCycleCheckCount(ppcImlGenContext, basicBlockInfo); + } + + // fill in all the basic blocks + // note: This step introduces new segments as is necessary for some instructions + for (size_t i = 0; i < basicBlockList.size(); i++) + { + PPCBasicBlockInfo& basicBlockInfo = basicBlockList[i]; + ppcImlGenContext.currentBasicBlock = &basicBlockInfo; + if (!PPCIMLGen_FillBasicBlock(ppcImlGenContext, basicBlockInfo)) + return false; + ppcImlGenContext.currentBasicBlock = nullptr; + } + + // mark segments with unknown jump destination (e.g. BLR and most macros) + PPCRecompiler_SetSegmentsUncertainFlow(ppcImlGenContext); + + // debug - check segment graph +#ifdef CEMU_DEBUG_ASSERT + //for (size_t i = 0; i < basicBlockList.size(); i++) + //{ + // IMLSegment* seg = ppcImlGenContext.segmentList2[i]; + // if (seg->list_prevSegments.empty()) + // { + // cemu_assert_debug(seg->isEnterable); + // } + //} + // debug - check if suffix instructions are at the end of segments and if they are present for branching segments + for (size_t segIndex = 0; segIndex < ppcImlGenContext.segmentList2.size(); segIndex++) + { + IMLSegment* seg = ppcImlGenContext.segmentList2[segIndex]; + IMLSegment* nextSeg = (segIndex+1) < ppcImlGenContext.segmentList2.size() ? ppcImlGenContext.segmentList2[segIndex + 1] : nullptr; + + if (seg->imlList.size() > 0) + { + for (size_t f = 0; f < seg->imlList.size() - 1; f++) + { + if (seg->imlList[f].IsSuffixInstruction()) + { + debug_printf("---------------- SegmentDump (Suffix instruction at wrong pos in segment 0x%x):\n", (int)segIndex); + IMLDebug_Dump(&ppcImlGenContext); + DEBUG_BREAK; + } + } + } + if (seg->nextSegmentBranchTaken) + { + if (!seg->HasSuffixInstruction()) + { + debug_printf("---------------- SegmentDump (NoSuffixInstruction in segment 0x%x):\n", (int)segIndex); + IMLDebug_Dump(&ppcImlGenContext); + DEBUG_BREAK; + } + } + if (seg->nextSegmentBranchNotTaken) + { + // if branch not taken, flow must continue to next segment in sequence + cemu_assert_debug(seg->nextSegmentBranchNotTaken == nextSeg); + } + // more detailed checks based on actual suffix instruction + if (seg->imlList.size() > 0) + { + IMLInstruction* inst = seg->GetLastInstruction(); + if (inst->type == PPCREC_IML_TYPE_MACRO && inst->op_macro.param == PPCREC_IML_MACRO_B_FAR) + { + cemu_assert_debug(!seg->GetBranchTaken()); + cemu_assert_debug(!seg->GetBranchNotTaken()); + } + if (inst->type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK) + { + cemu_assert_debug(seg->GetBranchTaken()); + cemu_assert_debug(seg->GetBranchNotTaken()); + } + if (inst->type == PPCREC_IML_TYPE_CONDITIONAL_JUMP) + { + if (!seg->GetBranchTaken() || !seg->GetBranchNotTaken()) + { + debug_printf("---------------- SegmentDump (Missing branch for conditional jump in segment 0x%x):\n", (int)segIndex); + IMLDebug_Dump(&ppcImlGenContext); + cemu_assert_error(); + } + } + } + segIndex++; + } +#endif + + + // todos: + // - basic block determination should look for the B(L) B(L) pattern. Or maybe just mark every bb without any input segments as an entry segment + + return true; +} + +bool PPCRecompiler_generateIntermediateCode(ppcImlGenContext_t& ppcImlGenContext, PPCRecFunction_t* ppcRecFunc, std::set<uint32>& entryAddresses, PPCFunctionBoundaryTracker& boundaryTracker) +{ + ppcImlGenContext.boundaryTracker = &boundaryTracker; + if (!PPCRecompiler_GenerateIML(ppcImlGenContext, boundaryTracker, entryAddresses)) + return false; + + // set range + // todo - support non-continuous functions for the range tracking? ppcRecRange_t recRange; recRange.ppcAddress = ppcRecFunc->ppcAddress; recRange.ppcSize = ppcRecFunc->ppcSize; ppcRecFunc->list_ranges.push_back(recRange); - // process ppc instructions - ppcImlGenContext.currentInstruction = (uint32*)memory_getPointerFromVirtualOffset(ppcRecFunc->ppcAddress); - bool unsupportedInstructionFound = false; - sint32 numPPCInstructions = ppcRecFunc->ppcSize/4; - sint32 unsupportedInstructionCount = 0; - uint32 unsupportedInstructionLastOffset = 0; - uint32* firstCurrentInstruction = ppcImlGenContext.currentInstruction; - uint32* endCurrentInstruction = ppcImlGenContext.currentInstruction + numPPCInstructions; + - while(ppcImlGenContext.currentInstruction < endCurrentInstruction) - { - uint32 addressOfCurrentInstruction = (uint32)((uint8*)ppcImlGenContext.currentInstruction - memory_base); - ppcImlGenContext.ppcAddressOfCurrentInstruction = addressOfCurrentInstruction; - ppcImlGenContext.cyclesSinceLastBranch++; - PPCRecompilerImlGen_generateNewInstruction_jumpmark(&ppcImlGenContext, addressOfCurrentInstruction); - - if (entryAddresses.find(addressOfCurrentInstruction) != entryAddresses.end()) - { - // add PPCEnter for addresses that are in entryAddresses - PPCRecompilerImlGen_generateNewInstruction_ppcEnter(&ppcImlGenContext, addressOfCurrentInstruction); - } - else if(ppcImlGenContext.currentInstruction != firstCurrentInstruction) - { - // add PPCEnter mark if code is seemingly unreachable (for example if between two unconditional jump instructions without jump goal) - uint32 opcodeCurrent = PPCRecompiler_getCurrentInstruction(&ppcImlGenContext); - uint32 opcodePrevious = PPCRecompiler_getPreviousInstruction(&ppcImlGenContext); - if( ((opcodePrevious>>26) == 18) && ((opcodeCurrent>>26) == 18) ) - { - // between two B(L) instructions - // todo: for BL only if they are not inlineable - - bool canInlineFunction = false; - if ((opcodePrevious & PPC_OPC_LK) && (opcodePrevious & PPC_OPC_AA) == 0) - { - uint32 li; - PPC_OPC_TEMPL_I(opcodePrevious, li); - sint32 inlineSize = 0; - if (PPCRecompiler_canInlineFunction(li + addressOfCurrentInstruction - 4, &inlineSize)) - canInlineFunction = true; - } - if( canInlineFunction == false && (opcodePrevious & PPC_OPC_LK) == false) - PPCRecompilerImlGen_generateNewInstruction_ppcEnter(&ppcImlGenContext, addressOfCurrentInstruction); - } - if( ((opcodePrevious>>26) == 19) && PPC_getBits(opcodePrevious, 30, 10) == 528 ) - { - uint32 BO, BI, BD; - PPC_OPC_TEMPL_XL(opcodePrevious, BO, BI, BD); - if( (BO & 16) && (opcodePrevious&PPC_OPC_LK) == 0 ) - { - // after unconditional BCTR instruction - PPCRecompilerImlGen_generateNewInstruction_ppcEnter(&ppcImlGenContext, addressOfCurrentInstruction); - } - } - } - - unsupportedInstructionFound = PPCRecompiler_decodePPCInstruction(&ppcImlGenContext); - if( unsupportedInstructionFound ) - { - unsupportedInstructionCount++; - unsupportedInstructionLastOffset = ppcImlGenContext.ppcAddressOfCurrentInstruction; - unsupportedInstructionFound = false; - //break; - } - } - ppcImlGenContext.ppcAddressOfCurrentInstruction = 0; // reset current instruction offset (any future generated IML instruction will be assigned to ppc address 0) - if( unsupportedInstructionCount > 0 || unsupportedInstructionFound ) - { - // could not compile function - debug_printf("Failed recompile due to unknown instruction at 0x%08x\n", unsupportedInstructionLastOffset); - PPCRecompiler_freeContext(&ppcImlGenContext); - return false; - } - // optimize unused jumpmarks away - // first, flag all jumpmarks as unused - std::map<uint32, PPCRecImlInstruction_t*> map_jumpMarks; - for(sint32 i=0; i<ppcImlGenContext.imlListCount; i++) - { - if( ppcImlGenContext.imlList[i].type == PPCREC_IML_TYPE_JUMPMARK ) - { - ppcImlGenContext.imlList[i].op_jumpmark.flags |= PPCREC_IML_OP_FLAG_UNUSED; -#ifdef CEMU_DEBUG_ASSERT - if (map_jumpMarks.find(ppcImlGenContext.imlList[i].op_jumpmark.address) != map_jumpMarks.end()) - assert_dbg(); -#endif - map_jumpMarks.emplace(ppcImlGenContext.imlList[i].op_jumpmark.address, ppcImlGenContext.imlList+i); - } - } - // second, unflag jumpmarks that have at least one reference - for(sint32 i=0; i<ppcImlGenContext.imlListCount; i++) - { - if( ppcImlGenContext.imlList[i].type == PPCREC_IML_TYPE_CJUMP ) - { - uint32 jumpDest = ppcImlGenContext.imlList[i].op_conditionalJump.jumpmarkAddress; - auto jumpMarkIml = map_jumpMarks.find(jumpDest); - if (jumpMarkIml != map_jumpMarks.end()) - jumpMarkIml->second->op_jumpmark.flags &= ~PPCREC_IML_OP_FLAG_UNUSED; - } - } - // lastly, remove jumpmarks that still have the unused flag set - sint32 currentImlIndex = 0; - for(sint32 i=0; i<ppcImlGenContext.imlListCount; i++) - { - if( ppcImlGenContext.imlList[i].type == PPCREC_IML_TYPE_JUMPMARK && (ppcImlGenContext.imlList[i].op_jumpmark.flags&PPCREC_IML_OP_FLAG_UNUSED) ) - { - continue; // skip this instruction - } - // move back instruction - if( currentImlIndex < i ) - { - memcpy(ppcImlGenContext.imlList+currentImlIndex, ppcImlGenContext.imlList+i, sizeof(PPCRecImlInstruction_t)); - } - currentImlIndex++; - } - // fix intermediate instruction count - ppcImlGenContext.imlListCount = currentImlIndex; - // divide iml instructions into segments - // each segment is defined by one or more instructions with no branches or jump destinations in between - // a branch instruction may only be the very last instruction of a segment - ppcImlGenContext.segmentListCount = 0; - ppcImlGenContext.segmentListSize = 2; - ppcImlGenContext.segmentList = (PPCRecImlSegment_t**)malloc(ppcImlGenContext.segmentListSize*sizeof(PPCRecImlSegment_t*)); - sint32 segmentStart = 0; - sint32 segmentImlIndex = 0; - while( segmentImlIndex < ppcImlGenContext.imlListCount ) - { - bool genNewSegment = false; - // segment definition: - // If we encounter a branch instruction -> end of segment after current instruction - // If we encounter a jumpmark -> end of segment before current instruction - // If we encounter ppc_enter -> end of segment before current instruction - if( ppcImlGenContext.imlList[segmentImlIndex].type == PPCREC_IML_TYPE_CJUMP || - (ppcImlGenContext.imlList[segmentImlIndex].type == PPCREC_IML_TYPE_MACRO && (ppcImlGenContext.imlList[segmentImlIndex].operation == PPCREC_IML_MACRO_BLR || ppcImlGenContext.imlList[segmentImlIndex].operation == PPCREC_IML_MACRO_BLRL || ppcImlGenContext.imlList[segmentImlIndex].operation == PPCREC_IML_MACRO_BCTR || ppcImlGenContext.imlList[segmentImlIndex].operation == PPCREC_IML_MACRO_BCTRL)) || - (ppcImlGenContext.imlList[segmentImlIndex].type == PPCREC_IML_TYPE_MACRO && (ppcImlGenContext.imlList[segmentImlIndex].operation == PPCREC_IML_MACRO_BL)) || - (ppcImlGenContext.imlList[segmentImlIndex].type == PPCREC_IML_TYPE_MACRO && (ppcImlGenContext.imlList[segmentImlIndex].operation == PPCREC_IML_MACRO_B_FAR)) || - (ppcImlGenContext.imlList[segmentImlIndex].type == PPCREC_IML_TYPE_MACRO && (ppcImlGenContext.imlList[segmentImlIndex].operation == PPCREC_IML_MACRO_LEAVE)) || - (ppcImlGenContext.imlList[segmentImlIndex].type == PPCREC_IML_TYPE_MACRO && (ppcImlGenContext.imlList[segmentImlIndex].operation == PPCREC_IML_MACRO_HLE)) || - (ppcImlGenContext.imlList[segmentImlIndex].type == PPCREC_IML_TYPE_MACRO && (ppcImlGenContext.imlList[segmentImlIndex].operation == PPCREC_IML_MACRO_MFTB)) ) - { - // segment ends after current instruction - PPCRecImlSegment_t* ppcRecSegment = PPCRecompiler_generateImlSegment(&ppcImlGenContext); - ppcRecSegment->startOffset = segmentStart; - ppcRecSegment->count = segmentImlIndex-segmentStart+1; - ppcRecSegment->ppcAddress = 0xFFFFFFFF; - segmentStart = segmentImlIndex+1; - } - else if( ppcImlGenContext.imlList[segmentImlIndex].type == PPCREC_IML_TYPE_JUMPMARK || - ppcImlGenContext.imlList[segmentImlIndex].type == PPCREC_IML_TYPE_PPC_ENTER ) - { - // segment ends before current instruction - if( segmentImlIndex > segmentStart ) - { - PPCRecImlSegment_t* ppcRecSegment = PPCRecompiler_generateImlSegment(&ppcImlGenContext); - ppcRecSegment->startOffset = segmentStart; - ppcRecSegment->count = segmentImlIndex-segmentStart; - ppcRecSegment->ppcAddress = 0xFFFFFFFF; - segmentStart = segmentImlIndex; - } - } - segmentImlIndex++; - } - if( segmentImlIndex != segmentStart ) - { - // final segment - PPCRecImlSegment_t* ppcRecSegment = PPCRecompiler_generateImlSegment(&ppcImlGenContext); - ppcRecSegment->startOffset = segmentStart; - ppcRecSegment->count = segmentImlIndex-segmentStart; - ppcRecSegment->ppcAddress = 0xFFFFFFFF; - segmentStart = segmentImlIndex; - } - // move iml instructions into the segments - for(sint32 s=0; s<ppcImlGenContext.segmentListCount; s++) - { - uint32 imlStartIndex = ppcImlGenContext.segmentList[s]->startOffset; - uint32 imlCount = ppcImlGenContext.segmentList[s]->count; - if( imlCount > 0 ) - { - ppcImlGenContext.segmentList[s]->imlListSize = imlCount + 4; - ppcImlGenContext.segmentList[s]->imlList = (PPCRecImlInstruction_t*)malloc(sizeof(PPCRecImlInstruction_t)*ppcImlGenContext.segmentList[s]->imlListSize); - ppcImlGenContext.segmentList[s]->imlListCount = imlCount; - memcpy(ppcImlGenContext.segmentList[s]->imlList, ppcImlGenContext.imlList+imlStartIndex, sizeof(PPCRecImlInstruction_t)*imlCount); - } - else - { - // empty segments are allowed so we can handle multiple PPC entry addresses pointing to the same code - ppcImlGenContext.segmentList[s]->imlList = NULL; - ppcImlGenContext.segmentList[s]->imlListSize = 0; - ppcImlGenContext.segmentList[s]->imlListCount = 0; - } - ppcImlGenContext.segmentList[s]->startOffset = 9999999; - ppcImlGenContext.segmentList[s]->count = 9999999; - } - // clear segment-independent iml list - free(ppcImlGenContext.imlList); - ppcImlGenContext.imlList = NULL; - ppcImlGenContext.imlListCount = 999999; // set to high number to force crash in case old code still uses ppcImlGenContext.imlList - // calculate PPC address of each segment based on iml instructions inside that segment (we need this info to calculate how many cpu cycles each segment takes) - for(sint32 s=0; s<ppcImlGenContext.segmentListCount; s++) - { - uint32 segmentPPCAddrMin = 0xFFFFFFFF; - uint32 segmentPPCAddrMax = 0x00000000; - for(sint32 i=0; i<ppcImlGenContext.segmentList[s]->imlListCount; i++) - { - if( ppcImlGenContext.segmentList[s]->imlList[i].associatedPPCAddress == 0 ) - continue; - //if( ppcImlGenContext.segmentList[s]->imlList[i].type == PPCREC_IML_TYPE_JUMPMARK || ppcImlGenContext.segmentList[s]->imlList[i].type == PPCREC_IML_TYPE_NO_OP ) - // continue; // jumpmarks and no-op instructions must not affect segment ppc address range - segmentPPCAddrMin = std::min(ppcImlGenContext.segmentList[s]->imlList[i].associatedPPCAddress, segmentPPCAddrMin); - segmentPPCAddrMax = std::max(ppcImlGenContext.segmentList[s]->imlList[i].associatedPPCAddress, segmentPPCAddrMax); - } - if( segmentPPCAddrMin != 0xFFFFFFFF ) - { - ppcImlGenContext.segmentList[s]->ppcAddrMin = segmentPPCAddrMin; - ppcImlGenContext.segmentList[s]->ppcAddrMax = segmentPPCAddrMax; - } - else - { - ppcImlGenContext.segmentList[s]->ppcAddrMin = 0; - ppcImlGenContext.segmentList[s]->ppcAddrMax = 0; - } - } - // certain instructions can change the segment state - // ppcEnter instruction marks a segment as enterable (BL, BCTR, etc. instructions can enter at this location from outside) - // jumpmarks mark the segment as a jump destination (within the same function) - for(sint32 s=0; s<ppcImlGenContext.segmentListCount; s++) - { - while( ppcImlGenContext.segmentList[s]->imlListCount > 0 ) - { - if( ppcImlGenContext.segmentList[s]->imlList[0].type == PPCREC_IML_TYPE_PPC_ENTER ) - { - // mark segment as enterable - if( ppcImlGenContext.segmentList[s]->isEnterable ) - assert_dbg(); // should not happen? - ppcImlGenContext.segmentList[s]->isEnterable = true; - ppcImlGenContext.segmentList[s]->enterPPCAddress = ppcImlGenContext.segmentList[s]->imlList[0].op_ppcEnter.ppcAddress; - // remove ppc_enter instruction - ppcImlGenContext.segmentList[s]->imlList[0].type = PPCREC_IML_TYPE_NO_OP; - ppcImlGenContext.segmentList[s]->imlList[0].crRegister = PPC_REC_INVALID_REGISTER; - ppcImlGenContext.segmentList[s]->imlList[0].associatedPPCAddress = 0; - } - else if( ppcImlGenContext.segmentList[s]->imlList[0].type == PPCREC_IML_TYPE_JUMPMARK ) - { - // mark segment as jump destination - if( ppcImlGenContext.segmentList[s]->isJumpDestination ) - assert_dbg(); // should not happen? - ppcImlGenContext.segmentList[s]->isJumpDestination = true; - ppcImlGenContext.segmentList[s]->jumpDestinationPPCAddress = ppcImlGenContext.segmentList[s]->imlList[0].op_jumpmark.address; - // remove jumpmark instruction - ppcImlGenContext.segmentList[s]->imlList[0].type = PPCREC_IML_TYPE_NO_OP; - ppcImlGenContext.segmentList[s]->imlList[0].crRegister = PPC_REC_INVALID_REGISTER; - ppcImlGenContext.segmentList[s]->imlList[0].associatedPPCAddress = 0; - } - else - break; - } - } - // the first segment is always enterable as the recompiled functions entrypoint - ppcImlGenContext.segmentList[0]->isEnterable = true; - ppcImlGenContext.segmentList[0]->enterPPCAddress = ppcImlGenContext.functionRef->ppcAddress; - - // link segments for further inter-segment optimization - PPCRecompilerIML_linkSegments(&ppcImlGenContext); - - // optimization pass - replace segments with conditional MOVs if possible - for (sint32 s = 0; s < ppcImlGenContext.segmentListCount; s++) - { - PPCRecImlSegment_t* imlSegment = ppcImlGenContext.segmentList[s]; - if (imlSegment->nextSegmentBranchNotTaken == NULL || imlSegment->nextSegmentBranchTaken == NULL) - continue; // not a branching segment - PPCRecImlInstruction_t* lastInstruction = PPCRecompilerIML_getLastInstruction(imlSegment); - if (lastInstruction->type != PPCREC_IML_TYPE_CJUMP || lastInstruction->op_conditionalJump.crRegisterIndex != 0) - continue; - PPCRecImlSegment_t* conditionalSegment = imlSegment->nextSegmentBranchNotTaken; - PPCRecImlSegment_t* finalSegment = imlSegment->nextSegmentBranchTaken; - if(imlSegment->nextSegmentBranchTaken != imlSegment->nextSegmentBranchNotTaken->nextSegmentBranchNotTaken) - continue; - if (imlSegment->nextSegmentBranchNotTaken->imlListCount > 4) - continue; - if(conditionalSegment->list_prevSegments.size() != 1) - continue; // the reduced segment must not be the target of any other branch - if(conditionalSegment->isEnterable) - continue; - // check if the segment contains only iml instructions that can be turned into conditional moves (Value assignment, register assignment) - bool canReduceSegment = true; - for (sint32 f = 0; f < conditionalSegment->imlListCount; f++) - { - PPCRecImlInstruction_t* imlInstruction = conditionalSegment->imlList+f; - if( imlInstruction->type == PPCREC_IML_TYPE_R_S32 && imlInstruction->operation == PPCREC_IML_OP_ASSIGN) - continue; - // todo: Register to register copy - canReduceSegment = false; - break; - } - - if( canReduceSegment == false ) - continue; - - // remove the branch instruction - uint8 branchCond_crRegisterIndex = lastInstruction->op_conditionalJump.crRegisterIndex; - uint8 branchCond_crBitIndex = lastInstruction->op_conditionalJump.crBitIndex; - bool branchCond_bitMustBeSet = lastInstruction->op_conditionalJump.bitMustBeSet; - - PPCRecompilerImlGen_generateNewInstruction_noOp(&ppcImlGenContext, lastInstruction); - - // append conditional moves based on branch condition - for (sint32 f = 0; f < conditionalSegment->imlListCount; f++) - { - PPCRecImlInstruction_t* imlInstruction = conditionalSegment->imlList + f; - if (imlInstruction->type == PPCREC_IML_TYPE_R_S32 && imlInstruction->operation == PPCREC_IML_OP_ASSIGN) - PPCRecompilerImlGen_generateNewInstruction_conditional_r_s32(&ppcImlGenContext, PPCRecompiler_appendInstruction(imlSegment), PPCREC_IML_OP_ASSIGN, imlInstruction->op_r_immS32.registerIndex, imlInstruction->op_r_immS32.immS32, branchCond_crRegisterIndex, branchCond_crBitIndex, !branchCond_bitMustBeSet); - else - assert_dbg(); - } - // update segment links - // source segment: imlSegment, conditional/removed segment: conditionalSegment, final segment: finalSegment - PPCRecompilerIML_removeLink(imlSegment, conditionalSegment); - PPCRecompilerIML_removeLink(imlSegment, finalSegment); - PPCRecompilerIML_removeLink(conditionalSegment, finalSegment); - PPCRecompilerIml_setLinkBranchNotTaken(imlSegment, finalSegment); - // remove all instructions from conditional segment - conditionalSegment->imlListCount = 0; - - // if possible, merge imlSegment with finalSegment - if (finalSegment->isEnterable == false && finalSegment->list_prevSegments.size() == 1) - { - // todo: Clean this up and move into separate function PPCRecompilerIML_mergeSegments() - PPCRecompilerIML_removeLink(imlSegment, finalSegment); - if (finalSegment->nextSegmentBranchNotTaken) - { - PPCRecImlSegment_t* tempSegment = finalSegment->nextSegmentBranchNotTaken; - PPCRecompilerIML_removeLink(finalSegment, tempSegment); - PPCRecompilerIml_setLinkBranchNotTaken(imlSegment, tempSegment); - } - if (finalSegment->nextSegmentBranchTaken) - { - PPCRecImlSegment_t* tempSegment = finalSegment->nextSegmentBranchTaken; - PPCRecompilerIML_removeLink(finalSegment, tempSegment); - PPCRecompilerIml_setLinkBranchTaken(imlSegment, tempSegment); - } - // copy IML instructions - for (sint32 f = 0; f < finalSegment->imlListCount; f++) - { - memcpy(PPCRecompiler_appendInstruction(imlSegment), finalSegment->imlList + f, sizeof(PPCRecImlInstruction_t)); - } - finalSegment->imlListCount = 0; - - //PPCRecompiler_dumpIML(ppcRecFunc, &ppcImlGenContext); - } - - // todo: If possible, merge with the segment following conditionalSegment (merging is only possible if the segment is not an entry point or has no other jump sources) - } - - // insert cycle counter instruction in every segment that has a cycle count greater zero - for(sint32 s=0; s<ppcImlGenContext.segmentListCount; s++) - { - PPCRecImlSegment_t* imlSegment = ppcImlGenContext.segmentList[s]; - if( imlSegment->ppcAddrMin == 0 ) - continue; - // count number of PPC instructions in segment - // note: This algorithm correctly counts inlined functions but it doesn't count NO-OP instructions like ISYNC - uint32 lastPPCInstAddr = 0; - uint32 ppcCount2 = 0; - for (sint32 i = 0; i < imlSegment->imlListCount; i++) - { - if (imlSegment->imlList[i].associatedPPCAddress == 0) - continue; - if (imlSegment->imlList[i].associatedPPCAddress == lastPPCInstAddr) - continue; - lastPPCInstAddr = imlSegment->imlList[i].associatedPPCAddress; - ppcCount2++; - } - //uint32 ppcCount = imlSegment->ppcAddrMax-imlSegment->ppcAddrMin+4; -> No longer works with inlined functions - uint32 cycleCount = ppcCount2;// ppcCount / 4; - if( cycleCount > 0 ) - { - PPCRecompiler_pushBackIMLInstructions(imlSegment, 0, 1); - imlSegment->imlList[0].type = PPCREC_IML_TYPE_MACRO; - imlSegment->imlList[0].crRegister = PPC_REC_INVALID_REGISTER; - imlSegment->imlList[0].operation = PPCREC_IML_MACRO_COUNT_CYCLES; - imlSegment->imlList[0].op_macro.param = cycleCount; - } - } - - // find segments that have a (conditional) jump instruction that points in reverse direction of code flow - // for these segments there is a risk that the recompiler could get trapped in an infinite busy loop. - // todo: We should do a loop-detection prepass where we flag segments that are actually in a loop. We can then use this information below to avoid generating the scheduler-exit code for segments that aren't actually in a loop despite them referencing an earlier segment (which could be an exit segment for example) - uint32 currentLoopEscapeJumpMarker = 0xFF000000; // start in an area where no valid code can be located - for(sint32 s=0; s<ppcImlGenContext.segmentListCount; s++) - { - // todo: This currently uses segment->ppcAddrMin which isn't really reliable. (We already had a problem where function inlining would generate falsified segment ranges by omitting the branch instruction). Find a better solution (use jumpmark/enterable offsets?) - PPCRecImlSegment_t* imlSegment = ppcImlGenContext.segmentList[s]; - if( imlSegment->imlListCount == 0 ) - continue; - if (imlSegment->imlList[imlSegment->imlListCount - 1].type != PPCREC_IML_TYPE_CJUMP || imlSegment->imlList[imlSegment->imlListCount - 1].op_conditionalJump.jumpmarkAddress > imlSegment->ppcAddrMin) - continue; - if (imlSegment->imlList[imlSegment->imlListCount - 1].type != PPCREC_IML_TYPE_CJUMP || imlSegment->imlList[imlSegment->imlListCount - 1].op_conditionalJump.jumpAccordingToSegment) - continue; - // exclude non-infinite tight loops - if (PPCRecompilerImlAnalyzer_isTightFiniteLoop(imlSegment)) - continue; - // potential loop segment found, split this segment into four: - // P0: This segment checks if the remaining cycles counter is still above zero. If yes, it jumps to segment P2 (it's also the jump destination for other segments) - // P1: This segment consists only of a single ppc_leave instruction and is usually skipped. Register unload instructions are later inserted here. - // P2: This segment contains the iml instructions of the original segment - // PEntry: This segment is used to enter the function, it jumps to P0 - // All segments are considered to be part of the same PPC instruction range - // The first segment also retains the jump destination and enterable properties from the original segment. - //debug_printf("--- Insert cycle counter check ---\n"); - //PPCRecompiler_dumpIML(ppcRecFunc, &ppcImlGenContext); - - PPCRecompilerIml_insertSegments(&ppcImlGenContext, s, 2); - imlSegment = NULL; - PPCRecImlSegment_t* imlSegmentP0 = ppcImlGenContext.segmentList[s+0]; - PPCRecImlSegment_t* imlSegmentP1 = ppcImlGenContext.segmentList[s+1]; - PPCRecImlSegment_t* imlSegmentP2 = ppcImlGenContext.segmentList[s+2]; - // create entry point segment - PPCRecompilerIml_insertSegments(&ppcImlGenContext, ppcImlGenContext.segmentListCount, 1); - PPCRecImlSegment_t* imlSegmentPEntry = ppcImlGenContext.segmentList[ppcImlGenContext.segmentListCount-1]; - // relink segments - PPCRecompilerIML_relinkInputSegment(imlSegmentP2, imlSegmentP0); - PPCRecompilerIml_setLinkBranchNotTaken(imlSegmentP0, imlSegmentP1); - PPCRecompilerIml_setLinkBranchTaken(imlSegmentP0, imlSegmentP2); - PPCRecompilerIml_setLinkBranchTaken(imlSegmentPEntry, imlSegmentP0); - // update segments - uint32 enterPPCAddress = imlSegmentP2->ppcAddrMin; - if (imlSegmentP2->isEnterable) - enterPPCAddress = imlSegmentP2->enterPPCAddress; - imlSegmentP0->ppcAddress = 0xFFFFFFFF; - imlSegmentP1->ppcAddress = 0xFFFFFFFF; - imlSegmentP2->ppcAddress = 0xFFFFFFFF; - cemu_assert_debug(imlSegmentP2->ppcAddrMin != 0); - // move segment properties from segment P2 to segment P0 - imlSegmentP0->isJumpDestination = imlSegmentP2->isJumpDestination; - imlSegmentP0->jumpDestinationPPCAddress = imlSegmentP2->jumpDestinationPPCAddress; - imlSegmentP0->isEnterable = false; - //imlSegmentP0->enterPPCAddress = imlSegmentP2->enterPPCAddress; - imlSegmentP0->ppcAddrMin = imlSegmentP2->ppcAddrMin; - imlSegmentP0->ppcAddrMax = imlSegmentP2->ppcAddrMax; - imlSegmentP2->isJumpDestination = false; - imlSegmentP2->jumpDestinationPPCAddress = 0; - imlSegmentP2->isEnterable = false; - imlSegmentP2->enterPPCAddress = 0; - imlSegmentP2->ppcAddrMin = 0; - imlSegmentP2->ppcAddrMax = 0; - // setup enterable segment - if( enterPPCAddress != 0 && enterPPCAddress != 0xFFFFFFFF ) - { - imlSegmentPEntry->isEnterable = true; - imlSegmentPEntry->ppcAddress = enterPPCAddress; - imlSegmentPEntry->enterPPCAddress = enterPPCAddress; - } - // assign new jumpmark to segment P2 - imlSegmentP2->isJumpDestination = true; - imlSegmentP2->jumpDestinationPPCAddress = currentLoopEscapeJumpMarker; - currentLoopEscapeJumpMarker++; - // create ppc_leave instruction in segment P1 - PPCRecompiler_pushBackIMLInstructions(imlSegmentP1, 0, 1); - imlSegmentP1->imlList[0].type = PPCREC_IML_TYPE_MACRO; - imlSegmentP1->imlList[0].operation = PPCREC_IML_MACRO_LEAVE; - imlSegmentP1->imlList[0].crRegister = PPC_REC_INVALID_REGISTER; - imlSegmentP1->imlList[0].op_macro.param = imlSegmentP0->ppcAddrMin; - imlSegmentP1->imlList[0].associatedPPCAddress = imlSegmentP0->ppcAddrMin; - // create cycle-based conditional instruction in segment P0 - PPCRecompiler_pushBackIMLInstructions(imlSegmentP0, 0, 1); - imlSegmentP0->imlList[0].type = PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK; - imlSegmentP0->imlList[0].operation = 0; - imlSegmentP0->imlList[0].crRegister = PPC_REC_INVALID_REGISTER; - imlSegmentP0->imlList[0].op_conditionalJump.jumpmarkAddress = imlSegmentP2->jumpDestinationPPCAddress; - imlSegmentP0->imlList[0].associatedPPCAddress = imlSegmentP0->ppcAddrMin; - // jump instruction for PEntry - PPCRecompiler_pushBackIMLInstructions(imlSegmentPEntry, 0, 1); - PPCRecompilerImlGen_generateNewInstruction_jumpSegment(&ppcImlGenContext, imlSegmentPEntry->imlList + 0); - - // skip the newly created segments - s += 2; - } - - // isolate entry points from function flow (enterable segments must not be the target of any other segment) - // this simplifies logic during register allocation - PPCRecompilerIML_isolateEnterableSegments(&ppcImlGenContext); - - // if GQRs can be predicted, optimize PSQ load/stores - PPCRecompiler_optimizePSQLoadAndStore(&ppcImlGenContext); - - // count number of used registers - uint32 numLoadedFPRRegisters = 0; - for(uint32 i=0; i<255; i++) - { - if( ppcImlGenContext.mappedFPRRegister[i] ) - numLoadedFPRRegisters++; - } - - // insert name store instructions at the end of each segment but before branch instructions - for(sint32 s=0; s<ppcImlGenContext.segmentListCount; s++) - { - PPCRecImlSegment_t* imlSegment = ppcImlGenContext.segmentList[s]; - if( ppcImlGenContext.segmentList[s]->imlListCount == 0 ) - continue; // ignore empty segments - // analyze segment for register usage - PPCImlOptimizerUsedRegisters_t registersUsed; - for(sint32 i=0; i<imlSegment->imlListCount; i++) - { - PPCRecompiler_checkRegisterUsage(&ppcImlGenContext, imlSegment->imlList+i, ®istersUsed); - //PPCRecompilerImlGen_findRegisterByMappedName(ppcImlGenContext, registersUsed.readGPR1); - sint32 accessedTempReg[5]; - // intermediate FPRs - accessedTempReg[0] = registersUsed.readFPR1; - accessedTempReg[1] = registersUsed.readFPR2; - accessedTempReg[2] = registersUsed.readFPR3; - accessedTempReg[3] = registersUsed.readFPR4; - accessedTempReg[4] = registersUsed.writtenFPR1; - for(sint32 f=0; f<5; f++) - { - if( accessedTempReg[f] == -1 ) - continue; - uint32 regName = ppcImlGenContext.mappedFPRRegister[accessedTempReg[f]]; - if( regName >= PPCREC_NAME_FPR0 && regName < PPCREC_NAME_FPR0+32 ) - { - imlSegment->ppcFPRUsed[regName - PPCREC_NAME_FPR0] = true; - } - } - } - } - - // merge certain float load+store patterns (must happen before FPR register remapping) - PPCRecompiler_optimizeDirectFloatCopies(&ppcImlGenContext); - // delay byte swapping for certain load+store patterns - PPCRecompiler_optimizeDirectIntegerCopies(&ppcImlGenContext); - - if (numLoadedFPRRegisters > 0) - { - if (PPCRecompiler_manageFPRRegisters(&ppcImlGenContext) == false) - { - PPCRecompiler_freeContext(&ppcImlGenContext); - return false; - } - } - - PPCRecompilerImm_allocateRegisters(&ppcImlGenContext); - - // remove redundant name load and store instructions - PPCRecompiler_reorderConditionModifyInstructions(&ppcImlGenContext); - PPCRecompiler_removeRedundantCRUpdates(&ppcImlGenContext); return true; } diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGenFPU.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGenFPU.cpp index 1efc41b8..96a7b560 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGenFPU.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGenFPU.cpp @@ -1,14 +1,19 @@ +#include "Cafe/HW/Espresso/EspressoISA.h" #include "../Interpreter/PPCInterpreterInternal.h" #include "PPCRecompiler.h" #include "PPCRecompilerIml.h" #include "Cafe/GameProfile/GameProfile.h" -void PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext_t* ppcImlGenContext, uint8 registerDestination, uint8 registerMemory, sint32 immS32, uint32 mode, bool switchEndian, uint8 registerGQR = PPC_REC_INVALID_REGISTER) +ATTR_MS_ABI double frsqrte_espresso(double input); +ATTR_MS_ABI double fres_espresso(double input); + +IMLReg _GetRegCR(ppcImlGenContext_t* ppcImlGenContext, uint8 crReg, uint8 crBit); + +void PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext_t* ppcImlGenContext, IMLReg registerDestination, IMLReg registerMemory, sint32 immS32, uint32 mode, bool switchEndian, IMLReg registerGQR = IMLREG_INVALID) { // load from memory - PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + IMLInstruction* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); imlInstruction->type = PPCREC_IML_TYPE_FPR_LOAD; - imlInstruction->crRegister = PPC_REC_INVALID_REGISTER; imlInstruction->operation = 0; imlInstruction->op_storeLoad.registerData = registerDestination; imlInstruction->op_storeLoad.registerMem = registerMemory; @@ -18,12 +23,11 @@ void PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext_t* imlInstruction->op_storeLoad.flags2.swapEndian = switchEndian; } -void PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory_indexed(ppcImlGenContext_t* ppcImlGenContext, uint8 registerDestination, uint8 registerMemory1, uint8 registerMemory2, uint32 mode, bool switchEndian, uint8 registerGQR = PPC_REC_INVALID_REGISTER) +void PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory_indexed(ppcImlGenContext_t* ppcImlGenContext, IMLReg registerDestination, IMLReg registerMemory1, IMLReg registerMemory2, uint32 mode, bool switchEndian, IMLReg registerGQR = IMLREG_INVALID) { // load from memory - PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + IMLInstruction* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); imlInstruction->type = PPCREC_IML_TYPE_FPR_LOAD_INDEXED; - imlInstruction->crRegister = PPC_REC_INVALID_REGISTER; imlInstruction->operation = 0; imlInstruction->op_storeLoad.registerData = registerDestination; imlInstruction->op_storeLoad.registerMem = registerMemory1; @@ -34,12 +38,11 @@ void PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory_indexed(ppcImlGenCo imlInstruction->op_storeLoad.flags2.swapEndian = switchEndian; } -void PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r(ppcImlGenContext_t* ppcImlGenContext, uint8 registerSource, uint8 registerMemory, sint32 immS32, uint32 mode, bool switchEndian, uint8 registerGQR = PPC_REC_INVALID_REGISTER) +void PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r(ppcImlGenContext_t* ppcImlGenContext, IMLReg registerSource, IMLReg registerMemory, sint32 immS32, uint32 mode, bool switchEndian, IMLReg registerGQR = IMLREG_INVALID) { // store to memory - PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + IMLInstruction* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); imlInstruction->type = PPCREC_IML_TYPE_FPR_STORE; - imlInstruction->crRegister = PPC_REC_INVALID_REGISTER; imlInstruction->operation = 0; imlInstruction->op_storeLoad.registerData = registerSource; imlInstruction->op_storeLoad.registerMem = registerMemory; @@ -49,12 +52,11 @@ void PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r(ppcImlGenContext_t* imlInstruction->op_storeLoad.flags2.swapEndian = switchEndian; } -void PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r_indexed(ppcImlGenContext_t* ppcImlGenContext, uint8 registerSource, uint8 registerMemory1, uint8 registerMemory2, sint32 immS32, uint32 mode, bool switchEndian, uint8 registerGQR = 0) +void PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r_indexed(ppcImlGenContext_t* ppcImlGenContext, IMLReg registerSource, IMLReg registerMemory1, IMLReg registerMemory2, sint32 immS32, uint32 mode, bool switchEndian, IMLReg registerGQR = IMLREG_INVALID) { // store to memory - PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + IMLInstruction* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); imlInstruction->type = PPCREC_IML_TYPE_FPR_STORE_INDEXED; - imlInstruction->crRegister = PPC_REC_INVALID_REGISTER; imlInstruction->operation = 0; imlInstruction->op_storeLoad.registerData = registerSource; imlInstruction->op_storeLoad.registerMem = registerMemory1; @@ -65,60 +67,53 @@ void PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r_indexed(ppcImlGenCo imlInstruction->op_storeLoad.flags2.swapEndian = switchEndian; } -void PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext_t* ppcImlGenContext, sint32 operation, uint8 registerResult, uint8 registerOperand, sint32 crRegister=PPC_REC_INVALID_REGISTER) +void PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext_t* ppcImlGenContext, sint32 operation, IMLReg registerResult, IMLReg registerOperand, sint32 crRegister=PPC_REC_INVALID_REGISTER) { // fpr OP fpr - PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + IMLInstruction* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); imlInstruction->type = PPCREC_IML_TYPE_FPR_R_R; imlInstruction->operation = operation; - imlInstruction->op_fpr_r_r.registerResult = registerResult; - imlInstruction->op_fpr_r_r.registerOperand = registerOperand; - imlInstruction->crRegister = crRegister; - imlInstruction->op_fpr_r_r.flags = 0; + imlInstruction->op_fpr_r_r.regR = registerResult; + imlInstruction->op_fpr_r_r.regA = registerOperand; } -void PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r(ppcImlGenContext_t* ppcImlGenContext, sint32 operation, uint8 registerResult, uint8 registerOperand1, uint8 registerOperand2, sint32 crRegister=PPC_REC_INVALID_REGISTER) +void PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r(ppcImlGenContext_t* ppcImlGenContext, sint32 operation, IMLReg registerResult, IMLReg registerOperand1, IMLReg registerOperand2, sint32 crRegister=PPC_REC_INVALID_REGISTER) { // fpr = OP (fpr,fpr) - PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + IMLInstruction* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); imlInstruction->type = PPCREC_IML_TYPE_FPR_R_R_R; imlInstruction->operation = operation; - imlInstruction->op_fpr_r_r_r.registerResult = registerResult; - imlInstruction->op_fpr_r_r_r.registerOperandA = registerOperand1; - imlInstruction->op_fpr_r_r_r.registerOperandB = registerOperand2; - imlInstruction->crRegister = crRegister; - imlInstruction->op_fpr_r_r_r.flags = 0; + imlInstruction->op_fpr_r_r_r.regR = registerResult; + imlInstruction->op_fpr_r_r_r.regA = registerOperand1; + imlInstruction->op_fpr_r_r_r.regB = registerOperand2; } -void PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r_r(ppcImlGenContext_t* ppcImlGenContext, sint32 operation, uint8 registerResult, uint8 registerOperandA, uint8 registerOperandB, uint8 registerOperandC, sint32 crRegister=PPC_REC_INVALID_REGISTER) +void PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r_r(ppcImlGenContext_t* ppcImlGenContext, sint32 operation, IMLReg registerResult, IMLReg registerOperandA, IMLReg registerOperandB, IMLReg registerOperandC, sint32 crRegister=PPC_REC_INVALID_REGISTER) { // fpr = OP (fpr,fpr,fpr) - PPCRecImlInstruction_t* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); + IMLInstruction* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); imlInstruction->type = PPCREC_IML_TYPE_FPR_R_R_R_R; imlInstruction->operation = operation; - imlInstruction->op_fpr_r_r_r_r.registerResult = registerResult; - imlInstruction->op_fpr_r_r_r_r.registerOperandA = registerOperandA; - imlInstruction->op_fpr_r_r_r_r.registerOperandB = registerOperandB; - imlInstruction->op_fpr_r_r_r_r.registerOperandC = registerOperandC; - imlInstruction->crRegister = crRegister; - imlInstruction->op_fpr_r_r_r_r.flags = 0; + imlInstruction->op_fpr_r_r_r_r.regR = registerResult; + imlInstruction->op_fpr_r_r_r_r.regA = registerOperandA; + imlInstruction->op_fpr_r_r_r_r.regB = registerOperandB; + imlInstruction->op_fpr_r_r_r_r.regC = registerOperandC; } -void PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction, sint32 operation, uint8 registerResult, sint32 crRegister) +void PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext_t* ppcImlGenContext, IMLInstruction* imlInstruction, sint32 operation, IMLReg registerResult) { // OP (fpr) if(imlInstruction == NULL) imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); imlInstruction->type = PPCREC_IML_TYPE_FPR_R; imlInstruction->operation = operation; - imlInstruction->op_fpr_r.registerResult = registerResult; - imlInstruction->crRegister = crRegister; + imlInstruction->op_fpr_r.regR = registerResult; } /* * Rounds the bottom double to single precision (if single precision accuracy is emulated) */ -void PPRecompilerImmGen_optionalRoundBottomFPRToSinglePrecision(ppcImlGenContext_t* ppcImlGenContext, uint32 fprRegister, bool flushDenormals=false) +void PPRecompilerImmGen_optionalRoundBottomFPRToSinglePrecision(ppcImlGenContext_t* ppcImlGenContext, IMLReg fprRegister, bool flushDenormals=false) { PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext, NULL, PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_BOTTOM, fprRegister); if( flushDenormals ) @@ -128,7 +123,7 @@ void PPRecompilerImmGen_optionalRoundBottomFPRToSinglePrecision(ppcImlGenContext /* * Rounds pair of doubles to single precision (if single precision accuracy is emulated) */ -void PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext_t* ppcImlGenContext, uint32 fprRegister, bool flushDenormals=false) +void PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext_t* ppcImlGenContext, IMLReg fprRegister, bool flushDenormals=false) { PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext, NULL, PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_PAIR, fprRegister); if( flushDenormals ) @@ -141,9 +136,9 @@ bool PPCRecompilerImlGen_LFS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, imm); // get memory gpr register index - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + IMLReg gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); // get fpr register index - uint32 fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); if( ppcImlGenContext->LSQE ) { PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext, fprRegister, gprRegister, imm, PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1, true); @@ -161,11 +156,11 @@ bool PPCRecompilerImlGen_LFSU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, imm); // get memory gpr register index - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + IMLReg gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); // add imm to memory register - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, gprRegister, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_ADD, gprRegister, gprRegister, (sint32)imm); // get fpr register index - uint32 fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); if( ppcImlGenContext->LSQE ) { PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext, fprRegister, gprRegister, 0, PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1, true); @@ -187,10 +182,10 @@ bool PPCRecompilerImlGen_LFSX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod return false; } // get memory gpr registers - uint32 gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + IMLReg gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + IMLReg gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); // get fpr register index - uint32 fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); if( ppcImlGenContext->LSQE ) { PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory_indexed(ppcImlGenContext, fprRegister, gprRegister1, gprRegister2, PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1, true); @@ -212,12 +207,12 @@ bool PPCRecompilerImlGen_LFSUX(ppcImlGenContext_t* ppcImlGenContext, uint32 opco return false; } // get memory gpr registers - uint32 gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + IMLReg gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + IMLReg gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); // add rB to rA (if rA != 0) - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ADD, gprRegister1, gprRegister2); + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_ADD, gprRegister1, gprRegister1, gprRegister2); // get fpr register index - uint32 fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); if( ppcImlGenContext->LSQE ) { PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext, fprRegister, gprRegister1, 0, PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1, true); @@ -239,9 +234,9 @@ bool PPCRecompilerImlGen_LFD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode assert_dbg(); } // get memory gpr register index - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + IMLReg gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); // get fpr register index - uint32 fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext, fprRegister, gprRegister, imm, PPCREC_FPR_LD_MODE_DOUBLE_INTO_PS0, true); return true; } @@ -256,11 +251,11 @@ bool PPCRecompilerImlGen_LFDU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod assert_dbg(); } // get memory gpr register index - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + IMLReg gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); // add imm to memory register - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, gprRegister, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_ADD, gprRegister, gprRegister, (sint32)imm); // get fpr register index - uint32 fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); // emit load iml PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext, fprRegister, gprRegister, 0, PPCREC_FPR_LD_MODE_DOUBLE_INTO_PS0, true); return true; @@ -276,10 +271,10 @@ bool PPCRecompilerImlGen_LFDX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod return false; } // get memory gpr registers - uint32 gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + IMLReg gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + IMLReg gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); // get fpr register index - uint32 fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory_indexed(ppcImlGenContext, fprRegister, gprRegister1, gprRegister2, PPCREC_FPR_LD_MODE_DOUBLE_INTO_PS0, true); return true; } @@ -293,13 +288,11 @@ bool PPCRecompilerImlGen_LFDUX(ppcImlGenContext_t* ppcImlGenContext, uint32 opco debugBreakpoint(); return false; } - // get memory gpr registers - uint32 gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + IMLReg gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + IMLReg gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); // add rB to rA (if rA != 0) - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ADD, gprRegister1, gprRegister2); - // get fpr register index - uint32 fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_ADD, gprRegister1, gprRegister1, gprRegister2); + IMLReg fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext, fprRegister, gprRegister1, 0, PPCREC_FPR_LD_MODE_DOUBLE_INTO_PS0, true); return true; } @@ -309,10 +302,8 @@ bool PPCRecompilerImlGen_STFS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod sint32 rA, frD; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, imm); - // get memory gpr register index - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - // get fpr register index - uint32 fprRegister = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + IMLReg fprRegister = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r(ppcImlGenContext, fprRegister, gprRegister, imm, PPCREC_FPR_ST_MODE_SINGLE_FROM_PS0, true); return true; @@ -324,11 +315,11 @@ bool PPCRecompilerImlGen_STFSU(ppcImlGenContext_t* ppcImlGenContext, uint32 opco uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, imm); // get memory gpr register index - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + IMLReg gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); // add imm to memory register - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, gprRegister, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_ADD, gprRegister, gprRegister, (sint32)imm); // get fpr register index - uint32 fprRegister = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegister = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r(ppcImlGenContext, fprRegister, gprRegister, 0, PPCREC_FPR_ST_MODE_SINGLE_FROM_PS0, true); return true; @@ -344,10 +335,10 @@ bool PPCRecompilerImlGen_STFSX(ppcImlGenContext_t* ppcImlGenContext, uint32 opco return false; } // get memory gpr registers - uint32 gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + IMLReg gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + IMLReg gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); // get fpr register index - uint32 fprRegister = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frS); + IMLReg fprRegister = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frS); if( ppcImlGenContext->LSQE ) { PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r_indexed(ppcImlGenContext, fprRegister, gprRegister1, gprRegister2, 0, PPCREC_FPR_ST_MODE_SINGLE_FROM_PS0, true); @@ -370,12 +361,12 @@ bool PPCRecompilerImlGen_STFSUX(ppcImlGenContext_t* ppcImlGenContext, uint32 opc return false; } // get memory gpr registers - uint32 gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + IMLReg gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + IMLReg gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); // get fpr register index - uint32 fprRegister = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frS); + IMLReg fprRegister = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frS); // calculate EA in rA - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, NULL, PPCREC_IML_OP_ADD, gprRegister1, gprRegister2); + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_ADD, gprRegister1, gprRegister1, gprRegister2); PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r(ppcImlGenContext, fprRegister, gprRegister1, 0, PPCREC_FPR_ST_MODE_SINGLE_FROM_PS0, true); return true; @@ -392,9 +383,9 @@ bool PPCRecompilerImlGen_STFD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod return false; } // get memory gpr register index - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + IMLReg gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); // get fpr register index - uint32 fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r(ppcImlGenContext, fprRegister, gprRegister, imm, PPCREC_FPR_ST_MODE_DOUBLE_FROM_PS0, true); return true; } @@ -410,11 +401,11 @@ bool PPCRecompilerImlGen_STFDU(ppcImlGenContext_t* ppcImlGenContext, uint32 opco return false; } // get memory gpr register index - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); + IMLReg gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); // add imm to memory register - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, gprRegister, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_ADD, gprRegister, gprRegister, (sint32)imm); // get fpr register index - uint32 fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r(ppcImlGenContext, fprRegister, gprRegister, 0, PPCREC_FPR_ST_MODE_DOUBLE_FROM_PS0, true); return true; @@ -430,10 +421,10 @@ bool PPCRecompilerImlGen_STFDX(ppcImlGenContext_t* ppcImlGenContext, uint32 opco return false; } // get memory gpr registers - uint32 gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - uint32 gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + IMLReg gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + IMLReg gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); // get fpr register index - uint32 fprRegister = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frS); + IMLReg fprRegister = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frS); if( ppcImlGenContext->LSQE ) { PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r_indexed(ppcImlGenContext, fprRegister, gprRegister1, gprRegister2, 0, PPCREC_FPR_ST_MODE_DOUBLE_FROM_PS0, true); @@ -450,21 +441,21 @@ bool PPCRecompilerImlGen_STFIWX(ppcImlGenContext_t* ppcImlGenContext, uint32 opc sint32 rA, frS, rB; PPC_OPC_TEMPL_X(opcode, frS, rA, rB); // get memory gpr registers - uint32 gprRegister1; - uint32 gprRegister2; + IMLReg gprRegister1; + IMLReg gprRegister2; if( rA != 0 ) { - gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA, false); - gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); + gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); } else { // rA is not used - gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB, false); - gprRegister2 = 0; + gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); + gprRegister2 = IMLREG_INVALID; } // get fpr register index - uint32 fprRegister = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frS); + IMLReg fprRegister = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frS); if( rA != 0 ) PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r_indexed(ppcImlGenContext, fprRegister, gprRegister1, gprRegister2, 0, PPCREC_FPR_ST_MODE_UI32_FROM_PS0, true); else @@ -479,9 +470,9 @@ bool PPCRecompilerImlGen_FADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod PPC_ASSERT(frC==0); // load registers - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_BOTTOM, fprRegisterD, fprRegisterA, fprRegisterB); return true; @@ -494,9 +485,9 @@ bool PPCRecompilerImlGen_FSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod PPC_ASSERT(frC==0); // load registers - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); // subtract bottom double of frB from bottom double of frD PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SUB_BOTTOM, fprRegisterD, fprRegisterA, fprRegisterB); return true; @@ -514,9 +505,9 @@ bool PPCRecompilerImlGen_FMUL(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod frC = temp; } // load registers - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); // move frA to frD (if different register) if( fprRegisterD != fprRegisterA ) PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, fprRegisterD, fprRegisterA); // always copy ps0 and ps1 @@ -531,13 +522,13 @@ bool PPCRecompilerImlGen_FDIV(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC_unused); PPC_ASSERT(frB==0); // load registers - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frA); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frB); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frD); if( frB == frD && frA != frB ) { - uint32 fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); + IMLReg fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); // move frA to temporary register PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, fprRegisterTemp, fprRegisterA); // divide bottom double of temporary register by bottom double of frB @@ -559,14 +550,14 @@ bool PPCRecompilerImlGen_FMADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opco sint32 frD, frA, frB, frC; PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); // load registers - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); // if frB is already in frD we need a temporary register to store the product of frA*frC if( frB == frD ) { - uint32 fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); + IMLReg fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); // move frA to temporary register PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, fprRegisterTemp, fprRegisterA); // multiply bottom double of temporary register with bottom double of frC @@ -579,7 +570,7 @@ bool PPCRecompilerImlGen_FMADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opco if( fprRegisterD == fprRegisterC ) { // swap frA and frC - sint32 temp = fprRegisterA; + IMLReg temp = fprRegisterA; fprRegisterA = fprRegisterC; fprRegisterC = temp; } @@ -598,10 +589,10 @@ bool PPCRecompilerImlGen_FMSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opco sint32 frD, frA, frB, frC; PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); // load registers - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); // if frB is already in frD we need a temporary register to store the product of frA*frC if( frB == frD ) { @@ -612,7 +603,7 @@ bool PPCRecompilerImlGen_FMSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opco if( fprRegisterD == fprRegisterC ) { // swap frA and frC - sint32 temp = fprRegisterA; + IMLReg temp = fprRegisterA; fprRegisterA = fprRegisterC; fprRegisterC = temp; } @@ -632,15 +623,15 @@ bool PPCRecompilerImlGen_FNMSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opc PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); // load registers - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); // if frB is already in frD we need a temporary register to store the product of frA*frC if( frB == frD ) { // hCPU->fpr[frD].fpr = -(hCPU->fpr[frA].fpr * hCPU->fpr[frC].fpr - hCPU->fpr[frD].fpr); - uint32 fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); + IMLReg fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); //// negate frB/frD //PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext, NULL,PPCREC_IML_OP_FPR_NEGATE_BOTTOM, fprRegisterD, true); // move frA to temporary register @@ -659,7 +650,7 @@ bool PPCRecompilerImlGen_FNMSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opc if( fprRegisterD == fprRegisterC ) { // swap frA and frC - sint32 temp = fprRegisterA; + IMLReg temp = fprRegisterA; fprRegisterA = fprRegisterC; fprRegisterC = temp; } @@ -688,9 +679,9 @@ bool PPCRecompilerImlGen_FMULS(ppcImlGenContext_t* ppcImlGenContext, uint32 opco frC = temp; } // load registers - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); // move frA to frD (if different register) if( fprRegisterD != fprRegisterA ) PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, fprRegisterD, fprRegisterA); // always copy ps0 and ps1 @@ -717,13 +708,13 @@ bool PPCRecompilerImlGen_FDIVS(ppcImlGenContext_t* ppcImlGenContext, uint32 opco if( hCPU->PSE ) hCPU->fpr[frD].fp1 = hCPU->fpr[frD].fp0;*/ // load registers - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); if( frB == frD && frA != frB ) { - uint32 fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); + IMLReg fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); // move frA to temporary register PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, fprRegisterTemp, fprRegisterA); // divide bottom double of temporary register by bottom double of frB @@ -767,9 +758,9 @@ bool PPCRecompilerImlGen_FADDS(ppcImlGenContext_t* ppcImlGenContext, uint32 opco frB = temp; } // load registers - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); // move frA to frD (if different register) if( fprRegisterD != fprRegisterA ) PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, fprRegisterD, fprRegisterA); // always copy ps0 and ps1 @@ -792,9 +783,9 @@ bool PPCRecompilerImlGen_FSUBS(ppcImlGenContext_t* ppcImlGenContext, uint32 opco PPC_ASSERT(frB==0); // load registers - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); // subtract bottom PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SUB_BOTTOM, fprRegisterD, fprRegisterA, fprRegisterB); // adjust accuracy @@ -816,11 +807,11 @@ bool PPCRecompilerImlGen_FMADDS(ppcImlGenContext_t* ppcImlGenContext, uint32 opc //if( hCPU->PSE ) // hCPU->fpr[frD].fp1 = hCPU->fpr[frD].fp0; // load registers - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - uint32 fprRegisterTemp; + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterTemp; // if none of the operand registers overlap with the result register then we can avoid the usage of a temporary register if( fprRegisterD != fprRegisterA && fprRegisterD != fprRegisterB && fprRegisterD != fprRegisterC ) fprRegisterTemp = fprRegisterD; @@ -850,11 +841,11 @@ bool PPCRecompilerImlGen_FMSUBS(ppcImlGenContext_t* ppcImlGenContext, uint32 opc //if( hCPU->PSE ) // hCPU->fpr[frD].fp1 = hCPU->fpr[frD].fp0; // load registers - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - uint32 fprRegisterTemp; + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterTemp; // if none of the operand registers overlap with the result register then we can avoid the usage of a temporary register if( fprRegisterD != fprRegisterA && fprRegisterD != fprRegisterB && fprRegisterD != fprRegisterC ) fprRegisterTemp = fprRegisterD; @@ -887,11 +878,11 @@ bool PPCRecompilerImlGen_FNMSUBS(ppcImlGenContext_t* ppcImlGenContext, uint32 op // hCPU->fpr[frD].fp1 = hCPU->fpr[frD].fp0; // load registers - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - uint32 fprRegisterTemp; + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterTemp; // if none of the operand registers overlap with the result register then we can avoid the usage of a temporary register if( fprRegisterD != fprRegisterA && fprRegisterD != fprRegisterB && fprRegisterD != fprRegisterC ) fprRegisterTemp = fprRegisterD; @@ -916,12 +907,33 @@ bool PPCRecompilerImlGen_FNMSUBS(ppcImlGenContext_t* ppcImlGenContext, uint32 op bool PPCRecompilerImlGen_FCMPO(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { - sint32 crfD, frA, frB; - PPC_OPC_TEMPL_X(opcode, crfD, frA, frB); - crfD >>= 2; - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_FCMPO_BOTTOM, fprRegisterA, fprRegisterB, crfD); + printf("FCMPO: Not implemented\n"); + return false; + + //sint32 crfD, frA, frB; + //PPC_OPC_TEMPL_X(opcode, crfD, frA, frB); + //crfD >>= 2; + //IMLReg regFprA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frA); + //IMLReg regFprB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frB); + + //IMLReg crBitRegLT = _GetCRReg(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_LT); + //IMLReg crBitRegGT = _GetCRReg(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_GT); + //IMLReg crBitRegEQ = _GetCRReg(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_EQ); + //IMLReg crBitRegSO = _GetCRReg(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_SO); + + //ppcImlGenContext->emitInst().make_fpr_compare(regFprA, regFprB, crBitRegLT, IMLCondition::UNORDERED_LT); + //ppcImlGenContext->emitInst().make_fpr_compare(regFprA, regFprB, crBitRegGT, IMLCondition::UNORDERED_GT); + //ppcImlGenContext->emitInst().make_fpr_compare(regFprA, regFprB, crBitRegEQ, IMLCondition::UNORDERED_EQ); + //ppcImlGenContext->emitInst().make_fpr_compare(regFprA, regFprB, crBitRegSO, IMLCondition::UNORDERED_U); + + // todo - set fpscr + + //sint32 crfD, frA, frB; + //PPC_OPC_TEMPL_X(opcode, crfD, frA, frB); + //crfD >>= 2; + //uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + //uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + //PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_FCMPO_BOTTOM, fprRegisterA, fprRegisterB, crfD); return true; } @@ -930,9 +942,21 @@ bool PPCRecompilerImlGen_FCMPU(ppcImlGenContext_t* ppcImlGenContext, uint32 opco sint32 crfD, frA, frB; PPC_OPC_TEMPL_X(opcode, crfD, frA, frB); crfD >>= 2; - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_FCMPU_BOTTOM, fprRegisterA, fprRegisterB, crfD); + IMLReg regFprA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frA); + IMLReg regFprB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frB); + + IMLReg crBitRegLT = _GetRegCR(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_LT); + IMLReg crBitRegGT = _GetRegCR(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_GT); + IMLReg crBitRegEQ = _GetRegCR(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_EQ); + IMLReg crBitRegSO = _GetRegCR(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_SO); + + ppcImlGenContext->emitInst().make_fpr_compare(regFprA, regFprB, crBitRegLT, IMLCondition::UNORDERED_LT); + ppcImlGenContext->emitInst().make_fpr_compare(regFprA, regFprB, crBitRegGT, IMLCondition::UNORDERED_GT); + ppcImlGenContext->emitInst().make_fpr_compare(regFprA, regFprB, crBitRegEQ, IMLCondition::UNORDERED_EQ); + ppcImlGenContext->emitInst().make_fpr_compare(regFprA, regFprB, crBitRegSO, IMLCondition::UNORDERED_U); + + // todo: set fpscr + return true; } @@ -940,8 +964,8 @@ bool PPCRecompilerImlGen_FMR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode { sint32 frD, rA, frB; PPC_OPC_TEMPL_X(opcode, frD, rA, frB); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterB); return true; } @@ -952,8 +976,8 @@ bool PPCRecompilerImlGen_FABS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod PPC_OPC_TEMPL_X(opcode, frD, frA, frB); PPC_ASSERT(frA==0); // load registers - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); // move frB to frD (if different register) if( fprRegisterD != fprRegisterB ) PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterB); @@ -968,8 +992,8 @@ bool PPCRecompilerImlGen_FNABS(ppcImlGenContext_t* ppcImlGenContext, uint32 opco PPC_OPC_TEMPL_X(opcode, frD, frA, frB); PPC_ASSERT(frA==0); // load registers - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); // move frB to frD (if different register) if( fprRegisterD != fprRegisterB ) PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterB); @@ -984,11 +1008,14 @@ bool PPCRecompilerImlGen_FRES(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod PPC_OPC_TEMPL_X(opcode, frD, frA, frB); PPC_ASSERT(frA==0); // load registers - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_BOTTOM_FRES_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterB); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + ppcImlGenContext->emitInst().make_call_imm((uintptr_t)fres_espresso, fprRegisterB, IMLREG_INVALID, IMLREG_INVALID, fprRegisterD); // adjust accuracy PPRecompilerImmGen_optionalRoundBottomFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + // copy result to top + if( ppcImlGenContext->PSE ) + PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterD); return true; } @@ -997,17 +1024,15 @@ bool PPCRecompilerImlGen_FRSP(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod sint32 frD, frA, frB; PPC_OPC_TEMPL_X(opcode, frD, frA, frB); PPC_ASSERT(frA==0); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); if( fprRegisterD != fprRegisterB ) { PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterB); } PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext, NULL,PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_BOTTOM, fprRegisterD); if( ppcImlGenContext->PSE ) - { PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterD); - } return true; } @@ -1021,8 +1046,8 @@ bool PPCRecompilerImlGen_FNEG(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod return false; } // load registers - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); // move frB to frD (if different register) if( fprRegisterD != fprRegisterB ) PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterB); @@ -1039,10 +1064,10 @@ bool PPCRecompilerImlGen_FSEL(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod { return false; } - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SELECT_BOTTOM, fprRegisterD, fprRegisterA, fprRegisterB, fprRegisterC); return true; } @@ -1052,9 +1077,9 @@ bool PPCRecompilerImlGen_FRSQRTE(ppcImlGenContext_t* ppcImlGenContext, uint32 op sint32 frD, frA, frB, frC; PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); // hCPU->fpr[frD].fpr = 1.0 / sqrt(hCPU->fpr[frB].fpr); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_BOTTOM_RECIPROCAL_SQRT, fprRegisterD, fprRegisterB); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + ppcImlGenContext->emitInst().make_call_imm((uintptr_t)frsqrte_espresso, fprRegisterB, IMLREG_INVALID, IMLREG_INVALID, fprRegisterD); // adjust accuracy PPRecompilerImmGen_optionalRoundBottomFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); return true; @@ -1064,8 +1089,8 @@ bool PPCRecompilerImlGen_FCTIWZ(ppcImlGenContext_t* ppcImlGenContext, uint32 opc { sint32 frD, frA, frB; PPC_OPC_TEMPL_X(opcode, frD, frA, frB); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_BOTTOM_FCTIWZ, fprRegisterD, fprRegisterB); return true; } @@ -1083,12 +1108,9 @@ bool PPCRecompilerImlGen_PSQ_L(ppcImlGenContext_t* ppcImlGenContext, uint32 opco bool readPS1 = (opcode & 0x8000) == false; - // get gqr register - uint32 gqrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + SPR_UGQR0 + gqrIndex, false); - // get memory gpr register index - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rA, false); - // get fpr register index - uint32 fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frD); + IMLReg gqrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + SPR_UGQR0 + gqrIndex); + IMLReg gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rA); + IMLReg fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frD); // psq load PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext, fprRegister, gprRegister, imm, readPS1 ? PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1 : PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0, true, gqrRegister); return true; @@ -1109,14 +1131,12 @@ bool PPCRecompilerImlGen_PSQ_LU(ppcImlGenContext_t* ppcImlGenContext, uint32 opc bool readPS1 = (opcode & 0x8000) == false; - // get gqr register - uint32 gqrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + SPR_UGQR0 + gqrIndex, false); - // get memory gpr register index - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rA, false); - // add imm to memory register - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, gprRegister, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); - // get fpr register index - uint32 fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frD); + IMLReg gqrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + SPR_UGQR0 + gqrIndex); + IMLReg gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rA); + + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_ADD, gprRegister, gprRegister, (sint32)imm); + + IMLReg fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frD); // paired load PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext, fprRegister, gprRegister, 0, readPS1 ? PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1 : PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0, true, gqrRegister); return true; @@ -1134,12 +1154,9 @@ bool PPCRecompilerImlGen_PSQ_ST(ppcImlGenContext_t* ppcImlGenContext, uint32 opc bool storePS1 = (opcode & 0x8000) == false; - // get gqr register - uint32 gqrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + SPR_UGQR0 + gqrIndex, false); - // get memory gpr register index - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rA, false); - // get fpr register index - uint32 fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frD); + IMLReg gqrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + SPR_UGQR0 + gqrIndex); + IMLReg gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rA); + IMLReg fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frD); // paired store PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r(ppcImlGenContext, fprRegister, gprRegister, imm, storePS1 ? PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1 : PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0, true, gqrRegister); return true; @@ -1160,14 +1177,11 @@ bool PPCRecompilerImlGen_PSQ_STU(ppcImlGenContext_t* ppcImlGenContext, uint32 op bool storePS1 = (opcode & 0x8000) == false; - // get gqr register - uint32 gqrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + SPR_UGQR0 + gqrIndex, false); - // get memory gpr register index - uint32 gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rA, false); - // add imm to memory register - PPCRecompilerImlGen_generateNewInstruction_r_s32(ppcImlGenContext, PPCREC_IML_OP_ADD, gprRegister, (sint32)imm, 0, false, false, PPC_REC_INVALID_REGISTER, 0); - // get fpr register index - uint32 fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frD); + IMLReg gqrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + SPR_UGQR0 + gqrIndex); + IMLReg gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rA); + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_ADD, gprRegister, gprRegister, (sint32)imm); + + IMLReg fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frD); // paired store PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r(ppcImlGenContext, fprRegister, gprRegister, 0, storePS1 ? PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1 : PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0, true, gqrRegister); return true; @@ -1180,11 +1194,11 @@ bool PPCRecompilerImlGen_PS_MULS0(ppcImlGenContext_t* ppcImlGenContext, uint32 o frA = (opcode>>16)&0x1F; frD = (opcode>>21)&0x1F; // load registers - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); // we need a temporary register to store frC.fp0 in low and high half - uint32 fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); + IMLReg fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, fprRegisterTemp, fprRegisterC); // if frD == frA we can multiply frD immediately and safe a copy instruction if( frD == frA ) @@ -1210,11 +1224,11 @@ bool PPCRecompilerImlGen_PS_MULS1(ppcImlGenContext_t* ppcImlGenContext, uint32 o frA = (opcode>>16)&0x1F; frD = (opcode>>21)&0x1F; // load registers - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); // we need a temporary register to store frC.fp0 in low and high half - uint32 fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); + IMLReg fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM_AND_TOP, fprRegisterTemp, fprRegisterC); // if frD == frA we can multiply frD immediately and safe a copy instruction if( frD == frA ) @@ -1243,12 +1257,12 @@ bool PPCRecompilerImlGen_PS_MADDS0(ppcImlGenContext_t* ppcImlGenContext, uint32 //float s0 = (float)(hCPU->fpr[frA].fp0 * hCPU->fpr[frC].fp0 + hCPU->fpr[frB].fp0); //float s1 = (float)(hCPU->fpr[frA].fp1 * hCPU->fpr[frC].fp0 + hCPU->fpr[frB].fp1); // load registers - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); // we need a temporary register to store frC.fp0 in low and high half - uint32 fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); + IMLReg fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, fprRegisterTemp, fprRegisterC); // if frD == frA and frD != frB we can multiply frD immediately and safe a copy instruction if( frD == frA && frD != frB ) @@ -1281,12 +1295,12 @@ bool PPCRecompilerImlGen_PS_MADDS1(ppcImlGenContext_t* ppcImlGenContext, uint32 //float s0 = (float)(hCPU->fpr[frA].fp0 * hCPU->fpr[frC].fp1 + hCPU->fpr[frB].fp0); //float s1 = (float)(hCPU->fpr[frA].fp1 * hCPU->fpr[frC].fp1 + hCPU->fpr[frB].fp1); // load registers - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); // we need a temporary register to store frC.fp1 in bottom and top half - uint32 fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); + IMLReg fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM_AND_TOP, fprRegisterTemp, fprRegisterC); // if frD == frA and frD != frB we can multiply frD immediately and safe a copy instruction if( frD == frA && frD != frB ) @@ -1318,9 +1332,9 @@ bool PPCRecompilerImlGen_PS_ADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opc //hCPU->fpr[frD].fp0 = hCPU->fpr[frA].fp0 + hCPU->fpr[frB].fp0; //hCPU->fpr[frD].fp1 = hCPU->fpr[frA].fp1 + hCPU->fpr[frB].fp1; // load registers - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); if( frD == frA ) { PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_PAIR, fprRegisterD, fprRegisterB); @@ -1348,9 +1362,9 @@ bool PPCRecompilerImlGen_PS_SUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opc //hCPU->fpr[frD].fp0 = hCPU->fpr[frA].fp0 - hCPU->fpr[frB].fp0; //hCPU->fpr[frD].fp1 = hCPU->fpr[frA].fp1 - hCPU->fpr[frB].fp1; // load registers - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SUB_PAIR, fprRegisterD, fprRegisterA, fprRegisterB); // adjust accuracy PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); @@ -1364,11 +1378,11 @@ bool PPCRecompilerImlGen_PS_MUL(ppcImlGenContext_t* ppcImlGenContext, uint32 opc frA = (opcode >> 16) & 0x1F; frD = (opcode >> 21) & 0x1F; // load registers - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frA); - uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frC); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frD); + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frA); + IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frC); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frD); // we need a temporary register - uint32 fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0 + 0); + IMLReg fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0 + 0); PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterTemp, fprRegisterC); // todo-optimize: This instruction can be optimized so that it doesn't always use a temporary register // if frD == frA we can multiply frD immediately and safe a copy instruction @@ -1397,9 +1411,9 @@ bool PPCRecompilerImlGen_PS_DIV(ppcImlGenContext_t* ppcImlGenContext, uint32 opc //hCPU->fpr[frD].fp0 = hCPU->fpr[frA].fp0 / hCPU->fpr[frB].fp0; //hCPU->fpr[frD].fp1 = hCPU->fpr[frA].fp1 / hCPU->fpr[frB].fp1; // load registers - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frA); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frB); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frD); + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frA); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frB); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frD); // todo-optimize: This instruction can be optimized so that it doesn't always use a temporary register // if frD == frA we can divide frD immediately and safe a copy instruction if (frD == frA) @@ -1409,7 +1423,7 @@ bool PPCRecompilerImlGen_PS_DIV(ppcImlGenContext_t* ppcImlGenContext, uint32 opc else { // we need a temporary register - uint32 fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0 + 0); + IMLReg fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0 + 0); PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterTemp, fprRegisterA); // we divide temporary by frB PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_DIVIDE_PAIR, fprRegisterTemp, fprRegisterB); @@ -1432,12 +1446,12 @@ bool PPCRecompilerImlGen_PS_MADD(ppcImlGenContext_t* ppcImlGenContext, uint32 op //float s1 = (float)(hCPU->fpr[frA].fp1 * hCPU->fpr[frC].fp1 + hCPU->fpr[frB].fp1); // load registers - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); // we need a temporary register to store frC.fp0 in low and high half - uint32 fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); + IMLReg fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterTemp, fprRegisterC); // todo-optimize: This instruction can be optimized so that it doesn't always use a temporary register // if frD == frA and frD != frB we can multiply frD immediately and save a copy instruction @@ -1470,12 +1484,12 @@ bool PPCRecompilerImlGen_PS_NMADD(ppcImlGenContext_t* ppcImlGenContext, uint32 o frD = (opcode>>21)&0x1F; // load registers - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); // we need a temporary register to store frC.fp0 in low and high half - uint32 fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); + IMLReg fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterTemp, fprRegisterC); // todo-optimize: This instruction can be optimized so that it doesn't always use a temporary register // if frD == frA and frD != frB we can multiply frD immediately and safe a copy instruction @@ -1514,12 +1528,12 @@ bool PPCRecompilerImlGen_PS_MSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 op //hCPU->fpr[frD].fp1 = (hCPU->fpr[frA].fp1 * hCPU->fpr[frC].fp1 - hCPU->fpr[frB].fp1); // load registers - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); // we need a temporary register to store frC.fp0 in low and high half - uint32 fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); + IMLReg fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterTemp, fprRegisterC); // todo-optimize: This instruction can be optimized so that it doesn't always use a temporary register // if frD == frA and frD != frB we can multiply frD immediately and safe a copy instruction @@ -1552,12 +1566,12 @@ bool PPCRecompilerImlGen_PS_NMSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 o frD = (opcode>>21)&0x1F; // load registers - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); // we need a temporary register to store frC.fp0 in low and high half - uint32 fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); + IMLReg fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterTemp, fprRegisterC); // todo-optimize: This instruction can be optimized so that it doesn't always use a temporary register // if frD == frA and frD != frB we can multiply frD immediately and safe a copy instruction @@ -1595,10 +1609,10 @@ bool PPCRecompilerImlGen_PS_SUM0(ppcImlGenContext_t* ppcImlGenContext, uint32 op //hCPU->fpr[frD].fp0 = s0; //hCPU->fpr[frD].fp1 = s1; // load registers - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SUM0, fprRegisterD, fprRegisterA, fprRegisterB, fprRegisterC); // adjust accuracy PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); @@ -1617,10 +1631,10 @@ bool PPCRecompilerImlGen_PS_SUM1(ppcImlGenContext_t* ppcImlGenContext, uint32 op //hCPU->fpr[frD].fp0 = s0; //hCPU->fpr[frD].fp1 = s1; // load registers - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SUM1, fprRegisterD, fprRegisterA, fprRegisterB, fprRegisterC); // adjust accuracy PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); @@ -1635,8 +1649,8 @@ bool PPCRecompilerImlGen_PS_NEG(ppcImlGenContext_t* ppcImlGenContext, uint32 opc //hCPU->fpr[frD].fp0 = -hCPU->fpr[frB].fp0; //hCPU->fpr[frD].fp1 = -hCPU->fpr[frB].fp1; // load registers - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_NEGATE_PAIR, fprRegisterD, fprRegisterB); return true; } @@ -1647,8 +1661,8 @@ bool PPCRecompilerImlGen_PS_ABS(ppcImlGenContext_t* ppcImlGenContext, uint32 opc frB = (opcode>>11)&0x1F; frD = (opcode>>21)&0x1F; // load registers - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ABS_PAIR, fprRegisterD, fprRegisterB); return true; } @@ -1662,8 +1676,8 @@ bool PPCRecompilerImlGen_PS_RES(ppcImlGenContext_t* ppcImlGenContext, uint32 opc //hCPU->fpr[frD].fp1 = (float)(1.0f / (float)hCPU->fpr[frB].fp1); // load registers - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_FRES_PAIR, fprRegisterD, fprRegisterB); return true; @@ -1678,8 +1692,8 @@ bool PPCRecompilerImlGen_PS_RSQRTE(ppcImlGenContext_t* ppcImlGenContext, uint32 //hCPU->fpr[frD].fp1 = (float)(1.0f / (float)sqrt(hCPU->fpr[frB].fp1)); // load registers - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_FRSQRTE_PAIR, fprRegisterD, fprRegisterB); return true; } @@ -1694,8 +1708,8 @@ bool PPCRecompilerImlGen_PS_MR(ppcImlGenContext_t* ppcImlGenContext, uint32 opco // load registers if( frB != frD ) { - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterD, fprRegisterB); } return true; @@ -1709,10 +1723,10 @@ bool PPCRecompilerImlGen_PS_SEL(ppcImlGenContext_t* ppcImlGenContext, uint32 opc frA = (opcode>>16)&0x1F; frD = (opcode>>21)&0x1F; - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SELECT_PAIR, fprRegisterD, fprRegisterA, fprRegisterB, fprRegisterC); return true; } @@ -1727,10 +1741,10 @@ bool PPCRecompilerImlGen_PS_MERGE00(ppcImlGenContext_t* ppcImlGenContext, uint32 //float s1 = (float)hCPU->fpr[frB].fp0; //hCPU->fpr[frD].fp0 = s0; //hCPU->fpr[frD].fp1 = s1; - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - // unpcklpd + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + if( frA == frB ) { // simply duplicate bottom into bottom and top of destination register @@ -1754,9 +1768,9 @@ bool PPCRecompilerImlGen_PS_MERGE01(ppcImlGenContext_t* ppcImlGenContext, uint32 frD = (opcode>>21)&0x1F; // hCPU->fpr[frD].fp0 = hCPU->fpr[frA].fp0; // hCPU->fpr[frD].fp1 = hCPU->fpr[frB].fp1; - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); if( fprRegisterD != fprRegisterB ) PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_TOP_TO_TOP, fprRegisterD, fprRegisterB); @@ -1773,9 +1787,9 @@ bool PPCRecompilerImlGen_PS_MERGE10(ppcImlGenContext_t* ppcImlGenContext, uint32 frA = (opcode>>16)&0x1F; frD = (opcode>>21)&0x1F; - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); if( frA == frB ) { // swap bottom and top @@ -1811,9 +1825,9 @@ bool PPCRecompilerImlGen_PS_MERGE11(ppcImlGenContext_t* ppcImlGenContext, uint32 frA = (opcode>>16)&0x1F; frD = (opcode>>21)&0x1F; - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - uint32 fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); if( fprRegisterA == fprRegisterB ) { PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterA); @@ -1837,38 +1851,47 @@ bool PPCRecompilerImlGen_PS_MERGE11(ppcImlGenContext_t* ppcImlGenContext, uint32 bool PPCRecompilerImlGen_PS_CMPO0(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { + printf("PS_CMPO0: Not implemented\n"); + return false; + sint32 crfD, frA, frB; uint32 c=0; frB = (opcode>>11)&0x1F; frA = (opcode>>16)&0x1F; crfD = (opcode>>23)&0x7; - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_FCMPO_BOTTOM, fprRegisterA, fprRegisterB, crfD); return true; } bool PPCRecompilerImlGen_PS_CMPU0(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { + printf("PS_CMPU0: Not implemented\n"); + return false; + sint32 crfD, frA, frB; frB = (opcode >> 11) & 0x1F; frA = (opcode >> 16) & 0x1F; crfD = (opcode >> 23) & 0x7; - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frA); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frB); + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frA); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frB); PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_FCMPU_BOTTOM, fprRegisterA, fprRegisterB, crfD); return true; } bool PPCRecompilerImlGen_PS_CMPU1(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { + printf("PS_CMPU1: Not implemented\n"); + return false; + sint32 crfD, frA, frB; frB = (opcode >> 11) & 0x1F; frA = (opcode >> 16) & 0x1F; crfD = (opcode >> 23) & 0x7; - uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frA); - uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frB); + IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frA); + IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frB); PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_FCMPU_TOP, fprRegisterA, fprRegisterB, crfD); return true; } \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlOptimizer.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlOptimizer.cpp deleted file mode 100644 index 45e27664..00000000 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlOptimizer.cpp +++ /dev/null @@ -1,2175 +0,0 @@ -#include "../Interpreter/PPCInterpreterInternal.h" -#include "PPCRecompiler.h" -#include "PPCRecompilerIml.h" -#include "PPCRecompilerX64.h" - -void PPCRecompiler_checkRegisterUsage(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction, PPCImlOptimizerUsedRegisters_t* registersUsed) -{ - registersUsed->readNamedReg1 = -1; - registersUsed->readNamedReg2 = -1; - registersUsed->readNamedReg3 = -1; - registersUsed->writtenNamedReg1 = -1; - registersUsed->readFPR1 = -1; - registersUsed->readFPR2 = -1; - registersUsed->readFPR3 = -1; - registersUsed->readFPR4 = -1; - registersUsed->writtenFPR1 = -1; - if( imlInstruction->type == PPCREC_IML_TYPE_R_NAME ) - { - registersUsed->writtenNamedReg1 = imlInstruction->op_r_name.registerIndex; - } - else if( imlInstruction->type == PPCREC_IML_TYPE_NAME_R ) - { - registersUsed->readNamedReg1 = imlInstruction->op_r_name.registerIndex; - } - else if( imlInstruction->type == PPCREC_IML_TYPE_R_R ) - { - if (imlInstruction->operation == PPCREC_IML_OP_COMPARE_SIGNED || imlInstruction->operation == PPCREC_IML_OP_COMPARE_UNSIGNED || imlInstruction->operation == PPCREC_IML_OP_DCBZ) - { - // both operands are read only - registersUsed->readNamedReg1 = imlInstruction->op_r_r.registerResult; - registersUsed->readNamedReg2 = imlInstruction->op_r_r.registerA; - } - else if ( - imlInstruction->operation == PPCREC_IML_OP_OR || - imlInstruction->operation == PPCREC_IML_OP_AND || - imlInstruction->operation == PPCREC_IML_OP_XOR || - imlInstruction->operation == PPCREC_IML_OP_ADD || - imlInstruction->operation == PPCREC_IML_OP_ADD_CARRY || - imlInstruction->operation == PPCREC_IML_OP_ADD_CARRY_ME || - imlInstruction->operation == PPCREC_IML_OP_SUB_CARRY_UPDATE_CARRY) - { - // result is read and written, operand is read - registersUsed->writtenNamedReg1 = imlInstruction->op_r_r.registerResult; - registersUsed->readNamedReg1 = imlInstruction->op_r_r.registerResult; - registersUsed->readNamedReg2 = imlInstruction->op_r_r.registerA; - } - else if ( - imlInstruction->operation == PPCREC_IML_OP_ASSIGN || - imlInstruction->operation == PPCREC_IML_OP_ENDIAN_SWAP || - imlInstruction->operation == PPCREC_IML_OP_CNTLZW || - imlInstruction->operation == PPCREC_IML_OP_NOT || - imlInstruction->operation == PPCREC_IML_OP_NEG || - imlInstruction->operation == PPCREC_IML_OP_ASSIGN_S16_TO_S32 || - imlInstruction->operation == PPCREC_IML_OP_ASSIGN_S8_TO_S32) - { - // result is written, operand is read - registersUsed->writtenNamedReg1 = imlInstruction->op_r_r.registerResult; - registersUsed->readNamedReg1 = imlInstruction->op_r_r.registerA; - } - else - cemu_assert_unimplemented(); - } - else if (imlInstruction->type == PPCREC_IML_TYPE_R_S32) - { - if (imlInstruction->operation == PPCREC_IML_OP_COMPARE_SIGNED || imlInstruction->operation == PPCREC_IML_OP_COMPARE_UNSIGNED || imlInstruction->operation == PPCREC_IML_OP_MTCRF) - { - // operand register is read only - registersUsed->readNamedReg1 = imlInstruction->op_r_immS32.registerIndex; - } - else if (imlInstruction->operation == PPCREC_IML_OP_ADD || - imlInstruction->operation == PPCREC_IML_OP_SUB || - imlInstruction->operation == PPCREC_IML_OP_AND || - imlInstruction->operation == PPCREC_IML_OP_OR || - imlInstruction->operation == PPCREC_IML_OP_XOR || - imlInstruction->operation == PPCREC_IML_OP_LEFT_ROTATE) - { - // operand register is read and write - registersUsed->readNamedReg1 = imlInstruction->op_r_immS32.registerIndex; - registersUsed->writtenNamedReg1 = imlInstruction->op_r_immS32.registerIndex; - } - else - { - // operand register is write only - // todo - use explicit lists, avoid default cases - registersUsed->writtenNamedReg1 = imlInstruction->op_r_immS32.registerIndex; - } - } - else if (imlInstruction->type == PPCREC_IML_TYPE_CONDITIONAL_R_S32) - { - if (imlInstruction->operation == PPCREC_IML_OP_ASSIGN) - { - // result is written, but also considered read (in case the condition fails) - registersUsed->readNamedReg1 = imlInstruction->op_conditional_r_s32.registerIndex; - registersUsed->writtenNamedReg1 = imlInstruction->op_conditional_r_s32.registerIndex; - } - else - cemu_assert_unimplemented(); - } - else if( imlInstruction->type == PPCREC_IML_TYPE_R_R_S32 ) - { - if( imlInstruction->operation == PPCREC_IML_OP_RLWIMI ) - { - // result and operand register are both read, result is written - registersUsed->writtenNamedReg1 = imlInstruction->op_r_r_s32.registerResult; - registersUsed->readNamedReg1 = imlInstruction->op_r_r_s32.registerResult; - registersUsed->readNamedReg2 = imlInstruction->op_r_r_s32.registerA; - } - else - { - // result is write only and operand is read only - registersUsed->writtenNamedReg1 = imlInstruction->op_r_r_s32.registerResult; - registersUsed->readNamedReg1 = imlInstruction->op_r_r_s32.registerA; - } - } - else if( imlInstruction->type == PPCREC_IML_TYPE_R_R_R ) - { - // in all cases result is written and other operands are read only - registersUsed->writtenNamedReg1 = imlInstruction->op_r_r_r.registerResult; - registersUsed->readNamedReg1 = imlInstruction->op_r_r_r.registerA; - registersUsed->readNamedReg2 = imlInstruction->op_r_r_r.registerB; - } - else if( imlInstruction->type == PPCREC_IML_TYPE_CJUMP || imlInstruction->type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK ) - { - // no effect on registers - } - else if( imlInstruction->type == PPCREC_IML_TYPE_NO_OP ) - { - // no effect on registers - } - else if( imlInstruction->type == PPCREC_IML_TYPE_MACRO ) - { - if( imlInstruction->operation == PPCREC_IML_MACRO_BL || imlInstruction->operation == PPCREC_IML_MACRO_B_FAR || imlInstruction->operation == PPCREC_IML_MACRO_BLR || imlInstruction->operation == PPCREC_IML_MACRO_BLRL || imlInstruction->operation == PPCREC_IML_MACRO_BCTR || imlInstruction->operation == PPCREC_IML_MACRO_BCTRL || imlInstruction->operation == PPCREC_IML_MACRO_LEAVE || imlInstruction->operation == PPCREC_IML_MACRO_DEBUGBREAK || imlInstruction->operation == PPCREC_IML_MACRO_COUNT_CYCLES || imlInstruction->operation == PPCREC_IML_MACRO_HLE || imlInstruction->operation == PPCREC_IML_MACRO_MFTB ) - { - // no effect on registers - } - else - cemu_assert_unimplemented(); - } - else if (imlInstruction->type == PPCREC_IML_TYPE_LOAD) - { - registersUsed->writtenNamedReg1 = imlInstruction->op_storeLoad.registerData; - if (imlInstruction->op_storeLoad.registerMem != PPC_REC_INVALID_REGISTER) - registersUsed->readNamedReg1 = imlInstruction->op_storeLoad.registerMem; - } - else if (imlInstruction->type == PPCREC_IML_TYPE_MEM2MEM) - { - registersUsed->readNamedReg1 = imlInstruction->op_mem2mem.src.registerMem; - registersUsed->readNamedReg2 = imlInstruction->op_mem2mem.dst.registerMem; - } - else if( imlInstruction->type == PPCREC_IML_TYPE_LOAD_INDEXED ) - { - registersUsed->writtenNamedReg1 = imlInstruction->op_storeLoad.registerData; - if( imlInstruction->op_storeLoad.registerMem != PPC_REC_INVALID_REGISTER ) - registersUsed->readNamedReg1 = imlInstruction->op_storeLoad.registerMem; - if( imlInstruction->op_storeLoad.registerMem2 != PPC_REC_INVALID_REGISTER ) - registersUsed->readNamedReg2 = imlInstruction->op_storeLoad.registerMem2; - } - else if( imlInstruction->type == PPCREC_IML_TYPE_STORE ) - { - registersUsed->readNamedReg1 = imlInstruction->op_storeLoad.registerData; - if( imlInstruction->op_storeLoad.registerMem != PPC_REC_INVALID_REGISTER ) - registersUsed->readNamedReg2 = imlInstruction->op_storeLoad.registerMem; - } - else if( imlInstruction->type == PPCREC_IML_TYPE_STORE_INDEXED ) - { - registersUsed->readNamedReg1 = imlInstruction->op_storeLoad.registerData; - if( imlInstruction->op_storeLoad.registerMem != PPC_REC_INVALID_REGISTER ) - registersUsed->readNamedReg2 = imlInstruction->op_storeLoad.registerMem; - if( imlInstruction->op_storeLoad.registerMem2 != PPC_REC_INVALID_REGISTER ) - registersUsed->readNamedReg3 = imlInstruction->op_storeLoad.registerMem2; - } - else if( imlInstruction->type == PPCREC_IML_TYPE_CR ) - { - // only affects cr register - } - else if( imlInstruction->type == PPCREC_IML_TYPE_JUMPMARK ) - { - // no effect on registers - } - else if( imlInstruction->type == PPCREC_IML_TYPE_PPC_ENTER ) - { - // no op - } - else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R_NAME ) - { - // fpr operation - registersUsed->writtenFPR1 = imlInstruction->op_r_name.registerIndex; - } - else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_NAME_R ) - { - // fpr operation - registersUsed->readFPR1 = imlInstruction->op_r_name.registerIndex; - } - else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD ) - { - // fpr load operation - registersUsed->writtenFPR1 = imlInstruction->op_storeLoad.registerData; - // address is in gpr register - if (imlInstruction->op_storeLoad.registerMem != PPC_REC_INVALID_REGISTER) - registersUsed->readNamedReg1 = imlInstruction->op_storeLoad.registerMem; - // determine partially written result - switch (imlInstruction->op_storeLoad.mode) - { - case PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0: - case PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1: - cemu_assert_debug(imlInstruction->op_storeLoad.registerGQR != PPC_REC_INVALID_REGISTER); - registersUsed->readNamedReg2 = imlInstruction->op_storeLoad.registerGQR; - break; - case PPCREC_FPR_LD_MODE_DOUBLE_INTO_PS0: - // PS1 remains the same - registersUsed->readFPR4 = imlInstruction->op_storeLoad.registerData; - break; - case PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1: - case PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0_PS1: - case PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0: - case PPCREC_FPR_LD_MODE_PSQ_S16_PS0: - case PPCREC_FPR_LD_MODE_PSQ_S16_PS0_PS1: - case PPCREC_FPR_LD_MODE_PSQ_U16_PS0_PS1: - case PPCREC_FPR_LD_MODE_PSQ_U16_PS0: - case PPCREC_FPR_LD_MODE_PSQ_S8_PS0_PS1: - case PPCREC_FPR_LD_MODE_PSQ_U8_PS0_PS1: - case PPCREC_FPR_LD_MODE_PSQ_U8_PS0: - case PPCREC_FPR_LD_MODE_PSQ_S8_PS0: - break; - default: - cemu_assert_unimplemented(); - } - } - else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED ) - { - // fpr load operation - registersUsed->writtenFPR1 = imlInstruction->op_storeLoad.registerData; - // address is in gpr registers - if (imlInstruction->op_storeLoad.registerMem != PPC_REC_INVALID_REGISTER) - registersUsed->readNamedReg1 = imlInstruction->op_storeLoad.registerMem; - if (imlInstruction->op_storeLoad.registerMem2 != PPC_REC_INVALID_REGISTER) - registersUsed->readNamedReg2 = imlInstruction->op_storeLoad.registerMem2; - // determine partially written result - switch (imlInstruction->op_storeLoad.mode) - { - case PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0: - case PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1: - cemu_assert_debug(imlInstruction->op_storeLoad.registerGQR != PPC_REC_INVALID_REGISTER); - registersUsed->readNamedReg3 = imlInstruction->op_storeLoad.registerGQR; - break; - case PPCREC_FPR_LD_MODE_DOUBLE_INTO_PS0: - // PS1 remains the same - registersUsed->readFPR4 = imlInstruction->op_storeLoad.registerData; - break; - case PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1: - case PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0_PS1: - case PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0: - case PPCREC_FPR_LD_MODE_PSQ_S16_PS0: - case PPCREC_FPR_LD_MODE_PSQ_S16_PS0_PS1: - case PPCREC_FPR_LD_MODE_PSQ_U16_PS0_PS1: - case PPCREC_FPR_LD_MODE_PSQ_U16_PS0: - case PPCREC_FPR_LD_MODE_PSQ_S8_PS0_PS1: - case PPCREC_FPR_LD_MODE_PSQ_U8_PS0_PS1: - case PPCREC_FPR_LD_MODE_PSQ_U8_PS0: - break; - default: - cemu_assert_unimplemented(); - } - } - else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE ) - { - // fpr store operation - registersUsed->readFPR1 = imlInstruction->op_storeLoad.registerData; - if( imlInstruction->op_storeLoad.registerMem != PPC_REC_INVALID_REGISTER ) - registersUsed->readNamedReg1 = imlInstruction->op_storeLoad.registerMem; - // PSQ generic stores also access GQR - switch (imlInstruction->op_storeLoad.mode) - { - case PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0: - case PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1: - cemu_assert_debug(imlInstruction->op_storeLoad.registerGQR != PPC_REC_INVALID_REGISTER); - registersUsed->readNamedReg2 = imlInstruction->op_storeLoad.registerGQR; - break; - default: - break; - } - } - else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE_INDEXED ) - { - // fpr store operation - registersUsed->readFPR1 = imlInstruction->op_storeLoad.registerData; - // address is in gpr registers - if( imlInstruction->op_storeLoad.registerMem != PPC_REC_INVALID_REGISTER ) - registersUsed->readNamedReg1 = imlInstruction->op_storeLoad.registerMem; - if( imlInstruction->op_storeLoad.registerMem2 != PPC_REC_INVALID_REGISTER ) - registersUsed->readNamedReg2 = imlInstruction->op_storeLoad.registerMem2; - // PSQ generic stores also access GQR - switch (imlInstruction->op_storeLoad.mode) - { - case PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0: - case PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1: - cemu_assert_debug(imlInstruction->op_storeLoad.registerGQR != PPC_REC_INVALID_REGISTER); - registersUsed->readNamedReg3 = imlInstruction->op_storeLoad.registerGQR; - break; - default: - break; - } - } - else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R ) - { - // fpr operation - if( imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP || - imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM_AND_TOP || - imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_BOTTOM_AND_TOP_SWAPPED || - imlInstruction->operation == PPCREC_IML_OP_ASSIGN || - imlInstruction->operation == PPCREC_IML_OP_FPR_BOTTOM_FRES_TO_BOTTOM_AND_TOP || - imlInstruction->operation == PPCREC_IML_OP_FPR_NEGATE_PAIR || - imlInstruction->operation == PPCREC_IML_OP_FPR_ABS_PAIR || - imlInstruction->operation == PPCREC_IML_OP_FPR_FRES_PAIR || - imlInstruction->operation == PPCREC_IML_OP_FPR_FRSQRTE_PAIR ) - { - // operand read, result written - registersUsed->readFPR1 = imlInstruction->op_fpr_r_r.registerOperand; - registersUsed->writtenFPR1 = imlInstruction->op_fpr_r_r.registerResult; - } - else if( - imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM || - imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_TOP || - imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_TOP_TO_TOP || - imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM || - imlInstruction->operation == PPCREC_IML_OP_FPR_EXPAND_BOTTOM32_TO_BOTTOM64_AND_TOP64 || - imlInstruction->operation == PPCREC_IML_OP_FPR_BOTTOM_FCTIWZ || - imlInstruction->operation == PPCREC_IML_OP_FPR_BOTTOM_RECIPROCAL_SQRT - ) - { - // operand read, result read and (partially) written - registersUsed->readFPR1 = imlInstruction->op_fpr_r_r.registerOperand; - registersUsed->readFPR4 = imlInstruction->op_fpr_r_r.registerResult; - registersUsed->writtenFPR1 = imlInstruction->op_fpr_r_r.registerResult; - } - else if( imlInstruction->operation == PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM || - imlInstruction->operation == PPCREC_IML_OP_FPR_MULTIPLY_PAIR || - imlInstruction->operation == PPCREC_IML_OP_FPR_DIVIDE_BOTTOM || - imlInstruction->operation == PPCREC_IML_OP_FPR_DIVIDE_PAIR || - imlInstruction->operation == PPCREC_IML_OP_FPR_ADD_BOTTOM || - imlInstruction->operation == PPCREC_IML_OP_FPR_ADD_PAIR || - imlInstruction->operation == PPCREC_IML_OP_FPR_SUB_PAIR || - imlInstruction->operation == PPCREC_IML_OP_FPR_SUB_BOTTOM ) - { - // operand read, result read and written - registersUsed->readFPR1 = imlInstruction->op_fpr_r_r.registerOperand; - registersUsed->readFPR2 = imlInstruction->op_fpr_r_r.registerResult; - registersUsed->writtenFPR1 = imlInstruction->op_fpr_r_r.registerResult; - - } - else if(imlInstruction->operation == PPCREC_IML_OP_FPR_FCMPU_BOTTOM || - imlInstruction->operation == PPCREC_IML_OP_FPR_FCMPU_TOP || - imlInstruction->operation == PPCREC_IML_OP_FPR_FCMPO_BOTTOM) - { - // operand read, result read - registersUsed->readFPR1 = imlInstruction->op_fpr_r_r.registerOperand; - registersUsed->readFPR2 = imlInstruction->op_fpr_r_r.registerResult; - } - else - cemu_assert_unimplemented(); - } - else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R ) - { - // fpr operation - registersUsed->readFPR1 = imlInstruction->op_fpr_r_r_r.registerOperandA; - registersUsed->readFPR2 = imlInstruction->op_fpr_r_r_r.registerOperandB; - registersUsed->writtenFPR1 = imlInstruction->op_fpr_r_r_r.registerResult; - // handle partially written result - switch (imlInstruction->operation) - { - case PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM: - case PPCREC_IML_OP_FPR_ADD_BOTTOM: - case PPCREC_IML_OP_FPR_SUB_BOTTOM: - registersUsed->readFPR4 = imlInstruction->op_fpr_r_r_r.registerResult; - break; - case PPCREC_IML_OP_FPR_SUB_PAIR: - break; - default: - cemu_assert_unimplemented(); - } - } - else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R_R ) - { - // fpr operation - registersUsed->readFPR1 = imlInstruction->op_fpr_r_r_r_r.registerOperandA; - registersUsed->readFPR2 = imlInstruction->op_fpr_r_r_r_r.registerOperandB; - registersUsed->readFPR3 = imlInstruction->op_fpr_r_r_r_r.registerOperandC; - registersUsed->writtenFPR1 = imlInstruction->op_fpr_r_r_r_r.registerResult; - // handle partially written result - switch (imlInstruction->operation) - { - case PPCREC_IML_OP_FPR_SELECT_BOTTOM: - registersUsed->readFPR4 = imlInstruction->op_fpr_r_r_r_r.registerResult; - break; - case PPCREC_IML_OP_FPR_SUM0: - case PPCREC_IML_OP_FPR_SUM1: - case PPCREC_IML_OP_FPR_SELECT_PAIR: - break; - default: - cemu_assert_unimplemented(); - } - } - else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R ) - { - // fpr operation - if( imlInstruction->operation == PPCREC_IML_OP_FPR_NEGATE_BOTTOM || - imlInstruction->operation == PPCREC_IML_OP_FPR_ABS_BOTTOM || - imlInstruction->operation == PPCREC_IML_OP_FPR_NEGATIVE_ABS_BOTTOM || - imlInstruction->operation == PPCREC_IML_OP_FPR_EXPAND_BOTTOM32_TO_BOTTOM64_AND_TOP64 || - imlInstruction->operation == PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_BOTTOM || - imlInstruction->operation == PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_PAIR ) - { - registersUsed->readFPR1 = imlInstruction->op_fpr_r.registerResult; - registersUsed->writtenFPR1 = imlInstruction->op_fpr_r.registerResult; - } - else - cemu_assert_unimplemented(); - } - else - { - cemu_assert_unimplemented(); - } -} - -#define replaceRegister(__x,__r,__n) (((__x)==(__r))?(__n):(__x)) - -sint32 replaceRegisterMultiple(sint32 reg, sint32 match[4], sint32 replaced[4]) -{ - for (sint32 i = 0; i < 4; i++) - { - if(match[i] < 0) - continue; - if (reg == match[i]) - { - return replaced[i]; - } - } - return reg; -} - -void PPCRecompiler_replaceGPRRegisterUsageMultiple(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction, sint32 gprRegisterSearched[4], sint32 gprRegisterReplaced[4]) -{ - if (imlInstruction->type == PPCREC_IML_TYPE_R_NAME) - { - imlInstruction->op_r_name.registerIndex = replaceRegisterMultiple(imlInstruction->op_r_name.registerIndex, gprRegisterSearched, gprRegisterReplaced); - } - else if (imlInstruction->type == PPCREC_IML_TYPE_NAME_R) - { - imlInstruction->op_r_name.registerIndex = replaceRegisterMultiple(imlInstruction->op_r_name.registerIndex, gprRegisterSearched, gprRegisterReplaced); - } - else if (imlInstruction->type == PPCREC_IML_TYPE_R_R) - { - imlInstruction->op_r_r.registerResult = replaceRegisterMultiple(imlInstruction->op_r_r.registerResult, gprRegisterSearched, gprRegisterReplaced); - imlInstruction->op_r_r.registerA = replaceRegisterMultiple(imlInstruction->op_r_r.registerA, gprRegisterSearched, gprRegisterReplaced); - } - else if (imlInstruction->type == PPCREC_IML_TYPE_R_S32) - { - imlInstruction->op_r_immS32.registerIndex = replaceRegisterMultiple(imlInstruction->op_r_immS32.registerIndex, gprRegisterSearched, gprRegisterReplaced); - } - else if (imlInstruction->type == PPCREC_IML_TYPE_CONDITIONAL_R_S32) - { - imlInstruction->op_conditional_r_s32.registerIndex = replaceRegisterMultiple(imlInstruction->op_conditional_r_s32.registerIndex, gprRegisterSearched, gprRegisterReplaced); - } - else if (imlInstruction->type == PPCREC_IML_TYPE_R_R_S32) - { - // in all cases result is written and other operand is read only - imlInstruction->op_r_r_s32.registerResult = replaceRegisterMultiple(imlInstruction->op_r_r_s32.registerResult, gprRegisterSearched, gprRegisterReplaced); - imlInstruction->op_r_r_s32.registerA = replaceRegisterMultiple(imlInstruction->op_r_r_s32.registerA, gprRegisterSearched, gprRegisterReplaced); - } - else if (imlInstruction->type == PPCREC_IML_TYPE_R_R_R) - { - // in all cases result is written and other operands are read only - imlInstruction->op_r_r_r.registerResult = replaceRegisterMultiple(imlInstruction->op_r_r_r.registerResult, gprRegisterSearched, gprRegisterReplaced); - imlInstruction->op_r_r_r.registerA = replaceRegisterMultiple(imlInstruction->op_r_r_r.registerA, gprRegisterSearched, gprRegisterReplaced); - imlInstruction->op_r_r_r.registerB = replaceRegisterMultiple(imlInstruction->op_r_r_r.registerB, gprRegisterSearched, gprRegisterReplaced); - } - else if (imlInstruction->type == PPCREC_IML_TYPE_CJUMP || imlInstruction->type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK) - { - // no effect on registers - } - else if (imlInstruction->type == PPCREC_IML_TYPE_NO_OP) - { - // no effect on registers - } - else if (imlInstruction->type == PPCREC_IML_TYPE_MACRO) - { - if (imlInstruction->operation == PPCREC_IML_MACRO_BL || imlInstruction->operation == PPCREC_IML_MACRO_B_FAR || imlInstruction->operation == PPCREC_IML_MACRO_BLR || imlInstruction->operation == PPCREC_IML_MACRO_BLRL || imlInstruction->operation == PPCREC_IML_MACRO_BCTR || imlInstruction->operation == PPCREC_IML_MACRO_BCTRL || imlInstruction->operation == PPCREC_IML_MACRO_LEAVE || imlInstruction->operation == PPCREC_IML_MACRO_DEBUGBREAK || imlInstruction->operation == PPCREC_IML_MACRO_HLE || imlInstruction->operation == PPCREC_IML_MACRO_MFTB || imlInstruction->operation == PPCREC_IML_MACRO_COUNT_CYCLES ) - { - // no effect on registers - } - else - { - cemu_assert_unimplemented(); - } - } - else if (imlInstruction->type == PPCREC_IML_TYPE_LOAD) - { - imlInstruction->op_storeLoad.registerData = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerData, gprRegisterSearched, gprRegisterReplaced); - if (imlInstruction->op_storeLoad.registerMem != PPC_REC_INVALID_REGISTER) - { - imlInstruction->op_storeLoad.registerMem = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerMem, gprRegisterSearched, gprRegisterReplaced); - } - } - else if (imlInstruction->type == PPCREC_IML_TYPE_LOAD_INDEXED) - { - imlInstruction->op_storeLoad.registerData = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerData, gprRegisterSearched, gprRegisterReplaced); - if (imlInstruction->op_storeLoad.registerMem != PPC_REC_INVALID_REGISTER) - imlInstruction->op_storeLoad.registerMem = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerMem, gprRegisterSearched, gprRegisterReplaced); - if (imlInstruction->op_storeLoad.registerMem2 != PPC_REC_INVALID_REGISTER) - imlInstruction->op_storeLoad.registerMem2 = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerMem2, gprRegisterSearched, gprRegisterReplaced); - } - else if (imlInstruction->type == PPCREC_IML_TYPE_STORE) - { - imlInstruction->op_storeLoad.registerData = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerData, gprRegisterSearched, gprRegisterReplaced); - if (imlInstruction->op_storeLoad.registerMem != PPC_REC_INVALID_REGISTER) - imlInstruction->op_storeLoad.registerMem = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerMem, gprRegisterSearched, gprRegisterReplaced); - } - else if (imlInstruction->type == PPCREC_IML_TYPE_STORE_INDEXED) - { - imlInstruction->op_storeLoad.registerData = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerData, gprRegisterSearched, gprRegisterReplaced); - if (imlInstruction->op_storeLoad.registerMem != PPC_REC_INVALID_REGISTER) - imlInstruction->op_storeLoad.registerMem = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerMem, gprRegisterSearched, gprRegisterReplaced); - if (imlInstruction->op_storeLoad.registerMem2 != PPC_REC_INVALID_REGISTER) - imlInstruction->op_storeLoad.registerMem2 = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerMem2, gprRegisterSearched, gprRegisterReplaced); - } - else if (imlInstruction->type == PPCREC_IML_TYPE_CR) - { - // only affects cr register - } - else if (imlInstruction->type == PPCREC_IML_TYPE_JUMPMARK) - { - // no effect on registers - } - else if (imlInstruction->type == PPCREC_IML_TYPE_PPC_ENTER) - { - // no op - } - else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_NAME) - { - - } - else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_NAME_R) - { - - } - else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD) - { - if (imlInstruction->op_storeLoad.registerMem != PPC_REC_INVALID_REGISTER) - { - imlInstruction->op_storeLoad.registerMem = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerMem, gprRegisterSearched, gprRegisterReplaced); - } - if (imlInstruction->op_storeLoad.registerGQR != PPC_REC_INVALID_REGISTER) - { - imlInstruction->op_storeLoad.registerGQR = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerGQR, gprRegisterSearched, gprRegisterReplaced); - } - } - else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED) - { - if (imlInstruction->op_storeLoad.registerMem != PPC_REC_INVALID_REGISTER) - { - imlInstruction->op_storeLoad.registerMem = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerMem, gprRegisterSearched, gprRegisterReplaced); - } - if (imlInstruction->op_storeLoad.registerMem2 != PPC_REC_INVALID_REGISTER) - { - imlInstruction->op_storeLoad.registerMem2 = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerMem2, gprRegisterSearched, gprRegisterReplaced); - } - if (imlInstruction->op_storeLoad.registerGQR != PPC_REC_INVALID_REGISTER) - { - imlInstruction->op_storeLoad.registerGQR = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerGQR, gprRegisterSearched, gprRegisterReplaced); - } - } - else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE) - { - if (imlInstruction->op_storeLoad.registerMem != PPC_REC_INVALID_REGISTER) - { - imlInstruction->op_storeLoad.registerMem = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerMem, gprRegisterSearched, gprRegisterReplaced); - } - if (imlInstruction->op_storeLoad.registerGQR != PPC_REC_INVALID_REGISTER) - { - imlInstruction->op_storeLoad.registerGQR = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerGQR, gprRegisterSearched, gprRegisterReplaced); - } - } - else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE_INDEXED) - { - if (imlInstruction->op_storeLoad.registerMem != PPC_REC_INVALID_REGISTER) - { - imlInstruction->op_storeLoad.registerMem = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerMem, gprRegisterSearched, gprRegisterReplaced); - } - if (imlInstruction->op_storeLoad.registerMem2 != PPC_REC_INVALID_REGISTER) - { - imlInstruction->op_storeLoad.registerMem2 = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerMem2, gprRegisterSearched, gprRegisterReplaced); - } - if (imlInstruction->op_storeLoad.registerGQR != PPC_REC_INVALID_REGISTER) - { - imlInstruction->op_storeLoad.registerGQR = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerGQR, gprRegisterSearched, gprRegisterReplaced); - } - } - else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R) - { - } - else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R) - { - } - else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R_R) - { - } - else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R) - { - } - else - { - cemu_assert_unimplemented(); - } -} - -void PPCRecompiler_replaceFPRRegisterUsageMultiple(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction, sint32 fprRegisterSearched[4], sint32 fprRegisterReplaced[4]) -{ - if (imlInstruction->type == PPCREC_IML_TYPE_R_NAME) - { - // not affected - } - else if (imlInstruction->type == PPCREC_IML_TYPE_NAME_R) - { - // not affected - } - else if (imlInstruction->type == PPCREC_IML_TYPE_R_R) - { - // not affected - } - else if (imlInstruction->type == PPCREC_IML_TYPE_R_S32) - { - // not affected - } - else if (imlInstruction->type == PPCREC_IML_TYPE_R_R_S32) - { - // not affected - } - else if (imlInstruction->type == PPCREC_IML_TYPE_R_R_R) - { - // not affected - } - else if (imlInstruction->type == PPCREC_IML_TYPE_CJUMP || imlInstruction->type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK) - { - // no effect on registers - } - else if (imlInstruction->type == PPCREC_IML_TYPE_NO_OP) - { - // no effect on registers - } - else if (imlInstruction->type == PPCREC_IML_TYPE_MACRO) - { - // not affected - } - else if (imlInstruction->type == PPCREC_IML_TYPE_LOAD) - { - // not affected - } - else if (imlInstruction->type == PPCREC_IML_TYPE_MEM2MEM) - { - // not affected - } - else if (imlInstruction->type == PPCREC_IML_TYPE_LOAD_INDEXED) - { - // not affected - } - else if (imlInstruction->type == PPCREC_IML_TYPE_STORE) - { - // not affected - } - else if (imlInstruction->type == PPCREC_IML_TYPE_STORE_INDEXED) - { - // not affected - } - else if (imlInstruction->type == PPCREC_IML_TYPE_CR) - { - // only affects cr register - } - else if (imlInstruction->type == PPCREC_IML_TYPE_JUMPMARK) - { - // no effect on registers - } - else if (imlInstruction->type == PPCREC_IML_TYPE_PPC_ENTER) - { - // no op - } - else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_NAME) - { - imlInstruction->op_r_name.registerIndex = replaceRegisterMultiple(imlInstruction->op_r_name.registerIndex, fprRegisterSearched, fprRegisterReplaced); - } - else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_NAME_R) - { - imlInstruction->op_r_name.registerIndex = replaceRegisterMultiple(imlInstruction->op_r_name.registerIndex, fprRegisterSearched, fprRegisterReplaced); - } - else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD) - { - imlInstruction->op_storeLoad.registerData = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerData, fprRegisterSearched, fprRegisterReplaced); - } - else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED) - { - imlInstruction->op_storeLoad.registerData = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerData, fprRegisterSearched, fprRegisterReplaced); - } - else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE) - { - imlInstruction->op_storeLoad.registerData = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerData, fprRegisterSearched, fprRegisterReplaced); - } - else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE_INDEXED) - { - imlInstruction->op_storeLoad.registerData = replaceRegisterMultiple(imlInstruction->op_storeLoad.registerData, fprRegisterSearched, fprRegisterReplaced); - } - else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R) - { - imlInstruction->op_fpr_r_r.registerResult = replaceRegisterMultiple(imlInstruction->op_fpr_r_r.registerResult, fprRegisterSearched, fprRegisterReplaced); - imlInstruction->op_fpr_r_r.registerOperand = replaceRegisterMultiple(imlInstruction->op_fpr_r_r.registerOperand, fprRegisterSearched, fprRegisterReplaced); - } - else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R) - { - imlInstruction->op_fpr_r_r_r.registerResult = replaceRegisterMultiple(imlInstruction->op_fpr_r_r_r.registerResult, fprRegisterSearched, fprRegisterReplaced); - imlInstruction->op_fpr_r_r_r.registerOperandA = replaceRegisterMultiple(imlInstruction->op_fpr_r_r_r.registerOperandA, fprRegisterSearched, fprRegisterReplaced); - imlInstruction->op_fpr_r_r_r.registerOperandB = replaceRegisterMultiple(imlInstruction->op_fpr_r_r_r.registerOperandB, fprRegisterSearched, fprRegisterReplaced); - } - else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R_R) - { - imlInstruction->op_fpr_r_r_r_r.registerResult = replaceRegisterMultiple(imlInstruction->op_fpr_r_r_r_r.registerResult, fprRegisterSearched, fprRegisterReplaced); - imlInstruction->op_fpr_r_r_r_r.registerOperandA = replaceRegisterMultiple(imlInstruction->op_fpr_r_r_r_r.registerOperandA, fprRegisterSearched, fprRegisterReplaced); - imlInstruction->op_fpr_r_r_r_r.registerOperandB = replaceRegisterMultiple(imlInstruction->op_fpr_r_r_r_r.registerOperandB, fprRegisterSearched, fprRegisterReplaced); - imlInstruction->op_fpr_r_r_r_r.registerOperandC = replaceRegisterMultiple(imlInstruction->op_fpr_r_r_r_r.registerOperandC, fprRegisterSearched, fprRegisterReplaced); - } - else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R) - { - imlInstruction->op_fpr_r.registerResult = replaceRegisterMultiple(imlInstruction->op_fpr_r.registerResult, fprRegisterSearched, fprRegisterReplaced); - } - else - { - cemu_assert_unimplemented(); - } -} - -void PPCRecompiler_replaceFPRRegisterUsage(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction, sint32 fprRegisterSearched, sint32 fprRegisterReplaced) -{ - if( imlInstruction->type == PPCREC_IML_TYPE_R_NAME ) - { - // not affected - } - else if( imlInstruction->type == PPCREC_IML_TYPE_NAME_R ) - { - // not affected - } - else if( imlInstruction->type == PPCREC_IML_TYPE_R_R ) - { - // not affected - } - else if( imlInstruction->type == PPCREC_IML_TYPE_R_S32 ) - { - // not affected - } - else if( imlInstruction->type == PPCREC_IML_TYPE_R_R_S32 ) - { - // not affected - } - else if( imlInstruction->type == PPCREC_IML_TYPE_R_R_R ) - { - // not affected - } - else if( imlInstruction->type == PPCREC_IML_TYPE_CJUMP || imlInstruction->type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK ) - { - // no effect on registers - } - else if( imlInstruction->type == PPCREC_IML_TYPE_NO_OP ) - { - // no effect on registers - } - else if( imlInstruction->type == PPCREC_IML_TYPE_MACRO ) - { - // not affected - } - else if( imlInstruction->type == PPCREC_IML_TYPE_LOAD ) - { - // not affected - } - else if (imlInstruction->type == PPCREC_IML_TYPE_MEM2MEM) - { - // not affected - } - else if( imlInstruction->type == PPCREC_IML_TYPE_LOAD_INDEXED ) - { - // not affected - } - else if( imlInstruction->type == PPCREC_IML_TYPE_STORE ) - { - // not affected - } - else if( imlInstruction->type == PPCREC_IML_TYPE_STORE_INDEXED ) - { - // not affected - } - else if( imlInstruction->type == PPCREC_IML_TYPE_CR ) - { - // only affects cr register - } - else if( imlInstruction->type == PPCREC_IML_TYPE_JUMPMARK ) - { - // no effect on registers - } - else if( imlInstruction->type == PPCREC_IML_TYPE_PPC_ENTER ) - { - // no op - } - else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R_NAME ) - { - imlInstruction->op_r_name.registerIndex = replaceRegister(imlInstruction->op_r_name.registerIndex, fprRegisterSearched, fprRegisterReplaced); - } - else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_NAME_R ) - { - imlInstruction->op_r_name.registerIndex = replaceRegister(imlInstruction->op_r_name.registerIndex, fprRegisterSearched, fprRegisterReplaced); - } - else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD ) - { - imlInstruction->op_storeLoad.registerData = replaceRegister(imlInstruction->op_storeLoad.registerData, fprRegisterSearched, fprRegisterReplaced); - } - else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED ) - { - imlInstruction->op_storeLoad.registerData = replaceRegister(imlInstruction->op_storeLoad.registerData, fprRegisterSearched, fprRegisterReplaced); - } - else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE ) - { - imlInstruction->op_storeLoad.registerData = replaceRegister(imlInstruction->op_storeLoad.registerData, fprRegisterSearched, fprRegisterReplaced); - } - else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE_INDEXED ) - { - imlInstruction->op_storeLoad.registerData = replaceRegister(imlInstruction->op_storeLoad.registerData, fprRegisterSearched, fprRegisterReplaced); - } - else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R ) - { - imlInstruction->op_fpr_r_r.registerResult = replaceRegister(imlInstruction->op_fpr_r_r.registerResult, fprRegisterSearched, fprRegisterReplaced); - imlInstruction->op_fpr_r_r.registerOperand = replaceRegister(imlInstruction->op_fpr_r_r.registerOperand, fprRegisterSearched, fprRegisterReplaced); - } - else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R ) - { - imlInstruction->op_fpr_r_r_r.registerResult = replaceRegister(imlInstruction->op_fpr_r_r_r.registerResult, fprRegisterSearched, fprRegisterReplaced); - imlInstruction->op_fpr_r_r_r.registerOperandA = replaceRegister(imlInstruction->op_fpr_r_r_r.registerOperandA, fprRegisterSearched, fprRegisterReplaced); - imlInstruction->op_fpr_r_r_r.registerOperandB = replaceRegister(imlInstruction->op_fpr_r_r_r.registerOperandB, fprRegisterSearched, fprRegisterReplaced); - } - else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R_R ) - { - imlInstruction->op_fpr_r_r_r_r.registerResult = replaceRegister(imlInstruction->op_fpr_r_r_r_r.registerResult, fprRegisterSearched, fprRegisterReplaced); - imlInstruction->op_fpr_r_r_r_r.registerOperandA = replaceRegister(imlInstruction->op_fpr_r_r_r_r.registerOperandA, fprRegisterSearched, fprRegisterReplaced); - imlInstruction->op_fpr_r_r_r_r.registerOperandB = replaceRegister(imlInstruction->op_fpr_r_r_r_r.registerOperandB, fprRegisterSearched, fprRegisterReplaced); - imlInstruction->op_fpr_r_r_r_r.registerOperandC = replaceRegister(imlInstruction->op_fpr_r_r_r_r.registerOperandC, fprRegisterSearched, fprRegisterReplaced); - } - else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R ) - { - imlInstruction->op_fpr_r.registerResult = replaceRegister(imlInstruction->op_fpr_r.registerResult, fprRegisterSearched, fprRegisterReplaced); - } - else - { - cemu_assert_unimplemented(); - } -} - -typedef struct -{ - struct - { - sint32 instructionIndex; - sint32 registerPreviousName; - sint32 registerNewName; - sint32 index; // new index - sint32 previousIndex; // previous index (always out of range) - bool nameMustBeMaintained; // must be stored before replacement and loaded after replacement ends - }replacedRegisterEntry[PPC_X64_GPR_USABLE_REGISTERS]; - sint32 count; -}replacedRegisterTracker_t; - -bool PPCRecompiler_checkIfGPRRegisterIsAccessed(PPCImlOptimizerUsedRegisters_t* registersUsed, sint32 gprRegister) -{ - if( registersUsed->readNamedReg1 == gprRegister ) - return true; - if( registersUsed->readNamedReg2 == gprRegister ) - return true; - if( registersUsed->readNamedReg3 == gprRegister ) - return true; - if( registersUsed->writtenNamedReg1 == gprRegister ) - return true; - return false; -} - -/* - * Returns index of register to replace - * If no register needs to be replaced, -1 is returned - */ -sint32 PPCRecompiler_getNextRegisterToReplace(PPCImlOptimizerUsedRegisters_t* registersUsed) -{ - // get index of register to replace - sint32 gprToReplace = -1; - if( registersUsed->readNamedReg1 >= PPC_X64_GPR_USABLE_REGISTERS ) - gprToReplace = registersUsed->readNamedReg1; - else if( registersUsed->readNamedReg2 >= PPC_X64_GPR_USABLE_REGISTERS ) - gprToReplace = registersUsed->readNamedReg2; - else if( registersUsed->readNamedReg3 >= PPC_X64_GPR_USABLE_REGISTERS ) - gprToReplace = registersUsed->readNamedReg3; - else if( registersUsed->writtenNamedReg1 >= PPC_X64_GPR_USABLE_REGISTERS ) - gprToReplace = registersUsed->writtenNamedReg1; - // return - return gprToReplace; -} - -bool PPCRecompiler_findAvailableRegisterDepr(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment, sint32 imlIndexStart, replacedRegisterTracker_t* replacedRegisterTracker, sint32* registerIndex, sint32* registerName, bool* isUsed) -{ - PPCImlOptimizerUsedRegisters_t registersUsed; - PPCRecompiler_checkRegisterUsage(ppcImlGenContext, imlSegment->imlList+imlIndexStart, ®istersUsed); - // mask all registers used by this instruction - uint32 instructionReservedRegisterMask = 0;//(1<<(PPC_X64_GPR_USABLE_REGISTERS+1))-1; - if( registersUsed.readNamedReg1 != -1 ) - instructionReservedRegisterMask |= (1<<(registersUsed.readNamedReg1)); - if( registersUsed.readNamedReg2 != -1 ) - instructionReservedRegisterMask |= (1<<(registersUsed.readNamedReg2)); - if( registersUsed.readNamedReg3 != -1 ) - instructionReservedRegisterMask |= (1<<(registersUsed.readNamedReg3)); - if( registersUsed.writtenNamedReg1 != -1 ) - instructionReservedRegisterMask |= (1<<(registersUsed.writtenNamedReg1)); - // mask all registers that are reserved for other replacements - uint32 replacementReservedRegisterMask = 0; - for(sint32 i=0; i<replacedRegisterTracker->count; i++) - { - replacementReservedRegisterMask |= (1<<replacedRegisterTracker->replacedRegisterEntry[i].index); - } - - // potential improvement: Scan ahead a few instructions and look for registers that are the least used (or ideally never used) - - // pick available register - const uint32 allRegisterMask = (1<<(PPC_X64_GPR_USABLE_REGISTERS+1))-1; // mask with set bit for every register - uint32 reservedRegisterMask = instructionReservedRegisterMask | replacementReservedRegisterMask; - cemu_assert(instructionReservedRegisterMask != allRegisterMask); // no usable register! (Need to store a register from the replacedRegisterTracker) - sint32 usedRegisterIndex = -1; - for(sint32 i=0; i<PPC_X64_GPR_USABLE_REGISTERS; i++) - { - if( (reservedRegisterMask&(1<<i)) == 0 ) - { - if( (instructionReservedRegisterMask&(1<<i)) == 0 && ppcImlGenContext->mappedRegister[i] != -1 ) - { - // register is reserved by segment -> In use - *isUsed = true; - *registerName = ppcImlGenContext->mappedRegister[i]; - } - else - { - *isUsed = false; - *registerName = -1; - } - *registerIndex = i; - return true; - } - } - return false; - -} - -bool PPCRecompiler_hasSuffixInstruction(PPCRecImlSegment_t* imlSegment) -{ - if( imlSegment->imlListCount == 0 ) - return false; - PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList+imlSegment->imlListCount-1; - if( imlInstruction->type == PPCREC_IML_TYPE_MACRO && (imlInstruction->operation == PPCREC_IML_MACRO_BLR || imlInstruction->operation == PPCREC_IML_MACRO_BCTR) || - imlInstruction->type == PPCREC_IML_TYPE_MACRO && imlInstruction->operation == PPCREC_IML_MACRO_BL || - imlInstruction->type == PPCREC_IML_TYPE_MACRO && imlInstruction->operation == PPCREC_IML_MACRO_B_FAR || - imlInstruction->type == PPCREC_IML_TYPE_MACRO && imlInstruction->operation == PPCREC_IML_MACRO_BLRL || - imlInstruction->type == PPCREC_IML_TYPE_MACRO && imlInstruction->operation == PPCREC_IML_MACRO_BCTRL || - imlInstruction->type == PPCREC_IML_TYPE_MACRO && imlInstruction->operation == PPCREC_IML_MACRO_LEAVE || - imlInstruction->type == PPCREC_IML_TYPE_MACRO && imlInstruction->operation == PPCREC_IML_MACRO_HLE || - imlInstruction->type == PPCREC_IML_TYPE_MACRO && imlInstruction->operation == PPCREC_IML_MACRO_MFTB || - imlInstruction->type == PPCREC_IML_TYPE_PPC_ENTER || - imlInstruction->type == PPCREC_IML_TYPE_CJUMP || - imlInstruction->type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK ) - return true; - return false; -} - -void PPCRecompiler_storeReplacedRegister(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment, replacedRegisterTracker_t* replacedRegisterTracker, sint32 registerTrackerIndex, sint32* imlIndex) -{ - // store register - sint32 imlIndexEdit = *imlIndex; - PPCRecompiler_pushBackIMLInstructions(imlSegment, imlIndexEdit, 1); - // name_unusedRegister = unusedRegister - PPCRecImlInstruction_t* imlInstructionItr = imlSegment->imlList+(imlIndexEdit+0); - memset(imlInstructionItr, 0x00, sizeof(PPCRecImlInstruction_t)); - imlInstructionItr->type = PPCREC_IML_TYPE_NAME_R; - imlInstructionItr->crRegister = PPC_REC_INVALID_REGISTER; - imlInstructionItr->operation = PPCREC_IML_OP_ASSIGN; - imlInstructionItr->op_r_name.registerIndex = replacedRegisterTracker->replacedRegisterEntry[registerTrackerIndex].index; - imlInstructionItr->op_r_name.name = replacedRegisterTracker->replacedRegisterEntry[registerTrackerIndex].registerNewName; - imlInstructionItr->op_r_name.copyWidth = 32; - imlInstructionItr->op_r_name.flags = 0; - imlIndexEdit++; - // load new register if required - if( replacedRegisterTracker->replacedRegisterEntry[registerTrackerIndex].nameMustBeMaintained ) - { - PPCRecompiler_pushBackIMLInstructions(imlSegment, imlIndexEdit, 1); - PPCRecImlInstruction_t* imlInstructionItr = imlSegment->imlList+(imlIndexEdit+0); - memset(imlInstructionItr, 0x00, sizeof(PPCRecImlInstruction_t)); - imlInstructionItr->type = PPCREC_IML_TYPE_R_NAME; - imlInstructionItr->crRegister = PPC_REC_INVALID_REGISTER; - imlInstructionItr->operation = PPCREC_IML_OP_ASSIGN; - imlInstructionItr->op_r_name.registerIndex = replacedRegisterTracker->replacedRegisterEntry[registerTrackerIndex].index; - imlInstructionItr->op_r_name.name = replacedRegisterTracker->replacedRegisterEntry[registerTrackerIndex].registerPreviousName;//ppcImlGenContext->mappedRegister[replacedRegisterTracker.replacedRegisterEntry[i].index]; - imlInstructionItr->op_r_name.copyWidth = 32; - imlInstructionItr->op_r_name.flags = 0; - imlIndexEdit += 1; - } - // move last entry to current one - memcpy(replacedRegisterTracker->replacedRegisterEntry+registerTrackerIndex, replacedRegisterTracker->replacedRegisterEntry+replacedRegisterTracker->count-1, sizeof(replacedRegisterTracker->replacedRegisterEntry[0])); - replacedRegisterTracker->count--; - *imlIndex = imlIndexEdit; -} - -bool PPCRecompiler_reduceNumberOfFPRRegisters(ppcImlGenContext_t* ppcImlGenContext) -{ - // only xmm0 to xmm14 may be used, xmm15 is reserved - // this method will reduce the number of fpr registers used - // inefficient algorithm for optimizing away excess registers - // we simply load, use and store excess registers into other unused registers when we need to - // first we remove all name load and store instructions that involve out-of-bounds registers - for(sint32 s=0; s<ppcImlGenContext->segmentListCount; s++) - { - PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[s]; - sint32 imlIndex = 0; - while( imlIndex < imlSegment->imlListCount ) - { - PPCRecImlInstruction_t* imlInstructionItr = imlSegment->imlList+imlIndex; - if( imlInstructionItr->type == PPCREC_IML_TYPE_FPR_R_NAME || imlInstructionItr->type == PPCREC_IML_TYPE_FPR_NAME_R ) - { - if( imlInstructionItr->op_r_name.registerIndex >= PPC_X64_FPR_USABLE_REGISTERS ) - { - // convert to NO-OP instruction - imlInstructionItr->type = PPCREC_IML_TYPE_NO_OP; - imlInstructionItr->associatedPPCAddress = 0; - } - } - imlIndex++; - } - } - // replace registers - for(sint32 s=0; s<ppcImlGenContext->segmentListCount; s++) - { - PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[s]; - sint32 imlIndex = 0; - while( imlIndex < imlSegment->imlListCount ) - { - PPCImlOptimizerUsedRegisters_t registersUsed; - while( true ) - { - PPCRecompiler_checkRegisterUsage(ppcImlGenContext, imlSegment->imlList+imlIndex, ®istersUsed); - if( registersUsed.readFPR1 >= PPC_X64_FPR_USABLE_REGISTERS || registersUsed.readFPR2 >= PPC_X64_FPR_USABLE_REGISTERS || registersUsed.readFPR3 >= PPC_X64_FPR_USABLE_REGISTERS || registersUsed.readFPR4 >= PPC_X64_FPR_USABLE_REGISTERS || registersUsed.writtenFPR1 >= PPC_X64_FPR_USABLE_REGISTERS ) - { - // get index of register to replace - sint32 fprToReplace = -1; - if( registersUsed.readFPR1 >= PPC_X64_FPR_USABLE_REGISTERS ) - fprToReplace = registersUsed.readFPR1; - else if( registersUsed.readFPR2 >= PPC_X64_FPR_USABLE_REGISTERS ) - fprToReplace = registersUsed.readFPR2; - else if (registersUsed.readFPR3 >= PPC_X64_FPR_USABLE_REGISTERS) - fprToReplace = registersUsed.readFPR3; - else if (registersUsed.readFPR4 >= PPC_X64_FPR_USABLE_REGISTERS) - fprToReplace = registersUsed.readFPR4; - else if( registersUsed.writtenFPR1 >= PPC_X64_FPR_USABLE_REGISTERS ) - fprToReplace = registersUsed.writtenFPR1; - // generate mask of useable registers - uint8 useableRegisterMask = 0x7F; // lowest bit is fpr register 0 - if( registersUsed.readFPR1 != -1 ) - useableRegisterMask &= ~(1<<(registersUsed.readFPR1)); - if( registersUsed.readFPR2 != -1 ) - useableRegisterMask &= ~(1<<(registersUsed.readFPR2)); - if (registersUsed.readFPR3 != -1) - useableRegisterMask &= ~(1 << (registersUsed.readFPR3)); - if (registersUsed.readFPR4 != -1) - useableRegisterMask &= ~(1 << (registersUsed.readFPR4)); - if( registersUsed.writtenFPR1 != -1 ) - useableRegisterMask &= ~(1<<(registersUsed.writtenFPR1)); - // get highest unused register index (0-6 range) - sint32 unusedRegisterIndex = -1; - for(sint32 f=0; f<PPC_X64_FPR_USABLE_REGISTERS; f++) - { - if( useableRegisterMask&(1<<f) ) - { - unusedRegisterIndex = f; - } - } - if( unusedRegisterIndex == -1 ) - assert_dbg(); - // determine if the placeholder register is actually used (if not we must not load/store it) - uint32 unusedRegisterName = ppcImlGenContext->mappedFPRRegister[unusedRegisterIndex]; - bool replacedRegisterIsUsed = true; - if( unusedRegisterName >= PPCREC_NAME_FPR0 && unusedRegisterName < (PPCREC_NAME_FPR0+32) ) - { - replacedRegisterIsUsed = imlSegment->ppcFPRUsed[unusedRegisterName-PPCREC_NAME_FPR0]; - } - // replace registers that are out of range - PPCRecompiler_replaceFPRRegisterUsage(ppcImlGenContext, imlSegment->imlList+imlIndex, fprToReplace, unusedRegisterIndex); - // add load/store name after instruction - PPCRecompiler_pushBackIMLInstructions(imlSegment, imlIndex+1, 2); - // add load/store before current instruction - PPCRecompiler_pushBackIMLInstructions(imlSegment, imlIndex, 2); - // name_unusedRegister = unusedRegister - PPCRecImlInstruction_t* imlInstructionItr = imlSegment->imlList+(imlIndex+0); - memset(imlInstructionItr, 0x00, sizeof(PPCRecImlInstruction_t)); - if( replacedRegisterIsUsed ) - { - imlInstructionItr->type = PPCREC_IML_TYPE_FPR_NAME_R; - imlInstructionItr->operation = PPCREC_IML_OP_ASSIGN; - imlInstructionItr->op_r_name.registerIndex = unusedRegisterIndex; - imlInstructionItr->op_r_name.name = ppcImlGenContext->mappedFPRRegister[unusedRegisterIndex]; - imlInstructionItr->op_r_name.copyWidth = 32; - imlInstructionItr->op_r_name.flags = 0; - } - else - imlInstructionItr->type = PPCREC_IML_TYPE_NO_OP; - imlInstructionItr = imlSegment->imlList+(imlIndex+1); - memset(imlInstructionItr, 0x00, sizeof(PPCRecImlInstruction_t)); - imlInstructionItr->type = PPCREC_IML_TYPE_FPR_R_NAME; - imlInstructionItr->operation = PPCREC_IML_OP_ASSIGN; - imlInstructionItr->op_r_name.registerIndex = unusedRegisterIndex; - imlInstructionItr->op_r_name.name = ppcImlGenContext->mappedFPRRegister[fprToReplace]; - imlInstructionItr->op_r_name.copyWidth = 32; - imlInstructionItr->op_r_name.flags = 0; - // name_gprToReplace = unusedRegister - imlInstructionItr = imlSegment->imlList+(imlIndex+3); - memset(imlInstructionItr, 0x00, sizeof(PPCRecImlInstruction_t)); - imlInstructionItr->type = PPCREC_IML_TYPE_FPR_NAME_R; - imlInstructionItr->operation = PPCREC_IML_OP_ASSIGN; - imlInstructionItr->op_r_name.registerIndex = unusedRegisterIndex; - imlInstructionItr->op_r_name.name = ppcImlGenContext->mappedFPRRegister[fprToReplace]; - imlInstructionItr->op_r_name.copyWidth = 32; - imlInstructionItr->op_r_name.flags = 0; - // unusedRegister = name_unusedRegister - imlInstructionItr = imlSegment->imlList+(imlIndex+4); - memset(imlInstructionItr, 0x00, sizeof(PPCRecImlInstruction_t)); - if( replacedRegisterIsUsed ) - { - imlInstructionItr->type = PPCREC_IML_TYPE_FPR_R_NAME; - imlInstructionItr->operation = PPCREC_IML_OP_ASSIGN; - imlInstructionItr->op_r_name.registerIndex = unusedRegisterIndex; - imlInstructionItr->op_r_name.name = ppcImlGenContext->mappedFPRRegister[unusedRegisterIndex]; - imlInstructionItr->op_r_name.copyWidth = 32; - imlInstructionItr->op_r_name.flags = 0; - } - else - imlInstructionItr->type = PPCREC_IML_TYPE_NO_OP; - } - else - break; - } - imlIndex++; - } - } - return true; -} - -typedef struct -{ - bool isActive; - uint32 virtualReg; - sint32 lastUseIndex; -}ppcRecRegisterMapping_t; - -typedef struct -{ - ppcRecRegisterMapping_t currentMapping[PPC_X64_FPR_USABLE_REGISTERS]; - sint32 ppcRegToMapping[64]; - sint32 currentUseIndex; -}ppcRecManageRegisters_t; - -ppcRecRegisterMapping_t* PPCRecompiler_findAvailableRegisterDepr(ppcRecManageRegisters_t* rCtx, PPCImlOptimizerUsedRegisters_t* instructionUsedRegisters) -{ - // find free register - for (sint32 i = 0; i < PPC_X64_FPR_USABLE_REGISTERS; i++) - { - if (rCtx->currentMapping[i].isActive == false) - { - rCtx->currentMapping[i].isActive = true; - rCtx->currentMapping[i].virtualReg = -1; - rCtx->currentMapping[i].lastUseIndex = rCtx->currentUseIndex; - return rCtx->currentMapping + i; - } - } - // all registers are used - return nullptr; -} - -ppcRecRegisterMapping_t* PPCRecompiler_findUnloadableRegister(ppcRecManageRegisters_t* rCtx, PPCImlOptimizerUsedRegisters_t* instructionUsedRegisters, uint32 unloadLockedMask) -{ - // find unloadable register (with lowest lastUseIndex) - sint32 unloadIndex = -1; - sint32 unloadIndexLastUse = 0x7FFFFFFF; - for (sint32 i = 0; i < PPC_X64_FPR_USABLE_REGISTERS; i++) - { - if (rCtx->currentMapping[i].isActive == false) - continue; - if( (unloadLockedMask&(1<<i)) != 0 ) - continue; - uint32 virtualReg = rCtx->currentMapping[i].virtualReg; - bool isReserved = false; - for (sint32 f = 0; f < 4; f++) - { - if (virtualReg == (sint32)instructionUsedRegisters->fpr[f]) - { - isReserved = true; - break; - } - } - if (isReserved) - continue; - if (rCtx->currentMapping[i].lastUseIndex < unloadIndexLastUse) - { - unloadIndexLastUse = rCtx->currentMapping[i].lastUseIndex; - unloadIndex = i; - } - } - cemu_assert(unloadIndex != -1); - return rCtx->currentMapping + unloadIndex; -} - -bool PPCRecompiler_manageFPRRegistersForSegment(ppcImlGenContext_t* ppcImlGenContext, sint32 segmentIndex) -{ - ppcRecManageRegisters_t rCtx = { 0 }; - for (sint32 i = 0; i < 64; i++) - rCtx.ppcRegToMapping[i] = -1; - PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[segmentIndex]; - sint32 idx = 0; - sint32 currentUseIndex = 0; - PPCImlOptimizerUsedRegisters_t registersUsed; - while (idx < imlSegment->imlListCount) - { - if ( PPCRecompiler_isSuffixInstruction(imlSegment->imlList + idx) ) - break; - PPCRecompiler_checkRegisterUsage(ppcImlGenContext, imlSegment->imlList + idx, ®istersUsed); - sint32 fprMatch[4]; - sint32 fprReplace[4]; - fprMatch[0] = -1; - fprMatch[1] = -1; - fprMatch[2] = -1; - fprMatch[3] = -1; - fprReplace[0] = -1; - fprReplace[1] = -1; - fprReplace[2] = -1; - fprReplace[3] = -1; - // generate a mask of registers that we may not free - sint32 numReplacedOperands = 0; - uint32 unloadLockedMask = 0; - for (sint32 f = 0; f < 5; f++) - { - sint32 virtualFpr; - if (f == 0) - virtualFpr = registersUsed.readFPR1; - else if (f == 1) - virtualFpr = registersUsed.readFPR2; - else if (f == 2) - virtualFpr = registersUsed.readFPR3; - else if (f == 3) - virtualFpr = registersUsed.readFPR4; - else if (f == 4) - virtualFpr = registersUsed.writtenFPR1; - if( virtualFpr < 0 ) - continue; - cemu_assert_debug(virtualFpr < 64); - // check if this virtual FPR is already loaded in any real register - ppcRecRegisterMapping_t* regMapping; - if (rCtx.ppcRegToMapping[virtualFpr] == -1) - { - // not loaded - // find available register - while (true) - { - regMapping = PPCRecompiler_findAvailableRegisterDepr(&rCtx, ®istersUsed); - if (regMapping == NULL) - { - // unload least recently used register and try again - ppcRecRegisterMapping_t* unloadRegMapping = PPCRecompiler_findUnloadableRegister(&rCtx, ®istersUsed, unloadLockedMask); - // mark as locked - unloadLockedMask |= (1<<(unloadRegMapping- rCtx.currentMapping)); - // create unload instruction - PPCRecompiler_pushBackIMLInstructions(imlSegment, idx, 1); - PPCRecImlInstruction_t* imlInstructionTemp = imlSegment->imlList + idx; - memset(imlInstructionTemp, 0x00, sizeof(PPCRecImlInstruction_t)); - imlInstructionTemp->type = PPCREC_IML_TYPE_FPR_NAME_R; - imlInstructionTemp->operation = PPCREC_IML_OP_ASSIGN; - imlInstructionTemp->op_r_name.registerIndex = (uint8)(unloadRegMapping - rCtx.currentMapping); - imlInstructionTemp->op_r_name.name = ppcImlGenContext->mappedFPRRegister[unloadRegMapping->virtualReg]; - imlInstructionTemp->op_r_name.copyWidth = 32; - imlInstructionTemp->op_r_name.flags = 0; - idx++; - // update mapping - unloadRegMapping->isActive = false; - rCtx.ppcRegToMapping[unloadRegMapping->virtualReg] = -1; - } - else - break; - } - // create load instruction - PPCRecompiler_pushBackIMLInstructions(imlSegment, idx, 1); - PPCRecImlInstruction_t* imlInstructionTemp = imlSegment->imlList + idx; - memset(imlInstructionTemp, 0x00, sizeof(PPCRecImlInstruction_t)); - imlInstructionTemp->type = PPCREC_IML_TYPE_FPR_R_NAME; - imlInstructionTemp->operation = PPCREC_IML_OP_ASSIGN; - imlInstructionTemp->op_r_name.registerIndex = (uint8)(regMapping-rCtx.currentMapping); - imlInstructionTemp->op_r_name.name = ppcImlGenContext->mappedFPRRegister[virtualFpr]; - imlInstructionTemp->op_r_name.copyWidth = 32; - imlInstructionTemp->op_r_name.flags = 0; - idx++; - // update mapping - regMapping->virtualReg = virtualFpr; - rCtx.ppcRegToMapping[virtualFpr] = (sint32)(regMapping - rCtx.currentMapping); - regMapping->lastUseIndex = rCtx.currentUseIndex; - rCtx.currentUseIndex++; - } - else - { - regMapping = rCtx.currentMapping + rCtx.ppcRegToMapping[virtualFpr]; - regMapping->lastUseIndex = rCtx.currentUseIndex; - rCtx.currentUseIndex++; - } - // replace FPR - bool entryFound = false; - for (sint32 t = 0; t < numReplacedOperands; t++) - { - if (fprMatch[t] == virtualFpr) - { - cemu_assert_debug(fprReplace[t] == (regMapping - rCtx.currentMapping)); - entryFound = true; - break; - } - } - if (entryFound == false) - { - cemu_assert_debug(numReplacedOperands != 4); - fprMatch[numReplacedOperands] = virtualFpr; - fprReplace[numReplacedOperands] = (sint32)(regMapping - rCtx.currentMapping); - numReplacedOperands++; - } - } - if (numReplacedOperands > 0) - { - PPCRecompiler_replaceFPRRegisterUsageMultiple(ppcImlGenContext, imlSegment->imlList + idx, fprMatch, fprReplace); - } - // next - idx++; - } - // count loaded registers - sint32 numLoadedRegisters = 0; - for (sint32 i = 0; i < PPC_X64_FPR_USABLE_REGISTERS; i++) - { - if (rCtx.currentMapping[i].isActive) - numLoadedRegisters++; - } - // store all loaded registers - if (numLoadedRegisters > 0) - { - PPCRecompiler_pushBackIMLInstructions(imlSegment, idx, numLoadedRegisters); - for (sint32 i = 0; i < PPC_X64_FPR_USABLE_REGISTERS; i++) - { - if (rCtx.currentMapping[i].isActive == false) - continue; - PPCRecImlInstruction_t* imlInstructionTemp = imlSegment->imlList + idx; - memset(imlInstructionTemp, 0x00, sizeof(PPCRecImlInstruction_t)); - imlInstructionTemp->type = PPCREC_IML_TYPE_FPR_NAME_R; - imlInstructionTemp->operation = PPCREC_IML_OP_ASSIGN; - imlInstructionTemp->op_r_name.registerIndex = i; - imlInstructionTemp->op_r_name.name = ppcImlGenContext->mappedFPRRegister[rCtx.currentMapping[i].virtualReg]; - imlInstructionTemp->op_r_name.copyWidth = 32; - imlInstructionTemp->op_r_name.flags = 0; - idx++; - } - } - return true; -} - -bool PPCRecompiler_manageFPRRegisters(ppcImlGenContext_t* ppcImlGenContext) -{ - for (sint32 s = 0; s < ppcImlGenContext->segmentListCount; s++) - { - if (PPCRecompiler_manageFPRRegistersForSegment(ppcImlGenContext, s) == false) - return false; - } - return true; -} - - -/* - * Returns true if the loaded value is guaranteed to be overwritten - */ -bool PPCRecompiler_trackRedundantNameLoadInstruction(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment, sint32 startIndex, PPCRecImlInstruction_t* nameStoreInstruction, sint32 scanDepth) -{ - sint16 registerIndex = nameStoreInstruction->op_r_name.registerIndex; - for(sint32 i=startIndex; i<imlSegment->imlListCount; i++) - { - PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList+i; - //nameStoreInstruction->op_r_name.registerIndex - PPCImlOptimizerUsedRegisters_t registersUsed; - PPCRecompiler_checkRegisterUsage(ppcImlGenContext, imlSegment->imlList+i, ®istersUsed); - if( registersUsed.readNamedReg1 == registerIndex || registersUsed.readNamedReg2 == registerIndex || registersUsed.readNamedReg3 == registerIndex ) - return false; - if( registersUsed.writtenNamedReg1 == registerIndex ) - return true; - } - // todo: Scan next segment(s) - return false; -} - -/* - * Returns true if the loaded value is guaranteed to be overwritten - */ -bool PPCRecompiler_trackRedundantFPRNameLoadInstruction(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment, sint32 startIndex, PPCRecImlInstruction_t* nameStoreInstruction, sint32 scanDepth) -{ - sint16 registerIndex = nameStoreInstruction->op_r_name.registerIndex; - for(sint32 i=startIndex; i<imlSegment->imlListCount; i++) - { - PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList+i; - PPCImlOptimizerUsedRegisters_t registersUsed; - PPCRecompiler_checkRegisterUsage(ppcImlGenContext, imlSegment->imlList+i, ®istersUsed); - if( registersUsed.readFPR1 == registerIndex || registersUsed.readFPR2 == registerIndex || registersUsed.readFPR3 == registerIndex || registersUsed.readFPR4 == registerIndex) - return false; - if( registersUsed.writtenFPR1 == registerIndex ) - return true; - } - // todo: Scan next segment(s) - return false; -} - -/* - * Returns true if the loaded name is never changed - */ -bool PPCRecompiler_trackRedundantNameStoreInstruction(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment, sint32 startIndex, PPCRecImlInstruction_t* nameStoreInstruction, sint32 scanDepth) -{ - sint16 registerIndex = nameStoreInstruction->op_r_name.registerIndex; - for(sint32 i=startIndex; i>=0; i--) - { - PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList+i; - PPCImlOptimizerUsedRegisters_t registersUsed; - PPCRecompiler_checkRegisterUsage(ppcImlGenContext, imlSegment->imlList+i, ®istersUsed); - if( registersUsed.writtenNamedReg1 == registerIndex ) - { - if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_R_NAME ) - return true; - return false; - } - } - return false; -} - -sint32 debugCallCounter1 = 0; - -/* - * Returns true if the name is overwritten in the current or any following segments - */ -bool PPCRecompiler_trackOverwrittenNameStoreInstruction(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment, sint32 startIndex, PPCRecImlInstruction_t* nameStoreInstruction, sint32 scanDepth) -{ - //sint16 registerIndex = nameStoreInstruction->op_r_name.registerIndex; - uint32 name = nameStoreInstruction->op_r_name.name; - for(sint32 i=startIndex; i<imlSegment->imlListCount; i++) - { - PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList+i; - if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_R_NAME ) - { - // name is loaded before being written - if( imlSegment->imlList[i].op_r_name.name == name ) - return false; - } - else if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_NAME_R ) - { - // name is written before being loaded - if( imlSegment->imlList[i].op_r_name.name == name ) - return true; - } - } - if( scanDepth >= 2 ) - return false; - if( imlSegment->nextSegmentIsUncertain ) - return false; - if( imlSegment->nextSegmentBranchTaken && PPCRecompiler_trackOverwrittenNameStoreInstruction(ppcImlGenContext, imlSegment->nextSegmentBranchTaken, 0, nameStoreInstruction, scanDepth+1) == false ) - return false; - if( imlSegment->nextSegmentBranchNotTaken && PPCRecompiler_trackOverwrittenNameStoreInstruction(ppcImlGenContext, imlSegment->nextSegmentBranchNotTaken, 0, nameStoreInstruction, scanDepth+1) == false ) - return false; - if( imlSegment->nextSegmentBranchTaken == NULL && imlSegment->nextSegmentBranchNotTaken == NULL ) - return false; - - return true; -} - -/* - * Returns true if the loaded FPR name is never changed - */ -bool PPCRecompiler_trackRedundantFPRNameStoreInstruction(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment, sint32 startIndex, PPCRecImlInstruction_t* nameStoreInstruction, sint32 scanDepth) -{ - sint16 registerIndex = nameStoreInstruction->op_r_name.registerIndex; - for(sint32 i=startIndex; i>=0; i--) - { - PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList+i; - PPCImlOptimizerUsedRegisters_t registersUsed; - PPCRecompiler_checkRegisterUsage(ppcImlGenContext, imlSegment->imlList+i, ®istersUsed); - if( registersUsed.writtenFPR1 == registerIndex ) - { - if( imlSegment->imlList[i].type == PPCREC_IML_TYPE_FPR_R_NAME ) - return true; - return false; - } - } - // todo: Scan next segment(s) - return false; -} - -uint32 _PPCRecompiler_getCROverwriteMask(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment, uint32 currentOverwriteMask, uint32 currentReadMask, uint32 scanDepth) -{ - // is any bit overwritten but not read? - uint32 overwriteMask = imlSegment->crBitsWritten&~imlSegment->crBitsInput; - currentOverwriteMask |= overwriteMask; - // next segment - if( imlSegment->nextSegmentIsUncertain == false && scanDepth < 3 ) - { - uint32 nextSegmentOverwriteMask = 0; - if( imlSegment->nextSegmentBranchTaken && imlSegment->nextSegmentBranchNotTaken ) - { - uint32 mask0 = _PPCRecompiler_getCROverwriteMask(ppcImlGenContext, imlSegment->nextSegmentBranchTaken, 0, 0, scanDepth+1); - uint32 mask1 = _PPCRecompiler_getCROverwriteMask(ppcImlGenContext, imlSegment->nextSegmentBranchNotTaken, 0, 0, scanDepth+1); - nextSegmentOverwriteMask = mask0&mask1; - } - else if( imlSegment->nextSegmentBranchNotTaken) - { - nextSegmentOverwriteMask = _PPCRecompiler_getCROverwriteMask(ppcImlGenContext, imlSegment->nextSegmentBranchNotTaken, 0, 0, scanDepth+1); - } - nextSegmentOverwriteMask &= ~imlSegment->crBitsRead; - currentOverwriteMask |= nextSegmentOverwriteMask; - } - else if (imlSegment->nextSegmentIsUncertain) - { - if (ppcImlGenContext->segmentListCount >= 5) - { - return 7; // for more complex functions we assume that CR is not passed on - } - } - return currentOverwriteMask; -} - -/* - * Returns a mask of all CR bits that are overwritten (written but not read) in the segment and all it's following segments - * If the write state of a CR bit cannot be determined, it is returned as 0 (not overwritten) - */ -uint32 PPCRecompiler_getCROverwriteMask(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment) -{ - if (imlSegment->nextSegmentIsUncertain) - { - return 0; - } - if( imlSegment->nextSegmentBranchTaken && imlSegment->nextSegmentBranchNotTaken ) - { - uint32 mask0 = _PPCRecompiler_getCROverwriteMask(ppcImlGenContext, imlSegment->nextSegmentBranchTaken, 0, 0, 0); - uint32 mask1 = _PPCRecompiler_getCROverwriteMask(ppcImlGenContext, imlSegment->nextSegmentBranchNotTaken, 0, 0, 0); - return mask0&mask1; // only return bits that are overwritten in both branches - } - else if( imlSegment->nextSegmentBranchNotTaken ) - { - uint32 mask = _PPCRecompiler_getCROverwriteMask(ppcImlGenContext, imlSegment->nextSegmentBranchNotTaken, 0, 0, 0); - return mask; - } - else - { - // not implemented - } - return 0; -} - -void PPCRecompiler_removeRedundantCRUpdates(ppcImlGenContext_t* ppcImlGenContext) -{ - for(sint32 s=0; s<ppcImlGenContext->segmentListCount; s++) - { - PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[s]; - - for(sint32 i=0; i<imlSegment->imlListCount; i++) - { - PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList+i; - if (imlInstruction->type == PPCREC_IML_TYPE_CJUMP) - { - if (imlInstruction->op_conditionalJump.condition != PPCREC_JUMP_CONDITION_NONE) - { - uint32 crBitFlag = 1 << (imlInstruction->op_conditionalJump.crRegisterIndex * 4 + imlInstruction->op_conditionalJump.crBitIndex); - imlSegment->crBitsInput |= (crBitFlag&~imlSegment->crBitsWritten); // flag bits that have not already been written - imlSegment->crBitsRead |= (crBitFlag); - } - } - else if (imlInstruction->type == PPCREC_IML_TYPE_CONDITIONAL_R_S32) - { - uint32 crBitFlag = 1 << (imlInstruction->op_conditional_r_s32.crRegisterIndex * 4 + imlInstruction->op_conditional_r_s32.crBitIndex); - imlSegment->crBitsInput |= (crBitFlag&~imlSegment->crBitsWritten); // flag bits that have not already been written - imlSegment->crBitsRead |= (crBitFlag); - } - else if (imlInstruction->type == PPCREC_IML_TYPE_R_S32 && imlInstruction->operation == PPCREC_IML_OP_MFCR) - { - imlSegment->crBitsRead |= 0xFFFFFFFF; - } - else if (imlInstruction->type == PPCREC_IML_TYPE_R_S32 && imlInstruction->operation == PPCREC_IML_OP_MTCRF) - { - imlSegment->crBitsWritten |= ppc_MTCRFMaskToCRBitMask((uint32)imlInstruction->op_r_immS32.immS32); - } - else if( imlInstruction->type == PPCREC_IML_TYPE_CR ) - { - if (imlInstruction->operation == PPCREC_IML_OP_CR_CLEAR || - imlInstruction->operation == PPCREC_IML_OP_CR_SET) - { - uint32 crBitFlag = 1 << (imlInstruction->op_cr.crD); - imlSegment->crBitsWritten |= (crBitFlag & ~imlSegment->crBitsWritten); - } - else if (imlInstruction->operation == PPCREC_IML_OP_CR_OR || - imlInstruction->operation == PPCREC_IML_OP_CR_ORC || - imlInstruction->operation == PPCREC_IML_OP_CR_AND || - imlInstruction->operation == PPCREC_IML_OP_CR_ANDC) - { - uint32 crBitFlag = 1 << (imlInstruction->op_cr.crD); - imlSegment->crBitsWritten |= (crBitFlag & ~imlSegment->crBitsWritten); - crBitFlag = 1 << (imlInstruction->op_cr.crA); - imlSegment->crBitsRead |= (crBitFlag & ~imlSegment->crBitsRead); - crBitFlag = 1 << (imlInstruction->op_cr.crB); - imlSegment->crBitsRead |= (crBitFlag & ~imlSegment->crBitsRead); - } - else - cemu_assert_unimplemented(); - } - else if( PPCRecompilerImlAnalyzer_canTypeWriteCR(imlInstruction) && imlInstruction->crRegister >= 0 && imlInstruction->crRegister <= 7 ) - { - imlSegment->crBitsWritten |= (0xF<<(imlInstruction->crRegister*4)); - } - else if( (imlInstruction->type == PPCREC_IML_TYPE_STORE || imlInstruction->type == PPCREC_IML_TYPE_STORE_INDEXED) && imlInstruction->op_storeLoad.copyWidth == PPC_REC_STORE_STWCX_MARKER ) - { - // overwrites CR0 - imlSegment->crBitsWritten |= (0xF<<0); - } - } - } - // flag instructions that write to CR where we can ignore individual CR bits - for(sint32 s=0; s<ppcImlGenContext->segmentListCount; s++) - { - PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[s]; - for(sint32 i=0; i<imlSegment->imlListCount; i++) - { - PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList+i; - if( PPCRecompilerImlAnalyzer_canTypeWriteCR(imlInstruction) && imlInstruction->crRegister >= 0 && imlInstruction->crRegister <= 7 ) - { - uint32 crBitFlags = 0xF<<((uint32)imlInstruction->crRegister*4); - uint32 crOverwriteMask = PPCRecompiler_getCROverwriteMask(ppcImlGenContext, imlSegment); - uint32 crIgnoreMask = crOverwriteMask & ~imlSegment->crBitsRead; - imlInstruction->crIgnoreMask = crIgnoreMask; - } - } - } -} - -bool PPCRecompiler_checkIfGPRIsModifiedInRange(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment, sint32 startIndex, sint32 endIndex, sint32 vreg) -{ - PPCImlOptimizerUsedRegisters_t registersUsed; - for (sint32 i = startIndex; i <= endIndex; i++) - { - PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList + i; - PPCRecompiler_checkRegisterUsage(ppcImlGenContext, imlInstruction, ®istersUsed); - if (registersUsed.writtenNamedReg1 == vreg) - return true; - } - return false; -} - -sint32 PPCRecompiler_scanBackwardsForReusableRegister(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* startSegment, sint32 startIndex, sint32 name) -{ - // current segment - sint32 currentIndex = startIndex; - PPCRecImlSegment_t* currentSegment = startSegment; - sint32 segmentIterateCount = 0; - sint32 foundRegister = -1; - while (true) - { - // stop scanning if segment is enterable - if (currentSegment->isEnterable) - return -1; - while (currentIndex >= 0) - { - if (currentSegment->imlList[currentIndex].type == PPCREC_IML_TYPE_NAME_R && currentSegment->imlList[currentIndex].op_r_name.name == name) - { - foundRegister = currentSegment->imlList[currentIndex].op_r_name.registerIndex; - break; - } - // previous instruction - currentIndex--; - } - if (foundRegister >= 0) - break; - // continue at previous segment (if there is only one) - if (segmentIterateCount >= 1) - return -1; - if (currentSegment->list_prevSegments.size() != 1) - return -1; - currentSegment = currentSegment->list_prevSegments[0]; - currentIndex = currentSegment->imlListCount - 1; - segmentIterateCount++; - } - // scan again to make sure the register is not modified inbetween - currentIndex = startIndex; - currentSegment = startSegment; - segmentIterateCount = 0; - PPCImlOptimizerUsedRegisters_t registersUsed; - while (true) - { - while (currentIndex >= 0) - { - // check if register is modified - PPCRecompiler_checkRegisterUsage(ppcImlGenContext, currentSegment->imlList+currentIndex, ®istersUsed); - if (registersUsed.writtenNamedReg1 == foundRegister) - return -1; - // check if end of scan reached - if (currentSegment->imlList[currentIndex].type == PPCREC_IML_TYPE_NAME_R && currentSegment->imlList[currentIndex].op_r_name.name == name) - { - //foundRegister = currentSegment->imlList[currentIndex].op_r_name.registerIndex; - return foundRegister; - } - // previous instruction - currentIndex--; - } - // continue at previous segment (if there is only one) - if (segmentIterateCount >= 1) - return -1; - if (currentSegment->list_prevSegments.size() != 1) - return -1; - currentSegment = currentSegment->list_prevSegments[0]; - currentIndex = currentSegment->imlListCount - 1; - segmentIterateCount++; - } - return -1; -} - -void PPCRecompiler_optimizeDirectFloatCopiesScanForward(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment, sint32 imlIndexLoad, sint32 fprIndex) -{ - PPCRecImlInstruction_t* imlInstructionLoad = imlSegment->imlList + imlIndexLoad; - if (imlInstructionLoad->op_storeLoad.flags2.notExpanded) - return; - - PPCImlOptimizerUsedRegisters_t registersUsed; - sint32 scanRangeEnd = std::min(imlIndexLoad + 25, imlSegment->imlListCount); // don't scan too far (saves performance and also the chances we can merge the load+store become low at high distances) - bool foundMatch = false; - sint32 lastStore = -1; - for (sint32 i = imlIndexLoad + 1; i < scanRangeEnd; i++) - { - PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList + i; - if (PPCRecompiler_isSuffixInstruction(imlInstruction)) - { - break; - } - - // check if FPR is stored - if ((imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE && imlInstruction->op_storeLoad.mode == PPCREC_FPR_ST_MODE_SINGLE_FROM_PS0) || - (imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE_INDEXED && imlInstruction->op_storeLoad.mode == PPCREC_FPR_ST_MODE_SINGLE_FROM_PS0)) - { - if (imlInstruction->op_storeLoad.registerData == fprIndex) - { - if (foundMatch == false) - { - // flag the load-single instruction as "don't expand" (leave single value as-is) - imlInstructionLoad->op_storeLoad.flags2.notExpanded = true; - } - // also set the flag for the store instruction - PPCRecImlInstruction_t* imlInstructionStore = imlInstruction; - imlInstructionStore->op_storeLoad.flags2.notExpanded = true; - - foundMatch = true; - lastStore = i + 1; - - continue; - } - } - - // check if FPR is overwritten (we can actually ignore read operations?) - PPCRecompiler_checkRegisterUsage(ppcImlGenContext, imlInstruction, ®istersUsed); - if (registersUsed.writtenFPR1 == fprIndex) - break; - if (registersUsed.readFPR1 == fprIndex) - break; - if (registersUsed.readFPR2 == fprIndex) - break; - if (registersUsed.readFPR3 == fprIndex) - break; - if (registersUsed.readFPR4 == fprIndex) - break; - } - - if (foundMatch) - { - // insert expand instruction after store - PPCRecImlInstruction_t* newExpand = PPCRecompiler_insertInstruction(imlSegment, lastStore); - PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext, newExpand, PPCREC_IML_OP_FPR_EXPAND_BOTTOM32_TO_BOTTOM64_AND_TOP64, fprIndex); - } -} - -/* -* Scans for patterns: -* <Load sp float into register f> -* <Random unrelated instructions> -* <Store sp float from register f> -* For these patterns the store and load is modified to work with un-extended values (float remains as float, no double conversion) -* The float->double extension is then executed later -* Advantages: -* Keeps denormals and other special float values intact -* Slightly improves performance -*/ -void PPCRecompiler_optimizeDirectFloatCopies(ppcImlGenContext_t* ppcImlGenContext) -{ - for (sint32 s = 0; s < ppcImlGenContext->segmentListCount; s++) - { - PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[s]; - - for (sint32 i = 0; i < imlSegment->imlListCount; i++) - { - PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList + i; - if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD && imlInstruction->op_storeLoad.mode == PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1) - { - PPCRecompiler_optimizeDirectFloatCopiesScanForward(ppcImlGenContext, imlSegment, i, imlInstruction->op_storeLoad.registerData); - } - else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED && imlInstruction->op_storeLoad.mode == PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1) - { - PPCRecompiler_optimizeDirectFloatCopiesScanForward(ppcImlGenContext, imlSegment, i, imlInstruction->op_storeLoad.registerData); - } - } - } -} - -void PPCRecompiler_optimizeDirectIntegerCopiesScanForward(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment, sint32 imlIndexLoad, sint32 gprIndex) -{ - PPCRecImlInstruction_t* imlInstructionLoad = imlSegment->imlList + imlIndexLoad; - if ( imlInstructionLoad->op_storeLoad.flags2.swapEndian == false ) - return; - bool foundMatch = false; - PPCImlOptimizerUsedRegisters_t registersUsed; - sint32 scanRangeEnd = std::min(imlIndexLoad + 25, imlSegment->imlListCount); // don't scan too far (saves performance and also the chances we can merge the load+store become low at high distances) - sint32 i = imlIndexLoad + 1; - for (; i < scanRangeEnd; i++) - { - PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList + i; - if (PPCRecompiler_isSuffixInstruction(imlInstruction)) - { - break; - } - // check if GPR is stored - if ((imlInstruction->type == PPCREC_IML_TYPE_STORE && imlInstruction->op_storeLoad.copyWidth == 32 ) ) - { - if (imlInstruction->op_storeLoad.registerMem == gprIndex) - break; - if (imlInstruction->op_storeLoad.registerData == gprIndex) - { - PPCRecImlInstruction_t* imlInstructionStore = imlInstruction; - if (foundMatch == false) - { - // switch the endian swap flag for the load instruction - imlInstructionLoad->op_storeLoad.flags2.swapEndian = !imlInstructionLoad->op_storeLoad.flags2.swapEndian; - foundMatch = true; - } - // switch the endian swap flag for the store instruction - imlInstructionStore->op_storeLoad.flags2.swapEndian = !imlInstructionStore->op_storeLoad.flags2.swapEndian; - // keep scanning - continue; - } - } - // check if GPR is accessed - PPCRecompiler_checkRegisterUsage(ppcImlGenContext, imlInstruction, ®istersUsed); - if (registersUsed.readNamedReg1 == gprIndex || - registersUsed.readNamedReg2 == gprIndex || - registersUsed.readNamedReg3 == gprIndex) - { - break; - } - if (registersUsed.writtenNamedReg1 == gprIndex) - return; // GPR overwritten, we don't need to byte swap anymore - } - if (foundMatch) - { - // insert expand instruction - PPCRecImlInstruction_t* newExpand = PPCRecompiler_insertInstruction(imlSegment, i); - PPCRecompilerImlGen_generateNewInstruction_r_r(ppcImlGenContext, newExpand, PPCREC_IML_OP_ENDIAN_SWAP, gprIndex, gprIndex); - } -} - -/* -* Scans for patterns: -* <Load sp integer into register r> -* <Random unrelated instructions> -* <Store sp integer from register r> -* For these patterns the store and load is modified to work with non-swapped values -* The big_endian->little_endian conversion is then executed later -* Advantages: -* Slightly improves performance -*/ -void PPCRecompiler_optimizeDirectIntegerCopies(ppcImlGenContext_t* ppcImlGenContext) -{ - for (sint32 s = 0; s < ppcImlGenContext->segmentListCount; s++) - { - PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[s]; - - for (sint32 i = 0; i < imlSegment->imlListCount; i++) - { - PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList + i; - if (imlInstruction->type == PPCREC_IML_TYPE_LOAD && imlInstruction->op_storeLoad.copyWidth == 32 && imlInstruction->op_storeLoad.flags2.swapEndian ) - { - PPCRecompiler_optimizeDirectIntegerCopiesScanForward(ppcImlGenContext, imlSegment, i, imlInstruction->op_storeLoad.registerData); - } - } - } -} - -sint32 _getGQRIndexFromRegister(ppcImlGenContext_t* ppcImlGenContext, sint32 registerIndex) -{ - if (registerIndex == PPC_REC_INVALID_REGISTER) - return -1; - sint32 namedReg = ppcImlGenContext->mappedRegister[registerIndex]; - if (namedReg >= (PPCREC_NAME_SPR0 + SPR_UGQR0) && namedReg <= (PPCREC_NAME_SPR0 + SPR_UGQR7)) - { - return namedReg - (PPCREC_NAME_SPR0 + SPR_UGQR0); - } - return -1; -} - -bool PPCRecompiler_isUGQRValueKnown(ppcImlGenContext_t* ppcImlGenContext, sint32 gqrIndex, uint32& gqrValue) -{ - // UGQR 2 to 7 are initialized by the OS and we assume that games won't ever permanently touch those - // todo - hack - replace with more accurate solution - if (gqrIndex == 2) - gqrValue = 0x00040004; - else if (gqrIndex == 3) - gqrValue = 0x00050005; - else if (gqrIndex == 4) - gqrValue = 0x00060006; - else if (gqrIndex == 5) - gqrValue = 0x00070007; - else - return false; - return true; -} - -/* - * If value of GQR can be predicted for a given PSQ load or store instruction then replace it with an optimized version - */ -void PPCRecompiler_optimizePSQLoadAndStore(ppcImlGenContext_t* ppcImlGenContext) -{ - for (sint32 s = 0; s < ppcImlGenContext->segmentListCount; s++) - { - PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[s]; - for (sint32 i = 0; i < imlSegment->imlListCount; i++) - { - PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList + i; - if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD || imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED) - { - if(imlInstruction->op_storeLoad.mode != PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0 && - imlInstruction->op_storeLoad.mode != PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1 ) - continue; - // get GQR value - cemu_assert_debug(imlInstruction->op_storeLoad.registerGQR != PPC_REC_INVALID_REGISTER); - sint32 gqrIndex = _getGQRIndexFromRegister(ppcImlGenContext, imlInstruction->op_storeLoad.registerGQR); - cemu_assert(gqrIndex >= 0); - if (ppcImlGenContext->tracking.modifiesGQR[gqrIndex]) - continue; - //uint32 gqrValue = ppcInterpreterCurrentInstance->sprNew.UGQR[gqrIndex]; - uint32 gqrValue; - if (!PPCRecompiler_isUGQRValueKnown(ppcImlGenContext, gqrIndex, gqrValue)) - continue; - - uint32 formatType = (gqrValue >> 16) & 7; - uint32 scale = (gqrValue >> 24) & 0x3F; - if (scale != 0) - continue; // only generic handler supports scale - if (imlInstruction->op_storeLoad.mode == PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0) - { - if (formatType == 0) - imlInstruction->op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0; - else if (formatType == 4) - imlInstruction->op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_U8_PS0; - else if (formatType == 5) - imlInstruction->op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_U16_PS0; - else if (formatType == 6) - imlInstruction->op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_S8_PS0; - else if (formatType == 7) - imlInstruction->op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_S16_PS0; - } - else if (imlInstruction->op_storeLoad.mode == PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1) - { - if (formatType == 0) - imlInstruction->op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0_PS1; - else if (formatType == 4) - imlInstruction->op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_U8_PS0_PS1; - else if (formatType == 5) - imlInstruction->op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_U16_PS0_PS1; - else if (formatType == 6) - imlInstruction->op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_S8_PS0_PS1; - else if (formatType == 7) - imlInstruction->op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_S16_PS0_PS1; - } - } - else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE || imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE_INDEXED) - { - if(imlInstruction->op_storeLoad.mode != PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0 && - imlInstruction->op_storeLoad.mode != PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1) - continue; - // get GQR value - cemu_assert_debug(imlInstruction->op_storeLoad.registerGQR != PPC_REC_INVALID_REGISTER); - sint32 gqrIndex = _getGQRIndexFromRegister(ppcImlGenContext, imlInstruction->op_storeLoad.registerGQR); - cemu_assert(gqrIndex >= 0); - if (ppcImlGenContext->tracking.modifiesGQR[gqrIndex]) - continue; - uint32 gqrValue; - if(!PPCRecompiler_isUGQRValueKnown(ppcImlGenContext, gqrIndex, gqrValue)) - continue; - uint32 formatType = (gqrValue >> 16) & 7; - uint32 scale = (gqrValue >> 24) & 0x3F; - if (scale != 0) - continue; // only generic handler supports scale - if (imlInstruction->op_storeLoad.mode == PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0) - { - if (formatType == 0) - imlInstruction->op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0; - else if (formatType == 4) - imlInstruction->op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_U8_PS0; - else if (formatType == 5) - imlInstruction->op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_U16_PS0; - else if (formatType == 6) - imlInstruction->op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_S8_PS0; - else if (formatType == 7) - imlInstruction->op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_S16_PS0; - } - else if (imlInstruction->op_storeLoad.mode == PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1) - { - if (formatType == 0) - imlInstruction->op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0_PS1; - else if (formatType == 4) - imlInstruction->op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_U8_PS0_PS1; - else if (formatType == 5) - imlInstruction->op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_U16_PS0_PS1; - else if (formatType == 6) - imlInstruction->op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_S8_PS0_PS1; - else if (formatType == 7) - imlInstruction->op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_S16_PS0_PS1; - } - } - } - } -} - -/* - * Returns true if registerWrite overwrites any of the registers read by registerRead - */ -bool PPCRecompilerAnalyzer_checkForGPROverwrite(PPCImlOptimizerUsedRegisters_t* registerRead, PPCImlOptimizerUsedRegisters_t* registerWrite) -{ - if (registerWrite->writtenNamedReg1 < 0) - return false; - - if (registerWrite->writtenNamedReg1 == registerRead->readNamedReg1) - return true; - if (registerWrite->writtenNamedReg1 == registerRead->readNamedReg2) - return true; - if (registerWrite->writtenNamedReg1 == registerRead->readNamedReg3) - return true; - return false; -} - -void _reorderConditionModifyInstructions(PPCRecImlSegment_t* imlSegment) -{ - PPCRecImlInstruction_t* lastInstruction = PPCRecompilerIML_getLastInstruction(imlSegment); - // last instruction a conditional branch? - if (lastInstruction == nullptr || lastInstruction->type != PPCREC_IML_TYPE_CJUMP) - return; - if (lastInstruction->op_conditionalJump.crRegisterIndex >= 8) - return; - // get CR bitmask of bit required for conditional jump - PPCRecCRTracking_t crTracking; - PPCRecompilerImlAnalyzer_getCRTracking(lastInstruction, &crTracking); - uint32 requiredCRBits = crTracking.readCRBits; - - // scan backwards until we find the instruction that sets the CR - sint32 crSetterInstructionIndex = -1; - sint32 unsafeInstructionIndex = -1; - for (sint32 i = imlSegment->imlListCount-2; i >= 0; i--) - { - PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList + i; - PPCRecompilerImlAnalyzer_getCRTracking(imlInstruction, &crTracking); - if (crTracking.readCRBits != 0) - return; // dont handle complex cases for now - if (crTracking.writtenCRBits != 0) - { - if ((crTracking.writtenCRBits&requiredCRBits) != 0) - { - crSetterInstructionIndex = i; - break; - } - else - { - return; // other CR bits overwritten (dont handle complex cases) - } - } - // is safe? (no risk of overwriting x64 eflags) - if ((imlInstruction->type == PPCREC_IML_TYPE_NAME_R || imlInstruction->type == PPCREC_IML_TYPE_R_NAME || imlInstruction->type == PPCREC_IML_TYPE_NO_OP) || - (imlInstruction->type == PPCREC_IML_TYPE_FPR_NAME_R || imlInstruction->type == PPCREC_IML_TYPE_FPR_R_NAME) || - (imlInstruction->type == PPCREC_IML_TYPE_R_S32 && (imlInstruction->operation == PPCREC_IML_OP_ASSIGN)) || - (imlInstruction->type == PPCREC_IML_TYPE_R_R && (imlInstruction->operation == PPCREC_IML_OP_ASSIGN)) ) - continue; - // not safe - //hasUnsafeInstructions = true; - if (unsafeInstructionIndex == -1) - unsafeInstructionIndex = i; - } - if (crSetterInstructionIndex < 0) - return; - if (unsafeInstructionIndex < 0) - return; // no danger of overwriting eflags, don't reorder - // check if we can move the CR setter instruction to after unsafeInstructionIndex - PPCRecCRTracking_t crTrackingSetter = crTracking; - PPCImlOptimizerUsedRegisters_t regTrackingCRSetter; - PPCRecompiler_checkRegisterUsage(NULL, imlSegment->imlList+crSetterInstructionIndex, ®TrackingCRSetter); - if (regTrackingCRSetter.writtenFPR1 >= 0 || regTrackingCRSetter.readFPR1 >= 0 || regTrackingCRSetter.readFPR2 >= 0 || regTrackingCRSetter.readFPR3 >= 0 || regTrackingCRSetter.readFPR4 >= 0) - return; // we don't handle FPR dependency yet so just ignore FPR instructions - PPCImlOptimizerUsedRegisters_t registerTracking; - if (regTrackingCRSetter.writtenNamedReg1 >= 0) - { - // CR setter does write GPR - for (sint32 i = crSetterInstructionIndex + 1; i <= unsafeInstructionIndex; i++) - { - PPCRecompiler_checkRegisterUsage(NULL, imlSegment->imlList + i, ®isterTracking); - // reads register written by CR setter? - if (PPCRecompilerAnalyzer_checkForGPROverwrite(®isterTracking, ®TrackingCRSetter)) - { - return; // cant move CR setter because of dependency - } - // writes register read by CR setter? - if (PPCRecompilerAnalyzer_checkForGPROverwrite(®TrackingCRSetter, ®isterTracking)) - { - return; // cant move CR setter because of dependency - } - // overwrites register written by CR setter? - if (regTrackingCRSetter.writtenNamedReg1 == registerTracking.writtenNamedReg1) - return; - } - } - else - { - // CR setter does not write GPR - for (sint32 i = crSetterInstructionIndex + 1; i <= unsafeInstructionIndex; i++) - { - PPCRecompiler_checkRegisterUsage(NULL, imlSegment->imlList + i, ®isterTracking); - // writes register read by CR setter? - if (PPCRecompilerAnalyzer_checkForGPROverwrite(®TrackingCRSetter, ®isterTracking)) - { - return; // cant move CR setter because of dependency - } - } - } - - // move CR setter instruction -#ifdef CEMU_DEBUG_ASSERT - if ((unsafeInstructionIndex + 1) <= crSetterInstructionIndex) - assert_dbg(); -#endif - PPCRecImlInstruction_t* newCRSetterInstruction = PPCRecompiler_insertInstruction(imlSegment, unsafeInstructionIndex+1); - memcpy(newCRSetterInstruction, imlSegment->imlList + crSetterInstructionIndex, sizeof(PPCRecImlInstruction_t)); - PPCRecompilerImlGen_generateNewInstruction_noOp(NULL, imlSegment->imlList + crSetterInstructionIndex); -} - -/* - * Move instructions which update the condition flags closer to the instruction that consumes them - * On x64 this improves performance since we often can avoid storing CR in memory - */ -void PPCRecompiler_reorderConditionModifyInstructions(ppcImlGenContext_t* ppcImlGenContext) -{ - // check if this segment has a conditional branch - for (sint32 s = 0; s < ppcImlGenContext->segmentListCount; s++) - { - PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[s]; - _reorderConditionModifyInstructions(imlSegment); - } -} diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlRanges.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlRanges.cpp deleted file mode 100644 index d31c02d4..00000000 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlRanges.cpp +++ /dev/null @@ -1,399 +0,0 @@ -#include "PPCRecompiler.h" -#include "PPCRecompilerIml.h" -#include "PPCRecompilerX64.h" -#include "PPCRecompilerImlRanges.h" -#include "util/helpers/MemoryPool.h" - -void PPCRecRARange_addLink_perVirtualGPR(raLivenessSubrange_t** root, raLivenessSubrange_t* subrange) -{ -#ifdef CEMU_DEBUG_ASSERT - if ((*root) && (*root)->range->virtualRegister != subrange->range->virtualRegister) - assert_dbg(); -#endif - subrange->link_sameVirtualRegisterGPR.next = *root; - if (*root) - (*root)->link_sameVirtualRegisterGPR.prev = subrange; - subrange->link_sameVirtualRegisterGPR.prev = nullptr; - *root = subrange; -} - -void PPCRecRARange_addLink_allSubrangesGPR(raLivenessSubrange_t** root, raLivenessSubrange_t* subrange) -{ - subrange->link_segmentSubrangesGPR.next = *root; - if (*root) - (*root)->link_segmentSubrangesGPR.prev = subrange; - subrange->link_segmentSubrangesGPR.prev = nullptr; - *root = subrange; -} - -void PPCRecRARange_removeLink_perVirtualGPR(raLivenessSubrange_t** root, raLivenessSubrange_t* subrange) -{ - raLivenessSubrange_t* tempPrev = subrange->link_sameVirtualRegisterGPR.prev; - if (subrange->link_sameVirtualRegisterGPR.prev) - subrange->link_sameVirtualRegisterGPR.prev->link_sameVirtualRegisterGPR.next = subrange->link_sameVirtualRegisterGPR.next; - else - (*root) = subrange->link_sameVirtualRegisterGPR.next; - if (subrange->link_sameVirtualRegisterGPR.next) - subrange->link_sameVirtualRegisterGPR.next->link_sameVirtualRegisterGPR.prev = tempPrev; -#ifdef CEMU_DEBUG_ASSERT - subrange->link_sameVirtualRegisterGPR.prev = (raLivenessSubrange_t*)1; - subrange->link_sameVirtualRegisterGPR.next = (raLivenessSubrange_t*)1; -#endif -} - -void PPCRecRARange_removeLink_allSubrangesGPR(raLivenessSubrange_t** root, raLivenessSubrange_t* subrange) -{ - raLivenessSubrange_t* tempPrev = subrange->link_segmentSubrangesGPR.prev; - if (subrange->link_segmentSubrangesGPR.prev) - subrange->link_segmentSubrangesGPR.prev->link_segmentSubrangesGPR.next = subrange->link_segmentSubrangesGPR.next; - else - (*root) = subrange->link_segmentSubrangesGPR.next; - if (subrange->link_segmentSubrangesGPR.next) - subrange->link_segmentSubrangesGPR.next->link_segmentSubrangesGPR.prev = tempPrev; -#ifdef CEMU_DEBUG_ASSERT - subrange->link_segmentSubrangesGPR.prev = (raLivenessSubrange_t*)1; - subrange->link_segmentSubrangesGPR.next = (raLivenessSubrange_t*)1; -#endif -} - -MemoryPoolPermanentObjects<raLivenessRange_t> memPool_livenessRange(4096); -MemoryPoolPermanentObjects<raLivenessSubrange_t> memPool_livenessSubrange(4096); - -raLivenessRange_t* PPCRecRA_createRangeBase(ppcImlGenContext_t* ppcImlGenContext, uint32 virtualRegister, uint32 name) -{ - raLivenessRange_t* livenessRange = memPool_livenessRange.acquireObj(); - livenessRange->list_subranges.resize(0); - livenessRange->virtualRegister = virtualRegister; - livenessRange->name = name; - livenessRange->physicalRegister = -1; - ppcImlGenContext->raInfo.list_ranges.push_back(livenessRange); - return livenessRange; -} - -raLivenessSubrange_t* PPCRecRA_createSubrange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange_t* range, PPCRecImlSegment_t* imlSegment, sint32 startIndex, sint32 endIndex) -{ - raLivenessSubrange_t* livenessSubrange = memPool_livenessSubrange.acquireObj(); - livenessSubrange->list_locations.resize(0); - livenessSubrange->range = range; - livenessSubrange->imlSegment = imlSegment; - PPCRecompilerIml_setSegmentPoint(&livenessSubrange->start, imlSegment, startIndex); - PPCRecompilerIml_setSegmentPoint(&livenessSubrange->end, imlSegment, endIndex); - // default values - livenessSubrange->hasStore = false; - livenessSubrange->hasStoreDelayed = false; - livenessSubrange->lastIterationIndex = 0; - livenessSubrange->subrangeBranchNotTaken = nullptr; - livenessSubrange->subrangeBranchTaken = nullptr; - livenessSubrange->_noLoad = false; - // add to range - range->list_subranges.push_back(livenessSubrange); - // add to segment - PPCRecRARange_addLink_perVirtualGPR(&(imlSegment->raInfo.linkedList_perVirtualGPR[range->virtualRegister]), livenessSubrange); - PPCRecRARange_addLink_allSubrangesGPR(&imlSegment->raInfo.linkedList_allSubranges, livenessSubrange); - return livenessSubrange; -} - -void _unlinkSubrange(raLivenessSubrange_t* subrange) -{ - PPCRecImlSegment_t* imlSegment = subrange->imlSegment; - PPCRecRARange_removeLink_perVirtualGPR(&imlSegment->raInfo.linkedList_perVirtualGPR[subrange->range->virtualRegister], subrange); - PPCRecRARange_removeLink_allSubrangesGPR(&imlSegment->raInfo.linkedList_allSubranges, subrange); -} - -void PPCRecRA_deleteSubrange(ppcImlGenContext_t* ppcImlGenContext, raLivenessSubrange_t* subrange) -{ - _unlinkSubrange(subrange); - subrange->range->list_subranges.erase(std::find(subrange->range->list_subranges.begin(), subrange->range->list_subranges.end(), subrange)); - subrange->list_locations.clear(); - PPCRecompilerIml_removeSegmentPoint(&subrange->start); - PPCRecompilerIml_removeSegmentPoint(&subrange->end); - memPool_livenessSubrange.releaseObj(subrange); -} - -void _PPCRecRA_deleteSubrangeNoUnlinkFromRange(ppcImlGenContext_t* ppcImlGenContext, raLivenessSubrange_t* subrange) -{ - _unlinkSubrange(subrange); - PPCRecompilerIml_removeSegmentPoint(&subrange->start); - PPCRecompilerIml_removeSegmentPoint(&subrange->end); - memPool_livenessSubrange.releaseObj(subrange); -} - -void PPCRecRA_deleteRange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange_t* range) -{ - for (auto& subrange : range->list_subranges) - { - _PPCRecRA_deleteSubrangeNoUnlinkFromRange(ppcImlGenContext, subrange); - } - ppcImlGenContext->raInfo.list_ranges.erase(std::find(ppcImlGenContext->raInfo.list_ranges.begin(), ppcImlGenContext->raInfo.list_ranges.end(), range)); - memPool_livenessRange.releaseObj(range); -} - -void PPCRecRA_deleteRangeNoUnlink(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange_t* range) -{ - for (auto& subrange : range->list_subranges) - { - _PPCRecRA_deleteSubrangeNoUnlinkFromRange(ppcImlGenContext, subrange); - } - memPool_livenessRange.releaseObj(range); -} - -void PPCRecRA_deleteAllRanges(ppcImlGenContext_t* ppcImlGenContext) -{ - for(auto& range : ppcImlGenContext->raInfo.list_ranges) - { - PPCRecRA_deleteRangeNoUnlink(ppcImlGenContext, range); - } - ppcImlGenContext->raInfo.list_ranges.clear(); -} - -void PPCRecRA_mergeRanges(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange_t* range, raLivenessRange_t* absorbedRange) -{ - cemu_assert_debug(range != absorbedRange); - cemu_assert_debug(range->virtualRegister == absorbedRange->virtualRegister); - // move all subranges from absorbedRange to range - for (auto& subrange : absorbedRange->list_subranges) - { - range->list_subranges.push_back(subrange); - subrange->range = range; - } - absorbedRange->list_subranges.clear(); - PPCRecRA_deleteRange(ppcImlGenContext, absorbedRange); -} - -void PPCRecRA_mergeSubranges(ppcImlGenContext_t* ppcImlGenContext, raLivenessSubrange_t* subrange, raLivenessSubrange_t* absorbedSubrange) -{ -#ifdef CEMU_DEBUG_ASSERT - PPCRecRA_debugValidateSubrange(subrange); - PPCRecRA_debugValidateSubrange(absorbedSubrange); - if (subrange->imlSegment != absorbedSubrange->imlSegment) - assert_dbg(); - if (subrange->end.index > absorbedSubrange->start.index) - assert_dbg(); - if (subrange->subrangeBranchTaken || subrange->subrangeBranchNotTaken) - assert_dbg(); - if (subrange == absorbedSubrange) - assert_dbg(); -#endif - subrange->subrangeBranchTaken = absorbedSubrange->subrangeBranchTaken; - subrange->subrangeBranchNotTaken = absorbedSubrange->subrangeBranchNotTaken; - - // merge usage locations - for (auto& location : absorbedSubrange->list_locations) - { - subrange->list_locations.push_back(location); - } - absorbedSubrange->list_locations.clear(); - - subrange->end.index = absorbedSubrange->end.index; - - PPCRecRA_debugValidateSubrange(subrange); - - PPCRecRA_deleteSubrange(ppcImlGenContext, absorbedSubrange); -} - -// remove all inter-segment connections from the range and split it into local ranges (also removes empty ranges) -void PPCRecRA_explodeRange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange_t* range) -{ - if (range->list_subranges.size() == 1) - assert_dbg(); - for (auto& subrange : range->list_subranges) - { - if (subrange->list_locations.empty()) - continue; - raLivenessRange_t* newRange = PPCRecRA_createRangeBase(ppcImlGenContext, range->virtualRegister, range->name); - raLivenessSubrange_t* newSubrange = PPCRecRA_createSubrange(ppcImlGenContext, newRange, subrange->imlSegment, subrange->list_locations.data()[0].index, subrange->list_locations.data()[subrange->list_locations.size() - 1].index + 1); - // copy locations - for (auto& location : subrange->list_locations) - { - newSubrange->list_locations.push_back(location); - } - } - // remove original range - PPCRecRA_deleteRange(ppcImlGenContext, range); -} - -#ifdef CEMU_DEBUG_ASSERT -void PPCRecRA_debugValidateSubrange(raLivenessSubrange_t* subrange) -{ - // validate subrange - if (subrange->subrangeBranchTaken && subrange->subrangeBranchTaken->imlSegment != subrange->imlSegment->nextSegmentBranchTaken) - assert_dbg(); - if (subrange->subrangeBranchNotTaken && subrange->subrangeBranchNotTaken->imlSegment != subrange->imlSegment->nextSegmentBranchNotTaken) - assert_dbg(); -} -#else -void PPCRecRA_debugValidateSubrange(raLivenessSubrange_t* subrange) {} -#endif - -// split subrange at the given index -// After the split there will be two ranges/subranges: -// head -> subrange is shortned to end at splitIndex -// tail -> a new subrange that reaches from splitIndex to the end of the original subrange -// if head has a physical register assigned it will not carry over to tail -// The return value is the tail subrange -// If trimToHole is true, the end of the head subrange and the start of the tail subrange will be moved to fit the locations -// Ranges that begin at RA_INTER_RANGE_START are allowed and can be split -raLivenessSubrange_t* PPCRecRA_splitLocalSubrange(ppcImlGenContext_t* ppcImlGenContext, raLivenessSubrange_t* subrange, sint32 splitIndex, bool trimToHole) -{ - // validation -#ifdef CEMU_DEBUG_ASSERT - if (subrange->end.index == RA_INTER_RANGE_END || subrange->end.index == RA_INTER_RANGE_START) - assert_dbg(); - if (subrange->start.index >= splitIndex) - assert_dbg(); - if (subrange->end.index <= splitIndex) - assert_dbg(); -#endif - // create tail - raLivenessRange_t* tailRange = PPCRecRA_createRangeBase(ppcImlGenContext, subrange->range->virtualRegister, subrange->range->name); - raLivenessSubrange_t* tailSubrange = PPCRecRA_createSubrange(ppcImlGenContext, tailRange, subrange->imlSegment, splitIndex, subrange->end.index); - // copy locations - for (auto& location : subrange->list_locations) - { - if (location.index >= splitIndex) - tailSubrange->list_locations.push_back(location); - } - // remove tail locations from head - for (sint32 i = 0; i < subrange->list_locations.size(); i++) - { - raLivenessLocation_t* location = subrange->list_locations.data() + i; - if (location->index >= splitIndex) - { - subrange->list_locations.resize(i); - break; - } - } - // adjust start/end - if (trimToHole) - { - if (subrange->list_locations.empty()) - { - subrange->end.index = subrange->start.index+1; - } - else - { - subrange->end.index = subrange->list_locations.back().index + 1; - } - if (tailSubrange->list_locations.empty()) - { - assert_dbg(); // should not happen? (In this case we can just avoid generating a tail at all) - } - else - { - tailSubrange->start.index = tailSubrange->list_locations.front().index; - } - } - return tailSubrange; -} - -void PPCRecRA_updateOrAddSubrangeLocation(raLivenessSubrange_t* subrange, sint32 index, bool isRead, bool isWrite) -{ - if (subrange->list_locations.empty()) - { - subrange->list_locations.emplace_back(index, isRead, isWrite); - return; - } - raLivenessLocation_t* lastLocation = subrange->list_locations.data() + (subrange->list_locations.size() - 1); - cemu_assert_debug(lastLocation->index <= index); - if (lastLocation->index == index) - { - // update - lastLocation->isRead = lastLocation->isRead || isRead; - lastLocation->isWrite = lastLocation->isWrite || isWrite; - return; - } - // add new - subrange->list_locations.emplace_back(index, isRead, isWrite); -} - -sint32 PPCRecRARange_getReadWriteCost(PPCRecImlSegment_t* imlSegment) -{ - sint32 v = imlSegment->loopDepth + 1; - v *= 5; - return v*v; // 25, 100, 225, 400 -} - -// calculate cost of entire range -// ignores data flow and does not detect avoidable reads/stores -sint32 PPCRecRARange_estimateCost(raLivenessRange_t* range) -{ - sint32 cost = 0; - - // todo - this algorithm isn't accurate. If we have 10 parallel branches with a load each then the actual cost is still only that of one branch (plus minimal extra cost for generating more code). - - // currently we calculate the cost based on the most expensive entry/exit point - - sint32 mostExpensiveRead = 0; - sint32 mostExpensiveWrite = 0; - sint32 readCount = 0; - sint32 writeCount = 0; - - for (auto& subrange : range->list_subranges) - { - if (subrange->start.index != RA_INTER_RANGE_START) - { - //cost += PPCRecRARange_getReadWriteCost(subrange->imlSegment); - mostExpensiveRead = std::max(mostExpensiveRead, PPCRecRARange_getReadWriteCost(subrange->imlSegment)); - readCount++; - } - if (subrange->end.index != RA_INTER_RANGE_END) - { - //cost += PPCRecRARange_getReadWriteCost(subrange->imlSegment); - mostExpensiveWrite = std::max(mostExpensiveWrite, PPCRecRARange_getReadWriteCost(subrange->imlSegment)); - writeCount++; - } - } - cost = mostExpensiveRead + mostExpensiveWrite; - cost = cost + (readCount + writeCount) / 10; - return cost; -} - -// calculate cost of range that it would have after calling PPCRecRA_explodeRange() on it -sint32 PPCRecRARange_estimateAdditionalCostAfterRangeExplode(raLivenessRange_t* range) -{ - sint32 cost = -PPCRecRARange_estimateCost(range); - for (auto& subrange : range->list_subranges) - { - if (subrange->list_locations.empty()) - continue; - cost += PPCRecRARange_getReadWriteCost(subrange->imlSegment) * 2; // we assume a read and a store - } - return cost; -} - -sint32 PPCRecRARange_estimateAdditionalCostAfterSplit(raLivenessSubrange_t* subrange, sint32 splitIndex) -{ - // validation -#ifdef CEMU_DEBUG_ASSERT - if (subrange->end.index == RA_INTER_RANGE_END) - assert_dbg(); -#endif - - sint32 cost = 0; - // find split position in location list - if (subrange->list_locations.empty()) - { - assert_dbg(); // should not happen? - return 0; - } - if (splitIndex <= subrange->list_locations.front().index) - return 0; - if (splitIndex > subrange->list_locations.back().index) - return 0; - - // todo - determine exact cost of split subranges - - cost += PPCRecRARange_getReadWriteCost(subrange->imlSegment) * 2; // currently we assume that the additional region will require a read and a store - - //for (sint32 f = 0; f < subrange->list_locations.size(); f++) - //{ - // raLivenessLocation_t* location = subrange->list_locations.data() + f; - // if (location->index >= splitIndex) - // { - // ... - // return cost; - // } - //} - - return cost; -} diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlRanges.h b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlRanges.h deleted file mode 100644 index 01970bbf..00000000 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlRanges.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -raLivenessRange_t* PPCRecRA_createRangeBase(ppcImlGenContext_t* ppcImlGenContext, uint32 virtualRegister, uint32 name); -raLivenessSubrange_t* PPCRecRA_createSubrange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange_t* range, PPCRecImlSegment_t* imlSegment, sint32 startIndex, sint32 endIndex); -void PPCRecRA_deleteSubrange(ppcImlGenContext_t* ppcImlGenContext, raLivenessSubrange_t* subrange); -void PPCRecRA_deleteRange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange_t* range); -void PPCRecRA_deleteAllRanges(ppcImlGenContext_t* ppcImlGenContext); - -void PPCRecRA_mergeRanges(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange_t* range, raLivenessRange_t* absorbedRange); -void PPCRecRA_explodeRange(ppcImlGenContext_t* ppcImlGenContext, raLivenessRange_t* range); - -void PPCRecRA_mergeSubranges(ppcImlGenContext_t* ppcImlGenContext, raLivenessSubrange_t* subrange, raLivenessSubrange_t* absorbedSubrange); - -raLivenessSubrange_t* PPCRecRA_splitLocalSubrange(ppcImlGenContext_t* ppcImlGenContext, raLivenessSubrange_t* subrange, sint32 splitIndex, bool trimToHole = false); - -void PPCRecRA_updateOrAddSubrangeLocation(raLivenessSubrange_t* subrange, sint32 index, bool isRead, bool isWrite); -void PPCRecRA_debugValidateSubrange(raLivenessSubrange_t* subrange); - -// cost estimation -sint32 PPCRecRARange_getReadWriteCost(PPCRecImlSegment_t* imlSegment); -sint32 PPCRecRARange_estimateCost(raLivenessRange_t* range); -sint32 PPCRecRARange_estimateAdditionalCostAfterRangeExplode(raLivenessRange_t* range); -sint32 PPCRecRARange_estimateAdditionalCostAfterSplit(raLivenessSubrange_t* subrange, sint32 splitIndex); - -// special values to mark the index of ranges that reach across the segment border -#define RA_INTER_RANGE_START (-1) -#define RA_INTER_RANGE_END (0x70000000) diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlRegisterAllocator.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlRegisterAllocator.cpp deleted file mode 100644 index 88d387e6..00000000 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlRegisterAllocator.cpp +++ /dev/null @@ -1,1012 +0,0 @@ -#include "PPCRecompiler.h" -#include "PPCRecompilerIml.h" -#include "PPCRecompilerX64.h" -#include "PPCRecompilerImlRanges.h" - -void PPCRecompiler_replaceGPRRegisterUsageMultiple(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlInstruction_t* imlInstruction, sint32 gprRegisterSearched[4], sint32 gprRegisterReplaced[4]); - -bool PPCRecompiler_isSuffixInstruction(PPCRecImlInstruction_t* iml); - -uint32 recRACurrentIterationIndex = 0; - -uint32 PPCRecRA_getNextIterationIndex() -{ - recRACurrentIterationIndex++; - return recRACurrentIterationIndex; -} - -bool _detectLoop(PPCRecImlSegment_t* currentSegment, sint32 depth, uint32 iterationIndex, PPCRecImlSegment_t* imlSegmentLoopBase) -{ - if (currentSegment == imlSegmentLoopBase) - return true; - if (currentSegment->raInfo.lastIterationIndex == iterationIndex) - return currentSegment->raInfo.isPartOfProcessedLoop; - if (depth >= 9) - return false; - currentSegment->raInfo.lastIterationIndex = iterationIndex; - currentSegment->raInfo.isPartOfProcessedLoop = false; - - if (currentSegment->nextSegmentIsUncertain) - return false; - if (currentSegment->nextSegmentBranchNotTaken) - { - if (currentSegment->nextSegmentBranchNotTaken->momentaryIndex > currentSegment->momentaryIndex) - { - currentSegment->raInfo.isPartOfProcessedLoop = _detectLoop(currentSegment->nextSegmentBranchNotTaken, depth + 1, iterationIndex, imlSegmentLoopBase); - } - } - if (currentSegment->nextSegmentBranchTaken) - { - if (currentSegment->nextSegmentBranchTaken->momentaryIndex > currentSegment->momentaryIndex) - { - currentSegment->raInfo.isPartOfProcessedLoop = _detectLoop(currentSegment->nextSegmentBranchTaken, depth + 1, iterationIndex, imlSegmentLoopBase); - } - } - if (currentSegment->raInfo.isPartOfProcessedLoop) - currentSegment->loopDepth++; - return currentSegment->raInfo.isPartOfProcessedLoop; -} - -void PPCRecRA_detectLoop(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegmentLoopBase) -{ - uint32 iterationIndex = PPCRecRA_getNextIterationIndex(); - imlSegmentLoopBase->raInfo.lastIterationIndex = iterationIndex; - if (_detectLoop(imlSegmentLoopBase->nextSegmentBranchTaken, 0, iterationIndex, imlSegmentLoopBase)) - { - imlSegmentLoopBase->loopDepth++; - } -} - -void PPCRecRA_identifyLoop(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment) -{ - if (imlSegment->nextSegmentIsUncertain) - return; - // check if this segment has a branch that links to itself (tight loop) - if (imlSegment->nextSegmentBranchTaken == imlSegment) - { - // segment loops over itself - imlSegment->loopDepth++; - return; - } - // check if this segment has a branch that goes backwards (potential complex loop) - if (imlSegment->nextSegmentBranchTaken && imlSegment->nextSegmentBranchTaken->momentaryIndex < imlSegment->momentaryIndex) - { - PPCRecRA_detectLoop(ppcImlGenContext, imlSegment); - } -} - -typedef struct -{ - sint32 name; - sint32 virtualRegister; - sint32 physicalRegister; - bool isDirty; -}raRegisterState_t; - -const sint32 _raInfo_physicalGPRCount = PPC_X64_GPR_USABLE_REGISTERS; - -raRegisterState_t* PPCRecRA_getRegisterState(raRegisterState_t* regState, sint32 virtualRegister) -{ - for (sint32 i = 0; i < _raInfo_physicalGPRCount; i++) - { - if (regState[i].virtualRegister == virtualRegister) - { -#ifdef CEMU_DEBUG_ASSERT - if (regState[i].physicalRegister < 0) - assert_dbg(); -#endif - return regState + i; - } - } - return nullptr; -} - -raRegisterState_t* PPCRecRA_getFreePhysicalRegister(raRegisterState_t* regState) -{ - for (sint32 i = 0; i < _raInfo_physicalGPRCount; i++) - { - if (regState[i].physicalRegister < 0) - { - regState[i].physicalRegister = i; - return regState + i; - } - } - return nullptr; -} - -typedef struct -{ - uint16 registerIndex; - uint16 registerName; -}raLoadStoreInfo_t; - -void PPCRecRA_insertGPRLoadInstruction(PPCRecImlSegment_t* imlSegment, sint32 insertIndex, sint32 registerIndex, sint32 registerName) -{ - PPCRecompiler_pushBackIMLInstructions(imlSegment, insertIndex, 1); - PPCRecImlInstruction_t* imlInstructionItr = imlSegment->imlList + (insertIndex + 0); - memset(imlInstructionItr, 0x00, sizeof(PPCRecImlInstruction_t)); - imlInstructionItr->type = PPCREC_IML_TYPE_R_NAME; - imlInstructionItr->operation = PPCREC_IML_OP_ASSIGN; - imlInstructionItr->op_r_name.registerIndex = registerIndex; - imlInstructionItr->op_r_name.name = registerName; - imlInstructionItr->op_r_name.copyWidth = 32; - imlInstructionItr->op_r_name.flags = 0; -} - -void PPCRecRA_insertGPRLoadInstructions(PPCRecImlSegment_t* imlSegment, sint32 insertIndex, raLoadStoreInfo_t* loadList, sint32 loadCount) -{ - PPCRecompiler_pushBackIMLInstructions(imlSegment, insertIndex, loadCount); - memset(imlSegment->imlList + (insertIndex + 0), 0x00, sizeof(PPCRecImlInstruction_t)*loadCount); - for (sint32 i = 0; i < loadCount; i++) - { - PPCRecImlInstruction_t* imlInstructionItr = imlSegment->imlList + (insertIndex + i); - imlInstructionItr->type = PPCREC_IML_TYPE_R_NAME; - imlInstructionItr->operation = PPCREC_IML_OP_ASSIGN; - imlInstructionItr->op_r_name.registerIndex = (uint8)loadList[i].registerIndex; - imlInstructionItr->op_r_name.name = (uint32)loadList[i].registerName; - imlInstructionItr->op_r_name.copyWidth = 32; - imlInstructionItr->op_r_name.flags = 0; - } -} - -void PPCRecRA_insertGPRStoreInstruction(PPCRecImlSegment_t* imlSegment, sint32 insertIndex, sint32 registerIndex, sint32 registerName) -{ - PPCRecompiler_pushBackIMLInstructions(imlSegment, insertIndex, 1); - PPCRecImlInstruction_t* imlInstructionItr = imlSegment->imlList + (insertIndex + 0); - memset(imlInstructionItr, 0x00, sizeof(PPCRecImlInstruction_t)); - imlInstructionItr->type = PPCREC_IML_TYPE_NAME_R; - imlInstructionItr->operation = PPCREC_IML_OP_ASSIGN; - imlInstructionItr->op_r_name.registerIndex = registerIndex; - imlInstructionItr->op_r_name.name = registerName; - imlInstructionItr->op_r_name.copyWidth = 32; - imlInstructionItr->op_r_name.flags = 0; -} - -void PPCRecRA_insertGPRStoreInstructions(PPCRecImlSegment_t* imlSegment, sint32 insertIndex, raLoadStoreInfo_t* storeList, sint32 storeCount) -{ - PPCRecompiler_pushBackIMLInstructions(imlSegment, insertIndex, storeCount); - memset(imlSegment->imlList + (insertIndex + 0), 0x00, sizeof(PPCRecImlInstruction_t)*storeCount); - for (sint32 i = 0; i < storeCount; i++) - { - PPCRecImlInstruction_t* imlInstructionItr = imlSegment->imlList + (insertIndex + i); - memset(imlInstructionItr, 0x00, sizeof(PPCRecImlInstruction_t)); - imlInstructionItr->type = PPCREC_IML_TYPE_NAME_R; - imlInstructionItr->operation = PPCREC_IML_OP_ASSIGN; - imlInstructionItr->op_r_name.registerIndex = (uint8)storeList[i].registerIndex; - imlInstructionItr->op_r_name.name = (uint32)storeList[i].registerName; - imlInstructionItr->op_r_name.copyWidth = 32; - imlInstructionItr->op_r_name.flags = 0; - } -} - -#define SUBRANGE_LIST_SIZE (128) - -sint32 PPCRecRA_countInstructionsUntilNextUse(raLivenessSubrange_t* subrange, sint32 startIndex) -{ - for (sint32 i = 0; i < subrange->list_locations.size(); i++) - { - if (subrange->list_locations.data()[i].index >= startIndex) - return subrange->list_locations.data()[i].index - startIndex; - } - return INT_MAX; -} - -// count how many instructions there are until physRegister is used by any subrange (returns 0 if register is in use at startIndex, and INT_MAX if not used for the remainder of the segment) -sint32 PPCRecRA_countInstructionsUntilNextLocalPhysRegisterUse(PPCRecImlSegment_t* imlSegment, sint32 startIndex, sint32 physRegister) -{ - sint32 minDistance = INT_MAX; - // next - raLivenessSubrange_t* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; - while(subrangeItr) - { - if (subrangeItr->range->physicalRegister != physRegister) - { - subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; - continue; - } - if (startIndex >= subrangeItr->start.index && startIndex < subrangeItr->end.index) - return 0; - if (subrangeItr->start.index >= startIndex) - { - minDistance = std::min(minDistance, (subrangeItr->start.index - startIndex)); - } - subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; - } - return minDistance; -} - -typedef struct -{ - raLivenessSubrange_t* liveRangeList[64]; - sint32 liveRangesCount; -}raLiveRangeInfo_t; - -// return a bitmask that contains only registers that are not used by any colliding range -uint32 PPCRecRA_getAllowedRegisterMaskForFullRange(raLivenessRange_t* range) -{ - uint32 physRegisterMask = (1 << PPC_X64_GPR_USABLE_REGISTERS) - 1; - for (auto& subrange : range->list_subranges) - { - PPCRecImlSegment_t* imlSegment = subrange->imlSegment; - raLivenessSubrange_t* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; - while(subrangeItr) - { - if (subrange == subrangeItr) - { - // next - subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; - continue; - } - - if (subrange->start.index < subrangeItr->end.index && subrange->end.index > subrangeItr->start.index || - (subrange->start.index == RA_INTER_RANGE_START && subrange->start.index == subrangeItr->start.index) || - (subrange->end.index == RA_INTER_RANGE_END && subrange->end.index == subrangeItr->end.index) ) - { - if(subrangeItr->range->physicalRegister >= 0) - physRegisterMask &= ~(1<<(subrangeItr->range->physicalRegister)); - } - // next - subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; - } - } - return physRegisterMask; -} - -bool _livenessRangeStartCompare(raLivenessSubrange_t* lhs, raLivenessSubrange_t* rhs) { return lhs->start.index < rhs->start.index; } - -void _sortSegmentAllSubrangesLinkedList(PPCRecImlSegment_t* imlSegment) -{ - raLivenessSubrange_t* subrangeList[4096+1]; - sint32 count = 0; - // disassemble linked list - raLivenessSubrange_t* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; - while (subrangeItr) - { - if (count >= 4096) - assert_dbg(); - subrangeList[count] = subrangeItr; - count++; - // next - subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; - } - if (count == 0) - { - imlSegment->raInfo.linkedList_allSubranges = nullptr; - return; - } - // sort - std::sort(subrangeList, subrangeList + count, _livenessRangeStartCompare); - //for (sint32 i1 = 0; i1 < count; i1++) - //{ - // for (sint32 i2 = i1+1; i2 < count; i2++) - // { - // if (subrangeList[i1]->start.index > subrangeList[i2]->start.index) - // { - // // swap - // raLivenessSubrange_t* temp = subrangeList[i1]; - // subrangeList[i1] = subrangeList[i2]; - // subrangeList[i2] = temp; - // } - // } - //} - // reassemble linked list - subrangeList[count] = nullptr; - imlSegment->raInfo.linkedList_allSubranges = subrangeList[0]; - subrangeList[0]->link_segmentSubrangesGPR.prev = nullptr; - subrangeList[0]->link_segmentSubrangesGPR.next = subrangeList[1]; - for (sint32 i = 1; i < count; i++) - { - subrangeList[i]->link_segmentSubrangesGPR.prev = subrangeList[i - 1]; - subrangeList[i]->link_segmentSubrangesGPR.next = subrangeList[i + 1]; - } - // validate list -#ifdef CEMU_DEBUG_ASSERT - sint32 count2 = 0; - subrangeItr = imlSegment->raInfo.linkedList_allSubranges; - sint32 currentStartIndex = RA_INTER_RANGE_START; - while (subrangeItr) - { - count2++; - if (subrangeItr->start.index < currentStartIndex) - assert_dbg(); - currentStartIndex = subrangeItr->start.index; - // next - subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; - } - if (count != count2) - assert_dbg(); -#endif -} - -bool PPCRecRA_assignSegmentRegisters(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment) -{ - - // sort subranges ascending by start index - - //std::sort(imlSegment->raInfo.list_subranges.begin(), imlSegment->raInfo.list_subranges.end(), _sortSubrangesByStartIndexDepr); - _sortSegmentAllSubrangesLinkedList(imlSegment); - - raLiveRangeInfo_t liveInfo; - liveInfo.liveRangesCount = 0; - //sint32 subrangeIndex = 0; - //for (auto& subrange : imlSegment->raInfo.list_subranges) - raLivenessSubrange_t* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; - while(subrangeItr) - { - sint32 currentIndex = subrangeItr->start.index; - // validate subrange - PPCRecRA_debugValidateSubrange(subrangeItr); - // expire ranges - for (sint32 f = 0; f < liveInfo.liveRangesCount; f++) - { - raLivenessSubrange_t* liverange = liveInfo.liveRangeList[f]; - if (liverange->end.index <= currentIndex && liverange->end.index != RA_INTER_RANGE_END) - { -#ifdef CEMU_DEBUG_ASSERT - if (liverange->subrangeBranchTaken || liverange->subrangeBranchNotTaken) - assert_dbg(); // infinite subranges should not expire -#endif - // remove entry - liveInfo.liveRangesCount--; - liveInfo.liveRangeList[f] = liveInfo.liveRangeList[liveInfo.liveRangesCount]; - f--; - } - } - // check if subrange already has register assigned - if (subrangeItr->range->physicalRegister >= 0) - { - // verify if register is actually available -#ifdef CEMU_DEBUG_ASSERT - for (sint32 f = 0; f < liveInfo.liveRangesCount; f++) - { - raLivenessSubrange_t* liverangeItr = liveInfo.liveRangeList[f]; - if (liverangeItr->range->physicalRegister == subrangeItr->range->physicalRegister) - { - // this should never happen because we try to preventively avoid register conflicts - assert_dbg(); - } - } -#endif - // add to live ranges - liveInfo.liveRangeList[liveInfo.liveRangesCount] = subrangeItr; - liveInfo.liveRangesCount++; - // next - subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; - continue; - } - // find free register - uint32 physRegisterMask = (1<<PPC_X64_GPR_USABLE_REGISTERS)-1; - for (sint32 f = 0; f < liveInfo.liveRangesCount; f++) - { - raLivenessSubrange_t* liverange = liveInfo.liveRangeList[f]; - if (liverange->range->physicalRegister < 0) - assert_dbg(); - physRegisterMask &= ~(1<<liverange->range->physicalRegister); - } - // check intersections with other ranges and determine allowed registers - uint32 allowedPhysRegisterMask = 0; - uint32 unusedRegisterMask = physRegisterMask; // mask of registers that are currently not used (does not include range checks) - if (physRegisterMask != 0) - { - allowedPhysRegisterMask = PPCRecRA_getAllowedRegisterMaskForFullRange(subrangeItr->range); - physRegisterMask &= allowedPhysRegisterMask; - } - if (physRegisterMask == 0) - { - struct - { - // estimated costs and chosen candidates for the different spill strategies - // hole cutting into a local range - struct - { - sint32 distance; - raLivenessSubrange_t* largestHoleSubrange; - sint32 cost; // additional cost of choosing this candidate - }localRangeHoleCutting; - // split current range (this is generally only a good choice when the current range is long but rarely used) - struct - { - sint32 cost; - sint32 physRegister; - sint32 distance; // size of hole - }availableRegisterHole; - // explode a inter-segment range (prefer ranges that are not read/written in this segment) - struct - { - raLivenessRange_t* range; - sint32 cost; - sint32 distance; // size of hole - // note: If we explode a range, we still have to check the size of the hole that becomes available, if too small then we need to add cost of splitting local subrange - }explodeRange; - // todo - add more strategies, make cost estimation smarter (for example, in some cases splitting can have reduced or no cost if read/store can be avoided due to data flow) - }spillStrategies; - // cant assign register - // there might be registers available, we just can't use them due to range conflicts - if (subrangeItr->end.index != RA_INTER_RANGE_END) - { - // range ends in current segment - - // Current algo looks like this: - // 1) Get the size of the largest possible hole that we can cut into any of the live local subranges - // 1.1) Check if the hole is large enough to hold the current subrange - // 2) If yes, cut hole and return false (full retry) - // 3) If no, try to reuse free register (need to determine how large the region is we can use) - // 4) If there is no free register or the range is extremely short go back to step 1+2 but additionally split the current subrange at where the hole ends - - cemu_assert_debug(currentIndex == subrangeItr->start.index); - - sint32 requiredSize = subrangeItr->end.index - subrangeItr->start.index; - // evaluate strategy: Cut hole into local subrange - spillStrategies.localRangeHoleCutting.distance = -1; - spillStrategies.localRangeHoleCutting.largestHoleSubrange = nullptr; - spillStrategies.localRangeHoleCutting.cost = INT_MAX; - if (currentIndex >= 0) - { - for (sint32 f = 0; f < liveInfo.liveRangesCount; f++) - { - raLivenessSubrange_t* candidate = liveInfo.liveRangeList[f]; - if (candidate->end.index == RA_INTER_RANGE_END) - continue; - sint32 distance = PPCRecRA_countInstructionsUntilNextUse(candidate, currentIndex); - if (distance < 2) - continue; // not even worth the consideration - // calculate split cost of candidate - sint32 cost = PPCRecRARange_estimateAdditionalCostAfterSplit(candidate, currentIndex + distance); - // calculate additional split cost of currentRange if hole is not large enough - if (distance < requiredSize) - { - cost += PPCRecRARange_estimateAdditionalCostAfterSplit(subrangeItr, currentIndex + distance); - // we also slightly increase cost in relation to the remaining length (in order to make the algorithm prefer larger holes) - cost += (requiredSize - distance) / 10; - } - // compare cost with previous candidates - if (cost < spillStrategies.localRangeHoleCutting.cost) - { - spillStrategies.localRangeHoleCutting.cost = cost; - spillStrategies.localRangeHoleCutting.distance = distance; - spillStrategies.localRangeHoleCutting.largestHoleSubrange = candidate; - } - } - } - // evaluate strategy: Split current range to fit in available holes - spillStrategies.availableRegisterHole.cost = INT_MAX; - spillStrategies.availableRegisterHole.distance = -1; - spillStrategies.availableRegisterHole.physRegister = -1; - if (currentIndex >= 0) - { - if (unusedRegisterMask != 0) - { - for (sint32 t = 0; t < PPC_X64_GPR_USABLE_REGISTERS; t++) - { - if ((unusedRegisterMask&(1 << t)) == 0) - continue; - // get size of potential hole for this register - sint32 distance = PPCRecRA_countInstructionsUntilNextLocalPhysRegisterUse(imlSegment, currentIndex, t); - if (distance < 2) - continue; // not worth consideration - // calculate additional cost due to split - if (distance >= requiredSize) - assert_dbg(); // should not happen or else we would have selected this register - sint32 cost = PPCRecRARange_estimateAdditionalCostAfterSplit(subrangeItr, currentIndex + distance); - // add small additional cost for the remaining range (prefer larger holes) - cost += (requiredSize - distance) / 10; - if (cost < spillStrategies.availableRegisterHole.cost) - { - spillStrategies.availableRegisterHole.cost = cost; - spillStrategies.availableRegisterHole.distance = distance; - spillStrategies.availableRegisterHole.physRegister = t; - } - } - } - } - // evaluate strategy: Explode inter-segment ranges - spillStrategies.explodeRange.cost = INT_MAX; - spillStrategies.explodeRange.range = nullptr; - spillStrategies.explodeRange.distance = -1; - for (sint32 f = 0; f < liveInfo.liveRangesCount; f++) - { - raLivenessSubrange_t* candidate = liveInfo.liveRangeList[f]; - if (candidate->end.index != RA_INTER_RANGE_END) - continue; - sint32 distance = PPCRecRA_countInstructionsUntilNextUse(liveInfo.liveRangeList[f], currentIndex); - if( distance < 2) - continue; - sint32 cost; - cost = PPCRecRARange_estimateAdditionalCostAfterRangeExplode(candidate->range); - // if the hole is not large enough, add cost of splitting current subrange - if (distance < requiredSize) - { - cost += PPCRecRARange_estimateAdditionalCostAfterSplit(subrangeItr, currentIndex + distance); - // add small additional cost for the remaining range (prefer larger holes) - cost += (requiredSize - distance) / 10; - } - // compare with current best candidate for this strategy - if (cost < spillStrategies.explodeRange.cost) - { - spillStrategies.explodeRange.cost = cost; - spillStrategies.explodeRange.distance = distance; - spillStrategies.explodeRange.range = candidate->range; - } - } - // choose strategy - if (spillStrategies.explodeRange.cost != INT_MAX && spillStrategies.explodeRange.cost <= spillStrategies.localRangeHoleCutting.cost && spillStrategies.explodeRange.cost <= spillStrategies.availableRegisterHole.cost) - { - // explode range - PPCRecRA_explodeRange(ppcImlGenContext, spillStrategies.explodeRange.range); - // split current subrange if necessary - if( requiredSize > spillStrategies.explodeRange.distance) - PPCRecRA_splitLocalSubrange(ppcImlGenContext, subrangeItr, currentIndex+spillStrategies.explodeRange.distance, true); - } - else if (spillStrategies.availableRegisterHole.cost != INT_MAX && spillStrategies.availableRegisterHole.cost <= spillStrategies.explodeRange.cost && spillStrategies.availableRegisterHole.cost <= spillStrategies.localRangeHoleCutting.cost) - { - // use available register - PPCRecRA_splitLocalSubrange(ppcImlGenContext, subrangeItr, currentIndex + spillStrategies.availableRegisterHole.distance, true); - } - else if (spillStrategies.localRangeHoleCutting.cost != INT_MAX && spillStrategies.localRangeHoleCutting.cost <= spillStrategies.explodeRange.cost && spillStrategies.localRangeHoleCutting.cost <= spillStrategies.availableRegisterHole.cost) - { - // cut hole - PPCRecRA_splitLocalSubrange(ppcImlGenContext, spillStrategies.localRangeHoleCutting.largestHoleSubrange, currentIndex + spillStrategies.localRangeHoleCutting.distance, true); - // split current subrange if necessary - if (requiredSize > spillStrategies.localRangeHoleCutting.distance) - PPCRecRA_splitLocalSubrange(ppcImlGenContext, subrangeItr, currentIndex + spillStrategies.localRangeHoleCutting.distance, true); - } - else if (subrangeItr->start.index == RA_INTER_RANGE_START) - { - // alternative strategy if we have no other choice: explode current range - PPCRecRA_explodeRange(ppcImlGenContext, subrangeItr->range); - } - else - assert_dbg(); - - return false; - } - else - { - // range exceeds segment border - // simple but bad solution -> explode the entire range (no longer allow it to cross segment boundaries) - // better solutions: 1) Depending on the situation, we can explode other ranges to resolve the conflict. Thus we should explode the range with the lowest extra cost - // 2) Or we explode the range only partially - // explode the range with the least cost - spillStrategies.explodeRange.cost = INT_MAX; - spillStrategies.explodeRange.range = nullptr; - spillStrategies.explodeRange.distance = -1; - for (sint32 f = 0; f < liveInfo.liveRangesCount; f++) - { - raLivenessSubrange_t* candidate = liveInfo.liveRangeList[f]; - if (candidate->end.index != RA_INTER_RANGE_END) - continue; - // only select candidates that clash with current subrange - if (candidate->range->physicalRegister < 0 && candidate != subrangeItr) - continue; - - sint32 cost; - cost = PPCRecRARange_estimateAdditionalCostAfterRangeExplode(candidate->range); - // compare with current best candidate for this strategy - if (cost < spillStrategies.explodeRange.cost) - { - spillStrategies.explodeRange.cost = cost; - spillStrategies.explodeRange.distance = INT_MAX; - spillStrategies.explodeRange.range = candidate->range; - } - } - // add current range as a candidate too - sint32 ownCost; - ownCost = PPCRecRARange_estimateAdditionalCostAfterRangeExplode(subrangeItr->range); - if (ownCost < spillStrategies.explodeRange.cost) - { - spillStrategies.explodeRange.cost = ownCost; - spillStrategies.explodeRange.distance = INT_MAX; - spillStrategies.explodeRange.range = subrangeItr->range; - } - if (spillStrategies.explodeRange.cost == INT_MAX) - assert_dbg(); // should not happen - PPCRecRA_explodeRange(ppcImlGenContext, spillStrategies.explodeRange.range); - } - return false; - } - // assign register to range - sint32 registerIndex = -1; - for (sint32 f = 0; f < PPC_X64_GPR_USABLE_REGISTERS; f++) - { - if ((physRegisterMask&(1 << f)) != 0) - { - registerIndex = f; - break; - } - } - subrangeItr->range->physicalRegister = registerIndex; - // add to live ranges - liveInfo.liveRangeList[liveInfo.liveRangesCount] = subrangeItr; - liveInfo.liveRangesCount++; - // next - subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; - } - return true; -} - -void PPCRecRA_assignRegisters(ppcImlGenContext_t* ppcImlGenContext) -{ - // start with frequently executed segments first - sint32 maxLoopDepth = 0; - for (sint32 i = 0; i < ppcImlGenContext->segmentListCount; i++) - { - maxLoopDepth = std::max(maxLoopDepth, ppcImlGenContext->segmentList[i]->loopDepth); - } - while (true) - { - bool done = false; - for (sint32 d = maxLoopDepth; d >= 0; d--) - { - for (sint32 i = 0; i < ppcImlGenContext->segmentListCount; i++) - { - PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[i]; - if (imlSegment->loopDepth != d) - continue; - done = PPCRecRA_assignSegmentRegisters(ppcImlGenContext, imlSegment); - if (done == false) - break; - } - if (done == false) - break; - } - if (done) - break; - } -} - -typedef struct -{ - raLivenessSubrange_t* subrangeList[SUBRANGE_LIST_SIZE]; - sint32 subrangeCount; - bool hasUndefinedEndings; -}subrangeEndingInfo_t; - -void _findSubrangeWriteEndings(raLivenessSubrange_t* subrange, uint32 iterationIndex, sint32 depth, subrangeEndingInfo_t* info) -{ - if (depth >= 30) - { - info->hasUndefinedEndings = true; - return; - } - if (subrange->lastIterationIndex == iterationIndex) - return; // already processed - subrange->lastIterationIndex = iterationIndex; - if (subrange->hasStoreDelayed) - return; // no need to traverse this subrange - PPCRecImlSegment_t* imlSegment = subrange->imlSegment; - if (subrange->end.index != RA_INTER_RANGE_END) - { - // ending segment - if (info->subrangeCount >= SUBRANGE_LIST_SIZE) - { - info->hasUndefinedEndings = true; - return; - } - else - { - info->subrangeList[info->subrangeCount] = subrange; - info->subrangeCount++; - } - return; - } - - // traverse next subranges in flow - if (imlSegment->nextSegmentBranchNotTaken) - { - if (subrange->subrangeBranchNotTaken == nullptr) - { - info->hasUndefinedEndings = true; - } - else - { - _findSubrangeWriteEndings(subrange->subrangeBranchNotTaken, iterationIndex, depth + 1, info); - } - } - if (imlSegment->nextSegmentBranchTaken) - { - if (subrange->subrangeBranchTaken == nullptr) - { - info->hasUndefinedEndings = true; - } - else - { - _findSubrangeWriteEndings(subrange->subrangeBranchTaken, iterationIndex, depth + 1, info); - } - } -} - -void _analyzeRangeDataFlow(raLivenessSubrange_t* subrange) -{ - if (subrange->end.index != RA_INTER_RANGE_END) - return; - // analyze data flow across segments (if this segment has writes) - if (subrange->hasStore) - { - subrangeEndingInfo_t writeEndingInfo; - writeEndingInfo.subrangeCount = 0; - writeEndingInfo.hasUndefinedEndings = false; - _findSubrangeWriteEndings(subrange, PPCRecRA_getNextIterationIndex(), 0, &writeEndingInfo); - if (writeEndingInfo.hasUndefinedEndings == false) - { - // get cost of delaying store into endings - sint32 delayStoreCost = 0; - bool alreadyStoredInAllEndings = true; - for (sint32 i = 0; i < writeEndingInfo.subrangeCount; i++) - { - raLivenessSubrange_t* subrangeItr = writeEndingInfo.subrangeList[i]; - if( subrangeItr->hasStore ) - continue; // this ending already stores, no extra cost - alreadyStoredInAllEndings = false; - sint32 storeCost = PPCRecRARange_getReadWriteCost(subrangeItr->imlSegment); - delayStoreCost = std::max(storeCost, delayStoreCost); - } - if (alreadyStoredInAllEndings) - { - subrange->hasStore = false; - subrange->hasStoreDelayed = true; - } - else if (delayStoreCost <= PPCRecRARange_getReadWriteCost(subrange->imlSegment)) - { - subrange->hasStore = false; - subrange->hasStoreDelayed = true; - for (sint32 i = 0; i < writeEndingInfo.subrangeCount; i++) - { - raLivenessSubrange_t* subrangeItr = writeEndingInfo.subrangeList[i]; - subrangeItr->hasStore = true; - } - } - } - } -} - -void PPCRecRA_generateSegmentInstructions(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment) -{ - sint16 virtualReg2PhysReg[PPC_REC_MAX_VIRTUAL_GPR]; - for (sint32 i = 0; i < PPC_REC_MAX_VIRTUAL_GPR; i++) - virtualReg2PhysReg[i] = -1; - - raLiveRangeInfo_t liveInfo; - liveInfo.liveRangesCount = 0; - sint32 index = 0; - sint32 suffixInstructionCount = (imlSegment->imlListCount > 0 && PPCRecompiler_isSuffixInstruction(imlSegment->imlList + imlSegment->imlListCount - 1)) ? 1 : 0; - // load register ranges that are supplied from previous segments - raLivenessSubrange_t* subrangeItr = imlSegment->raInfo.linkedList_allSubranges; - //for (auto& subrange : imlSegment->raInfo.list_subranges) - while(subrangeItr) - { - if (subrangeItr->start.index == RA_INTER_RANGE_START) - { - liveInfo.liveRangeList[liveInfo.liveRangesCount] = subrangeItr; - liveInfo.liveRangesCount++; -#ifdef CEMU_DEBUG_ASSERT - // load GPR - if (subrangeItr->_noLoad == false) - { - assert_dbg(); - } - // update translation table - if (virtualReg2PhysReg[subrangeItr->range->virtualRegister] != -1) - assert_dbg(); -#endif - virtualReg2PhysReg[subrangeItr->range->virtualRegister] = subrangeItr->range->physicalRegister; - } - // next - subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; - } - // process instructions - while(index < imlSegment->imlListCount+1) - { - // expire ranges - for (sint32 f = 0; f < liveInfo.liveRangesCount; f++) - { - raLivenessSubrange_t* liverange = liveInfo.liveRangeList[f]; - if (liverange->end.index <= index) - { - // update translation table - if (virtualReg2PhysReg[liverange->range->virtualRegister] == -1) - assert_dbg(); - virtualReg2PhysReg[liverange->range->virtualRegister] = -1; - // store GPR - if (liverange->hasStore) - { - PPCRecRA_insertGPRStoreInstruction(imlSegment, std::min(index, imlSegment->imlListCount - suffixInstructionCount), liverange->range->physicalRegister, liverange->range->name); - index++; - } - // remove entry - liveInfo.liveRangesCount--; - liveInfo.liveRangeList[f] = liveInfo.liveRangeList[liveInfo.liveRangesCount]; - f--; - } - } - // load new ranges - subrangeItr = imlSegment->raInfo.linkedList_allSubranges; - while(subrangeItr) - { - if (subrangeItr->start.index == index) - { - liveInfo.liveRangeList[liveInfo.liveRangesCount] = subrangeItr; - liveInfo.liveRangesCount++; - // load GPR - if (subrangeItr->_noLoad == false) - { - PPCRecRA_insertGPRLoadInstruction(imlSegment, std::min(index, imlSegment->imlListCount - suffixInstructionCount), subrangeItr->range->physicalRegister, subrangeItr->range->name); - index++; - subrangeItr->start.index--; - } - // update translation table - cemu_assert_debug(virtualReg2PhysReg[subrangeItr->range->virtualRegister] == -1); - virtualReg2PhysReg[subrangeItr->range->virtualRegister] = subrangeItr->range->physicalRegister; - } - subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; - } - // replace registers - if (index < imlSegment->imlListCount) - { - PPCImlOptimizerUsedRegisters_t gprTracking; - PPCRecompiler_checkRegisterUsage(NULL, imlSegment->imlList + index, &gprTracking); - - sint32 inputGpr[4]; - inputGpr[0] = gprTracking.gpr[0]; - inputGpr[1] = gprTracking.gpr[1]; - inputGpr[2] = gprTracking.gpr[2]; - inputGpr[3] = gprTracking.gpr[3]; - sint32 replaceGpr[4]; - for (sint32 f = 0; f < 4; f++) - { - sint32 virtualRegister = gprTracking.gpr[f]; - if (virtualRegister < 0) - { - replaceGpr[f] = -1; - continue; - } - if (virtualRegister >= PPC_REC_MAX_VIRTUAL_GPR) - assert_dbg(); - replaceGpr[f] = virtualReg2PhysReg[virtualRegister]; - cemu_assert_debug(replaceGpr[f] >= 0); - } - PPCRecompiler_replaceGPRRegisterUsageMultiple(ppcImlGenContext, imlSegment->imlList + index, inputGpr, replaceGpr); - } - // next iml instruction - index++; - } - // expire infinite subranges (subranges that cross the segment border) - sint32 storeLoadListLength = 0; - raLoadStoreInfo_t loadStoreList[PPC_REC_MAX_VIRTUAL_GPR]; - for (sint32 f = 0; f < liveInfo.liveRangesCount; f++) - { - raLivenessSubrange_t* liverange = liveInfo.liveRangeList[f]; - if (liverange->end.index == RA_INTER_RANGE_END) - { - // update translation table - cemu_assert_debug(virtualReg2PhysReg[liverange->range->virtualRegister] != -1); - virtualReg2PhysReg[liverange->range->virtualRegister] = -1; - // store GPR - if (liverange->hasStore) - { - loadStoreList[storeLoadListLength].registerIndex = liverange->range->physicalRegister; - loadStoreList[storeLoadListLength].registerName = liverange->range->name; - storeLoadListLength++; - } - // remove entry - liveInfo.liveRangesCount--; - liveInfo.liveRangeList[f] = liveInfo.liveRangeList[liveInfo.liveRangesCount]; - f--; - } - else - { - cemu_assert_suspicious(); - } - } - if (storeLoadListLength > 0) - { - PPCRecRA_insertGPRStoreInstructions(imlSegment, imlSegment->imlListCount - suffixInstructionCount, loadStoreList, storeLoadListLength); - } - // load subranges for next segments - subrangeItr = imlSegment->raInfo.linkedList_allSubranges; - storeLoadListLength = 0; - while(subrangeItr) - { - if (subrangeItr->start.index == RA_INTER_RANGE_END) - { - liveInfo.liveRangeList[liveInfo.liveRangesCount] = subrangeItr; - liveInfo.liveRangesCount++; - // load GPR - if (subrangeItr->_noLoad == false) - { - loadStoreList[storeLoadListLength].registerIndex = subrangeItr->range->physicalRegister; - loadStoreList[storeLoadListLength].registerName = subrangeItr->range->name; - storeLoadListLength++; - } - // update translation table - cemu_assert_debug(virtualReg2PhysReg[subrangeItr->range->virtualRegister] == -1); - virtualReg2PhysReg[subrangeItr->range->virtualRegister] = subrangeItr->range->physicalRegister; - } - // next - subrangeItr = subrangeItr->link_segmentSubrangesGPR.next; - } - if (storeLoadListLength > 0) - { - PPCRecRA_insertGPRLoadInstructions(imlSegment, imlSegment->imlListCount - suffixInstructionCount, loadStoreList, storeLoadListLength); - } -} - -void PPCRecRA_generateMoveInstructions(ppcImlGenContext_t* ppcImlGenContext) -{ - for (sint32 s = 0; s < ppcImlGenContext->segmentListCount; s++) - { - PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[s]; - PPCRecRA_generateSegmentInstructions(ppcImlGenContext, imlSegment); - } -} - -void PPCRecRA_calculateLivenessRangesV2(ppcImlGenContext_t* ppcImlGenContext); -void PPCRecRA_processFlowAndCalculateLivenessRangesV2(ppcImlGenContext_t* ppcImlGenContext); -void PPCRecRA_analyzeRangeDataFlowV2(ppcImlGenContext_t* ppcImlGenContext); - -void PPCRecompilerImm_prepareForRegisterAllocation(ppcImlGenContext_t* ppcImlGenContext) -{ - // insert empty segments after every non-taken branch if the linked segment has more than one input - // this gives the register allocator more room to create efficient spill code - sint32 segmentIndex = 0; - while (segmentIndex < ppcImlGenContext->segmentListCount) - { - PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[segmentIndex]; - if (imlSegment->nextSegmentIsUncertain) - { - segmentIndex++; - continue; - } - if (imlSegment->nextSegmentBranchTaken == nullptr || imlSegment->nextSegmentBranchNotTaken == nullptr) - { - segmentIndex++; - continue; - } - if (imlSegment->nextSegmentBranchNotTaken->list_prevSegments.size() <= 1) - { - segmentIndex++; - continue; - } - if (imlSegment->nextSegmentBranchNotTaken->isEnterable) - { - segmentIndex++; - continue; - } - PPCRecompilerIml_insertSegments(ppcImlGenContext, segmentIndex + 1, 1); - PPCRecImlSegment_t* imlSegmentP0 = ppcImlGenContext->segmentList[segmentIndex + 0]; - PPCRecImlSegment_t* imlSegmentP1 = ppcImlGenContext->segmentList[segmentIndex + 1]; - PPCRecImlSegment_t* nextSegment = imlSegment->nextSegmentBranchNotTaken; - PPCRecompilerIML_removeLink(imlSegmentP0, nextSegment); - PPCRecompilerIml_setLinkBranchNotTaken(imlSegmentP1, nextSegment); - PPCRecompilerIml_setLinkBranchNotTaken(imlSegmentP0, imlSegmentP1); - segmentIndex++; - } - // detect loops - for (sint32 s = 0; s < ppcImlGenContext->segmentListCount; s++) - { - PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[s]; - imlSegment->momentaryIndex = s; - } - for (sint32 s = 0; s < ppcImlGenContext->segmentListCount; s++) - { - PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[s]; - PPCRecRA_identifyLoop(ppcImlGenContext, imlSegment); - } -} - -void PPCRecompilerImm_allocateRegisters(ppcImlGenContext_t* ppcImlGenContext) -{ - PPCRecompilerImm_prepareForRegisterAllocation(ppcImlGenContext); - - ppcImlGenContext->raInfo.list_ranges = std::vector<raLivenessRange_t*>(); - - // calculate liveness - PPCRecRA_calculateLivenessRangesV2(ppcImlGenContext); - PPCRecRA_processFlowAndCalculateLivenessRangesV2(ppcImlGenContext); - - PPCRecRA_assignRegisters(ppcImlGenContext); - - PPCRecRA_analyzeRangeDataFlowV2(ppcImlGenContext); - PPCRecRA_generateMoveInstructions(ppcImlGenContext); - - PPCRecRA_deleteAllRanges(ppcImlGenContext); -} \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlRegisterAllocator2.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlRegisterAllocator2.cpp deleted file mode 100644 index abb47e92..00000000 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlRegisterAllocator2.cpp +++ /dev/null @@ -1,414 +0,0 @@ -#include "PPCRecompiler.h" -#include "PPCRecompilerIml.h" -#include "PPCRecompilerX64.h" -#include "PPCRecompilerImlRanges.h" -#include <queue> - -bool _isRangeDefined(PPCRecImlSegment_t* imlSegment, sint32 vGPR) -{ - return (imlSegment->raDistances.reg[vGPR].usageStart != INT_MAX); -} - -void PPCRecRA_calculateSegmentMinMaxRanges(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment) -{ - for (sint32 i = 0; i < PPC_REC_MAX_VIRTUAL_GPR; i++) - { - imlSegment->raDistances.reg[i].usageStart = INT_MAX; - imlSegment->raDistances.reg[i].usageEnd = INT_MIN; - } - // scan instructions for usage range - sint32 index = 0; - PPCImlOptimizerUsedRegisters_t gprTracking; - while (index < imlSegment->imlListCount) - { - // end loop at suffix instruction - if (PPCRecompiler_isSuffixInstruction(imlSegment->imlList + index)) - break; - // get accessed GPRs - PPCRecompiler_checkRegisterUsage(NULL, imlSegment->imlList + index, &gprTracking); - for (sint32 t = 0; t < 4; t++) - { - sint32 virtualRegister = gprTracking.gpr[t]; - if (virtualRegister < 0) - continue; - cemu_assert_debug(virtualRegister < PPC_REC_MAX_VIRTUAL_GPR); - imlSegment->raDistances.reg[virtualRegister].usageStart = std::min(imlSegment->raDistances.reg[virtualRegister].usageStart, index); // index before/at instruction - imlSegment->raDistances.reg[virtualRegister].usageEnd = std::max(imlSegment->raDistances.reg[virtualRegister].usageEnd, index+1); // index after instruction - } - // next instruction - index++; - } -} - -void PPCRecRA_calculateLivenessRangesV2(ppcImlGenContext_t* ppcImlGenContext) -{ - // for each register calculate min/max index of usage range within each segment - for (sint32 s = 0; s < ppcImlGenContext->segmentListCount; s++) - { - PPCRecRA_calculateSegmentMinMaxRanges(ppcImlGenContext, ppcImlGenContext->segmentList[s]); - } -} - -raLivenessSubrange_t* PPCRecRA_convertToMappedRanges(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment, sint32 vGPR, raLivenessRange_t* range) -{ - if (imlSegment->raDistances.isProcessed[vGPR]) - { - // return already existing segment - return imlSegment->raInfo.linkedList_perVirtualGPR[vGPR]; - } - imlSegment->raDistances.isProcessed[vGPR] = true; - if (_isRangeDefined(imlSegment, vGPR) == false) - return nullptr; - // create subrange - cemu_assert_debug(imlSegment->raInfo.linkedList_perVirtualGPR[vGPR] == nullptr); - raLivenessSubrange_t* subrange = PPCRecRA_createSubrange(ppcImlGenContext, range, imlSegment, imlSegment->raDistances.reg[vGPR].usageStart, imlSegment->raDistances.reg[vGPR].usageEnd); - // traverse forward - if (imlSegment->raDistances.reg[vGPR].usageEnd == RA_INTER_RANGE_END) - { - if (imlSegment->nextSegmentBranchTaken && imlSegment->nextSegmentBranchTaken->raDistances.reg[vGPR].usageStart == RA_INTER_RANGE_START) - { - subrange->subrangeBranchTaken = PPCRecRA_convertToMappedRanges(ppcImlGenContext, imlSegment->nextSegmentBranchTaken, vGPR, range); - cemu_assert_debug(subrange->subrangeBranchTaken->start.index == RA_INTER_RANGE_START); - } - if (imlSegment->nextSegmentBranchNotTaken && imlSegment->nextSegmentBranchNotTaken->raDistances.reg[vGPR].usageStart == RA_INTER_RANGE_START) - { - subrange->subrangeBranchNotTaken = PPCRecRA_convertToMappedRanges(ppcImlGenContext, imlSegment->nextSegmentBranchNotTaken, vGPR, range); - cemu_assert_debug(subrange->subrangeBranchNotTaken->start.index == RA_INTER_RANGE_START); - } - } - // traverse backward - if (imlSegment->raDistances.reg[vGPR].usageStart == RA_INTER_RANGE_START) - { - for (auto& it : imlSegment->list_prevSegments) - { - if (it->raDistances.reg[vGPR].usageEnd == RA_INTER_RANGE_END) - PPCRecRA_convertToMappedRanges(ppcImlGenContext, it, vGPR, range); - } - } - // return subrange - return subrange; -} - -void PPCRecRA_createSegmentLivenessRanges(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment) -{ - for (sint32 i = 0; i < PPC_REC_MAX_VIRTUAL_GPR; i++) - { - if( _isRangeDefined(imlSegment, i) == false ) - continue; - if( imlSegment->raDistances.isProcessed[i]) - continue; - raLivenessRange_t* range = PPCRecRA_createRangeBase(ppcImlGenContext, i, ppcImlGenContext->mappedRegister[i]); - PPCRecRA_convertToMappedRanges(ppcImlGenContext, imlSegment, i, range); - } - // create lookup table of ranges - raLivenessSubrange_t* vGPR2Subrange[PPC_REC_MAX_VIRTUAL_GPR]; - for (sint32 i = 0; i < PPC_REC_MAX_VIRTUAL_GPR; i++) - { - vGPR2Subrange[i] = imlSegment->raInfo.linkedList_perVirtualGPR[i]; -#ifdef CEMU_DEBUG_ASSERT - if (vGPR2Subrange[i] && vGPR2Subrange[i]->link_sameVirtualRegisterGPR.next != nullptr) - assert_dbg(); -#endif - } - // parse instructions and convert to locations - sint32 index = 0; - PPCImlOptimizerUsedRegisters_t gprTracking; - while (index < imlSegment->imlListCount) - { - // end loop at suffix instruction - if (PPCRecompiler_isSuffixInstruction(imlSegment->imlList + index)) - break; - // get accessed GPRs - PPCRecompiler_checkRegisterUsage(NULL, imlSegment->imlList + index, &gprTracking); - // handle accessed GPR - for (sint32 t = 0; t < 4; t++) - { - sint32 virtualRegister = gprTracking.gpr[t]; - if (virtualRegister < 0) - continue; - bool isWrite = (t == 3); - // add location - PPCRecRA_updateOrAddSubrangeLocation(vGPR2Subrange[virtualRegister], index, isWrite == false, isWrite); -#ifdef CEMU_DEBUG_ASSERT - if (index < vGPR2Subrange[virtualRegister]->start.index) - assert_dbg(); - if (index+1 > vGPR2Subrange[virtualRegister]->end.index) - assert_dbg(); -#endif - } - // next instruction - index++; - } -} - -void PPCRecRA_extendRangeToEndOfSegment(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment, sint32 vGPR) -{ - if (_isRangeDefined(imlSegment, vGPR) == false) - { - imlSegment->raDistances.reg[vGPR].usageStart = RA_INTER_RANGE_END; - imlSegment->raDistances.reg[vGPR].usageEnd = RA_INTER_RANGE_END; - return; - } - imlSegment->raDistances.reg[vGPR].usageEnd = RA_INTER_RANGE_END; -} - -void PPCRecRA_extendRangeToBeginningOfSegment(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment, sint32 vGPR) -{ - if (_isRangeDefined(imlSegment, vGPR) == false) - { - imlSegment->raDistances.reg[vGPR].usageStart = RA_INTER_RANGE_START; - imlSegment->raDistances.reg[vGPR].usageEnd = RA_INTER_RANGE_START; - } - else - { - imlSegment->raDistances.reg[vGPR].usageStart = RA_INTER_RANGE_START; - } - // propagate backwards - for (auto& it : imlSegment->list_prevSegments) - { - PPCRecRA_extendRangeToEndOfSegment(ppcImlGenContext, it, vGPR); - } -} - -void _PPCRecRA_connectRanges(ppcImlGenContext_t* ppcImlGenContext, sint32 vGPR, PPCRecImlSegment_t** route, sint32 routeDepth) -{ -#ifdef CEMU_DEBUG_ASSERT - if (routeDepth < 2) - assert_dbg(); -#endif - // extend starting range to end of segment - PPCRecRA_extendRangeToEndOfSegment(ppcImlGenContext, route[0], vGPR); - // extend all the connecting segments in both directions - for (sint32 i = 1; i < (routeDepth - 1); i++) - { - PPCRecRA_extendRangeToEndOfSegment(ppcImlGenContext, route[i], vGPR); - PPCRecRA_extendRangeToBeginningOfSegment(ppcImlGenContext, route[i], vGPR); - } - // extend the final segment towards the beginning - PPCRecRA_extendRangeToBeginningOfSegment(ppcImlGenContext, route[routeDepth-1], vGPR); -} - -void _PPCRecRA_checkAndTryExtendRange(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* currentSegment, sint32 vGPR, sint32 distanceLeft, PPCRecImlSegment_t** route, sint32 routeDepth) -{ - if (routeDepth >= 64) - { - cemuLog_logDebug(LogType::Force, "Recompiler RA route maximum depth exceeded for function 0x{:08x}", ppcImlGenContext->functionRef->ppcAddress); - return; - } - route[routeDepth] = currentSegment; - if (currentSegment->raDistances.reg[vGPR].usageStart == INT_MAX) - { - // measure distance to end of segment - distanceLeft -= currentSegment->imlListCount; - if (distanceLeft > 0) - { - if (currentSegment->nextSegmentBranchNotTaken) - _PPCRecRA_checkAndTryExtendRange(ppcImlGenContext, currentSegment->nextSegmentBranchNotTaken, vGPR, distanceLeft, route, routeDepth + 1); - if (currentSegment->nextSegmentBranchTaken) - _PPCRecRA_checkAndTryExtendRange(ppcImlGenContext, currentSegment->nextSegmentBranchTaken, vGPR, distanceLeft, route, routeDepth + 1); - } - return; - } - else - { - // measure distance to range - if (currentSegment->raDistances.reg[vGPR].usageStart == RA_INTER_RANGE_END) - { - if (distanceLeft < currentSegment->imlListCount) - return; // range too far away - } - else if (currentSegment->raDistances.reg[vGPR].usageStart != RA_INTER_RANGE_START && currentSegment->raDistances.reg[vGPR].usageStart > distanceLeft) - return; // out of range - // found close range -> connect ranges - _PPCRecRA_connectRanges(ppcImlGenContext, vGPR, route, routeDepth + 1); - } -} - -void PPCRecRA_checkAndTryExtendRange(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* currentSegment, sint32 vGPR) -{ -#ifdef CEMU_DEBUG_ASSERT - if (currentSegment->raDistances.reg[vGPR].usageEnd < 0) - assert_dbg(); -#endif - // count instructions to end of initial segment - if (currentSegment->raDistances.reg[vGPR].usageEnd == RA_INTER_RANGE_START) - assert_dbg(); - sint32 instructionsUntilEndOfSeg; - if (currentSegment->raDistances.reg[vGPR].usageEnd == RA_INTER_RANGE_END) - instructionsUntilEndOfSeg = 0; - else - instructionsUntilEndOfSeg = currentSegment->imlListCount - currentSegment->raDistances.reg[vGPR].usageEnd; - -#ifdef CEMU_DEBUG_ASSERT - if (instructionsUntilEndOfSeg < 0) - assert_dbg(); -#endif - sint32 remainingScanDist = 45 - instructionsUntilEndOfSeg; - if (remainingScanDist <= 0) - return; // can't reach end - - // also dont forget: Extending is easier if we allow 'non symetric' branches. E.g. register range one enters one branch - PPCRecImlSegment_t* route[64]; - route[0] = currentSegment; - if (currentSegment->nextSegmentBranchNotTaken) - { - _PPCRecRA_checkAndTryExtendRange(ppcImlGenContext, currentSegment->nextSegmentBranchNotTaken, vGPR, remainingScanDist, route, 1); - } - if (currentSegment->nextSegmentBranchTaken) - { - _PPCRecRA_checkAndTryExtendRange(ppcImlGenContext, currentSegment->nextSegmentBranchTaken, vGPR, remainingScanDist, route, 1); - } -} - -void PPCRecRA_mergeCloseRangesForSegmentV2(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment) -{ - for (sint32 i = 0; i < PPC_REC_MAX_VIRTUAL_GPR; i++) // todo: Use dynamic maximum or list of used vGPRs so we can avoid parsing empty entries - { - if(imlSegment->raDistances.reg[i].usageStart == INT_MAX) - continue; // not used - // check and extend if possible - PPCRecRA_checkAndTryExtendRange(ppcImlGenContext, imlSegment, i); - } -#ifdef CEMU_DEBUG_ASSERT - if (imlSegment->list_prevSegments.empty() == false && imlSegment->isEnterable) - assert_dbg(); - if ((imlSegment->nextSegmentBranchNotTaken != nullptr || imlSegment->nextSegmentBranchTaken != nullptr) && imlSegment->nextSegmentIsUncertain) - assert_dbg(); -#endif -} - -void PPCRecRA_followFlowAndExtendRanges(ppcImlGenContext_t* ppcImlGenContext, PPCRecImlSegment_t* imlSegment) -{ - std::vector<PPCRecImlSegment_t*> list_segments; - list_segments.reserve(1000); - sint32 index = 0; - imlSegment->raRangeExtendProcessed = true; - list_segments.push_back(imlSegment); - while (index < list_segments.size()) - { - PPCRecImlSegment_t* currentSegment = list_segments[index]; - PPCRecRA_mergeCloseRangesForSegmentV2(ppcImlGenContext, currentSegment); - // follow flow - if (currentSegment->nextSegmentBranchNotTaken && currentSegment->nextSegmentBranchNotTaken->raRangeExtendProcessed == false) - { - currentSegment->nextSegmentBranchNotTaken->raRangeExtendProcessed = true; - list_segments.push_back(currentSegment->nextSegmentBranchNotTaken); - } - if (currentSegment->nextSegmentBranchTaken && currentSegment->nextSegmentBranchTaken->raRangeExtendProcessed == false) - { - currentSegment->nextSegmentBranchTaken->raRangeExtendProcessed = true; - list_segments.push_back(currentSegment->nextSegmentBranchTaken); - } - index++; - } -} - -void PPCRecRA_mergeCloseRangesV2(ppcImlGenContext_t* ppcImlGenContext) -{ - for (sint32 s = 0; s < ppcImlGenContext->segmentListCount; s++) - { - PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[s]; - if (imlSegment->list_prevSegments.empty()) - { - if (imlSegment->raRangeExtendProcessed) - assert_dbg(); // should not happen - PPCRecRA_followFlowAndExtendRanges(ppcImlGenContext, imlSegment); - } - } -} - -void PPCRecRA_extendRangesOutOfLoopsV2(ppcImlGenContext_t* ppcImlGenContext) -{ - for (sint32 s = 0; s < ppcImlGenContext->segmentListCount; s++) - { - PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[s]; - auto localLoopDepth = imlSegment->loopDepth; - if( localLoopDepth <= 0 ) - continue; // not inside a loop - // look for loop exit - bool hasLoopExit = false; - if (imlSegment->nextSegmentBranchTaken && imlSegment->nextSegmentBranchTaken->loopDepth < localLoopDepth) - { - hasLoopExit = true; - } - if (imlSegment->nextSegmentBranchNotTaken && imlSegment->nextSegmentBranchNotTaken->loopDepth < localLoopDepth) - { - hasLoopExit = true; - } - if(hasLoopExit == false) - continue; - - // extend looping ranges into all exits (this allows the data flow analyzer to move stores out of the loop) - for (sint32 i = 0; i < PPC_REC_MAX_VIRTUAL_GPR; i++) // todo: Use dynamic maximum or list of used vGPRs so we can avoid parsing empty entries - { - if (imlSegment->raDistances.reg[i].usageEnd != RA_INTER_RANGE_END) - continue; // range not set or does not reach end of segment - if(imlSegment->nextSegmentBranchTaken) - PPCRecRA_extendRangeToBeginningOfSegment(ppcImlGenContext, imlSegment->nextSegmentBranchTaken, i); - if(imlSegment->nextSegmentBranchNotTaken) - PPCRecRA_extendRangeToBeginningOfSegment(ppcImlGenContext, imlSegment->nextSegmentBranchNotTaken, i); - } - } -} - -void PPCRecRA_processFlowAndCalculateLivenessRangesV2(ppcImlGenContext_t* ppcImlGenContext) -{ - // merge close ranges - PPCRecRA_mergeCloseRangesV2(ppcImlGenContext); - // extra pass to move register stores out of loops - PPCRecRA_extendRangesOutOfLoopsV2(ppcImlGenContext); - // calculate liveness ranges - for (sint32 s = 0; s < ppcImlGenContext->segmentListCount; s++) - { - PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[s]; - PPCRecRA_createSegmentLivenessRanges(ppcImlGenContext, imlSegment); - } -} - -void PPCRecRA_analyzeSubrangeDataDependencyV2(raLivenessSubrange_t* subrange) -{ - bool isRead = false; - bool isWritten = false; - bool isOverwritten = false; - for (auto& location : subrange->list_locations) - { - if (location.isRead) - { - isRead = true; - } - if (location.isWrite) - { - if (isRead == false) - isOverwritten = true; - isWritten = true; - } - } - subrange->_noLoad = isOverwritten; - subrange->hasStore = isWritten; - - if (subrange->start.index == RA_INTER_RANGE_START) - subrange->_noLoad = true; -} - -void _analyzeRangeDataFlow(raLivenessSubrange_t* subrange); - -void PPCRecRA_analyzeRangeDataFlowV2(ppcImlGenContext_t* ppcImlGenContext) -{ - // this function is called after _assignRegisters(), which means that all ranges are already final and wont change anymore - // first do a per-subrange pass - for (auto& range : ppcImlGenContext->raInfo.list_ranges) - { - for (auto& subrange : range->list_subranges) - { - PPCRecRA_analyzeSubrangeDataDependencyV2(subrange); - } - } - // then do a second pass where we scan along subrange flow - for (auto& range : ppcImlGenContext->raInfo.list_ranges) - { - for (auto& subrange : range->list_subranges) // todo - traversing this backwards should be faster and yield better results due to the nature of the algorithm - { - _analyzeRangeDataFlow(subrange); - } - } -} diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerIntermediate.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerIntermediate.cpp index fcbe64be..468af5b2 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerIntermediate.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerIntermediate.cpp @@ -1,173 +1,26 @@ #include "PPCRecompiler.h" #include "PPCRecompilerIml.h" -PPCRecImlSegment_t* PPCRecompiler_getSegmentByPPCJumpAddress(ppcImlGenContext_t* ppcImlGenContext, uint32 ppcOffset) -{ - for(sint32 s=0; s<ppcImlGenContext->segmentListCount; s++) - { - if( ppcImlGenContext->segmentList[s]->isJumpDestination && ppcImlGenContext->segmentList[s]->jumpDestinationPPCAddress == ppcOffset ) - { - return ppcImlGenContext->segmentList[s]; - } - } - debug_printf("PPCRecompiler_getSegmentByPPCJumpAddress(): Unable to find segment (ppcOffset 0x%08x)\n", ppcOffset); - return NULL; -} - -void PPCRecompilerIml_setLinkBranchNotTaken(PPCRecImlSegment_t* imlSegmentSrc, PPCRecImlSegment_t* imlSegmentDst) -{ - // make sure segments aren't already linked - if (imlSegmentSrc->nextSegmentBranchNotTaken == imlSegmentDst) - return; - // add as next segment for source - if (imlSegmentSrc->nextSegmentBranchNotTaken != NULL) - assert_dbg(); - imlSegmentSrc->nextSegmentBranchNotTaken = imlSegmentDst; - // add as previous segment for destination - imlSegmentDst->list_prevSegments.push_back(imlSegmentSrc); -} - -void PPCRecompilerIml_setLinkBranchTaken(PPCRecImlSegment_t* imlSegmentSrc, PPCRecImlSegment_t* imlSegmentDst) -{ - // make sure segments aren't already linked - if (imlSegmentSrc->nextSegmentBranchTaken == imlSegmentDst) - return; - // add as next segment for source - if (imlSegmentSrc->nextSegmentBranchTaken != NULL) - assert_dbg(); - imlSegmentSrc->nextSegmentBranchTaken = imlSegmentDst; - // add as previous segment for destination - imlSegmentDst->list_prevSegments.push_back(imlSegmentSrc); -} - -void PPCRecompilerIML_removeLink(PPCRecImlSegment_t* imlSegmentSrc, PPCRecImlSegment_t* imlSegmentDst) -{ - if (imlSegmentSrc->nextSegmentBranchNotTaken == imlSegmentDst) - { - imlSegmentSrc->nextSegmentBranchNotTaken = NULL; - } - else if (imlSegmentSrc->nextSegmentBranchTaken == imlSegmentDst) - { - imlSegmentSrc->nextSegmentBranchTaken = NULL; - } - else - assert_dbg(); - - bool matchFound = false; - for (sint32 i = 0; i < imlSegmentDst->list_prevSegments.size(); i++) - { - if (imlSegmentDst->list_prevSegments[i] == imlSegmentSrc) - { - imlSegmentDst->list_prevSegments.erase(imlSegmentDst->list_prevSegments.begin()+i); - matchFound = true; - break; - } - } - if (matchFound == false) - assert_dbg(); -} - -/* - * Replaces all links to segment orig with linkts to segment new - */ -void PPCRecompilerIML_relinkInputSegment(PPCRecImlSegment_t* imlSegmentOrig, PPCRecImlSegment_t* imlSegmentNew) -{ - while (imlSegmentOrig->list_prevSegments.size() != 0) - { - PPCRecImlSegment_t* prevSegment = imlSegmentOrig->list_prevSegments[0]; - if (prevSegment->nextSegmentBranchNotTaken == imlSegmentOrig) - { - PPCRecompilerIML_removeLink(prevSegment, imlSegmentOrig); - PPCRecompilerIml_setLinkBranchNotTaken(prevSegment, imlSegmentNew); - } - else if (prevSegment->nextSegmentBranchTaken == imlSegmentOrig) - { - PPCRecompilerIML_removeLink(prevSegment, imlSegmentOrig); - PPCRecompilerIml_setLinkBranchTaken(prevSegment, imlSegmentNew); - } - else - { - assert_dbg(); - } - } -} - -void PPCRecompilerIML_linkSegments(ppcImlGenContext_t* ppcImlGenContext) -{ - for(sint32 s=0; s<ppcImlGenContext->segmentListCount; s++) - { - PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[s]; - - bool isLastSegment = (s+1)>=ppcImlGenContext->segmentListCount; - PPCRecImlSegment_t* nextSegment = isLastSegment?NULL:ppcImlGenContext->segmentList[s+1]; - // handle empty segment - if( imlSegment->imlListCount == 0 ) - { - if (isLastSegment == false) - PPCRecompilerIml_setLinkBranchNotTaken(imlSegment, ppcImlGenContext->segmentList[s+1]); // continue execution to next segment - else - imlSegment->nextSegmentIsUncertain = true; - continue; - } - // check last instruction of segment - PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList+(imlSegment->imlListCount-1); - if( imlInstruction->type == PPCREC_IML_TYPE_CJUMP || imlInstruction->type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK ) - { - // find destination segment by ppc jump address - PPCRecImlSegment_t* jumpDestSegment = PPCRecompiler_getSegmentByPPCJumpAddress(ppcImlGenContext, imlInstruction->op_conditionalJump.jumpmarkAddress); - if( jumpDestSegment ) - { - if (imlInstruction->op_conditionalJump.condition != PPCREC_JUMP_CONDITION_NONE) - PPCRecompilerIml_setLinkBranchNotTaken(imlSegment, nextSegment); - PPCRecompilerIml_setLinkBranchTaken(imlSegment, jumpDestSegment); - } - else - { - imlSegment->nextSegmentIsUncertain = true; - } - } - else if( imlInstruction->type == PPCREC_IML_TYPE_MACRO ) - { - // currently we assume that the next segment is unknown for all macros - imlSegment->nextSegmentIsUncertain = true; - } - else - { - // all other instruction types do not branch - //imlSegment->nextSegment[0] = nextSegment; - PPCRecompilerIml_setLinkBranchNotTaken(imlSegment, nextSegment); - //imlSegment->nextSegmentIsUncertain = true; - } - } -} - void PPCRecompilerIML_isolateEnterableSegments(ppcImlGenContext_t* ppcImlGenContext) { - sint32 initialSegmentCount = ppcImlGenContext->segmentListCount; - for (sint32 i = 0; i < ppcImlGenContext->segmentListCount; i++) + size_t initialSegmentCount = ppcImlGenContext->segmentList2.size(); + for (size_t i = 0; i < initialSegmentCount; i++) { - PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[i]; + IMLSegment* imlSegment = ppcImlGenContext->segmentList2[i]; if (imlSegment->list_prevSegments.empty() == false && imlSegment->isEnterable) { // spawn new segment at end - PPCRecompilerIml_insertSegments(ppcImlGenContext, ppcImlGenContext->segmentListCount, 1); - PPCRecImlSegment_t* entrySegment = ppcImlGenContext->segmentList[ppcImlGenContext->segmentListCount-1]; + PPCRecompilerIml_insertSegments(ppcImlGenContext, ppcImlGenContext->segmentList2.size(), 1); + IMLSegment* entrySegment = ppcImlGenContext->segmentList2[ppcImlGenContext->segmentList2.size()-1]; entrySegment->isEnterable = true; entrySegment->enterPPCAddress = imlSegment->enterPPCAddress; // create jump instruction PPCRecompiler_pushBackIMLInstructions(entrySegment, 0, 1); - PPCRecompilerImlGen_generateNewInstruction_jumpSegment(ppcImlGenContext, entrySegment->imlList + 0); - PPCRecompilerIml_setLinkBranchTaken(entrySegment, imlSegment); + entrySegment->imlList.data()[0].make_jump(); + IMLSegment_SetLinkBranchTaken(entrySegment, imlSegment); // remove enterable flag from original segment imlSegment->isEnterable = false; imlSegment->enterPPCAddress = 0; } } -} - -PPCRecImlInstruction_t* PPCRecompilerIML_getLastInstruction(PPCRecImlSegment_t* imlSegment) -{ - if (imlSegment->imlListCount == 0) - return nullptr; - return imlSegment->imlList + (imlSegment->imlListCount - 1); -} +} \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64.cpp deleted file mode 100644 index 97b2c14c..00000000 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerX64.cpp +++ /dev/null @@ -1,2687 +0,0 @@ -#include "Cafe/HW/Espresso/PPCState.h" -#include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h" -#include "Cafe/HW/Espresso/Interpreter/PPCInterpreterHelper.h" -#include "PPCRecompiler.h" -#include "PPCRecompilerIml.h" -#include "PPCRecompilerX64.h" -#include "Cafe/OS/libs/coreinit/coreinit_Time.h" -#include "util/MemMapper/MemMapper.h" -#include "Common/cpu_features.h" - -sint32 x64Gen_registerMap[12] = // virtual GPR to x64 register mapping -{ - REG_RAX, REG_RDX, REG_RBX, REG_RBP, REG_RSI, REG_RDI, REG_R8, REG_R9, REG_R10, REG_R11, REG_R12, REG_RCX -}; - -/* -* Remember current instruction output offset for reloc -* The instruction generated after this method has been called will be adjusted -*/ -void PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext_t* x64GenContext, uint8 type, void* extraInfo = nullptr) -{ - if( x64GenContext->relocateOffsetTableCount >= x64GenContext->relocateOffsetTableSize ) - { - x64GenContext->relocateOffsetTableSize = std::max(4, x64GenContext->relocateOffsetTableSize*2); - x64GenContext->relocateOffsetTable = (x64RelocEntry_t*)realloc(x64GenContext->relocateOffsetTable, sizeof(x64RelocEntry_t)*x64GenContext->relocateOffsetTableSize); - } - x64GenContext->relocateOffsetTable[x64GenContext->relocateOffsetTableCount].offset = x64GenContext->codeBufferIndex; - x64GenContext->relocateOffsetTable[x64GenContext->relocateOffsetTableCount].type = type; - x64GenContext->relocateOffsetTable[x64GenContext->relocateOffsetTableCount].extraInfo = extraInfo; - x64GenContext->relocateOffsetTableCount++; -} - -/* -* Overwrites the currently cached (in x64 cf) cr* register -* Should be called before each x64 instruction which overwrites the current status flags (with mappedCRRegister set to PPCREC_CR_TEMPORARY unless explicitly set by PPC instruction) -*/ -void PPCRecompilerX64Gen_crConditionFlags_set(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, sint32 mappedCRRegister, sint32 crState) -{ - x64GenContext->activeCRRegister = mappedCRRegister; - x64GenContext->activeCRState = crState; -} - -/* -* Reset cached cr* register without storing it first -*/ -void PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext) -{ - x64GenContext->activeCRRegister = PPC_REC_INVALID_REGISTER; -} - -void PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext_t* x64GenContext, sint32 jumpInstructionOffset, sint32 destinationOffset) -{ - uint8* instructionData = x64GenContext->codeBuffer + jumpInstructionOffset; - if (instructionData[0] == 0x0F && (instructionData[1] >= 0x80 && instructionData[1] <= 0x8F)) - { - // far conditional jump - *(uint32*)(instructionData + 2) = (destinationOffset - (jumpInstructionOffset + 6)); - } - else if (instructionData[0] >= 0x70 && instructionData[0] <= 0x7F) - { - // short conditional jump - sint32 distance = (sint32)((destinationOffset - (jumpInstructionOffset + 2))); - cemu_assert_debug(distance >= -128 && distance <= 127); - *(uint8*)(instructionData + 1) = (uint8)distance; - } - else if (instructionData[0] == 0xE9) - { - *(uint32*)(instructionData + 1) = (destinationOffset - (jumpInstructionOffset + 5)); - } - else if (instructionData[0] == 0xEB) - { - sint32 distance = (sint32)((destinationOffset - (jumpInstructionOffset + 2))); - cemu_assert_debug(distance >= -128 && distance <= 127); - *(uint8*)(instructionData + 1) = (uint8)distance; - } - else - { - assert_dbg(); - } -} - -void PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) -{ - sint32 crRegister = imlInstruction->crRegister; - if( (imlInstruction->crIgnoreMask&(1<<(crRegister*4+PPCREC_CR_BIT_LT))) == 0 ) - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_SIGN, REG_RSP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_LT)); // check for sign instead of _BELOW (CF) which is not set by TEST - if( (imlInstruction->crIgnoreMask&(1<<(crRegister*4+PPCREC_CR_BIT_GT))) == 0 ) - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_SIGNED_GREATER, REG_RSP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_GT)); - if( (imlInstruction->crIgnoreMask&(1<<(crRegister*4+PPCREC_CR_BIT_EQ))) == 0 ) - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_EQUAL, REG_RSP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_EQ)); - // todo: Set CR SO if XER SO bit is set - PPCRecompilerX64Gen_crConditionFlags_set(PPCRecFunction, ppcImlGenContext, x64GenContext, crRegister, PPCREC_CR_STATE_TYPE_LOGICAL); -} - -void* ATTR_MS_ABI PPCRecompiler_virtualHLE(PPCInterpreter_t* hCPU, uint32 hleFuncId) -{ - void* prevRSPTemp = hCPU->rspTemp; - if( hleFuncId == 0xFFD0 ) - { - hCPU->remainingCycles -= 500; // let subtract about 500 cycles for each HLE call - hCPU->gpr[3] = 0; - PPCInterpreter_nextInstruction(hCPU); - return hCPU; - } - else - { - auto hleCall = PPCInterpreter_getHLECall(hleFuncId); - cemu_assert(hleCall != nullptr); - hleCall(hCPU); - } - hCPU->rspTemp = prevRSPTemp; - return PPCInterpreter_getCurrentInstance(); -} - -void ATTR_MS_ABI PPCRecompiler_getTBL(PPCInterpreter_t* hCPU, uint32 gprIndex) -{ - uint64 coreTime = coreinit::OSGetSystemTime(); - hCPU->gpr[gprIndex] = (uint32)(coreTime&0xFFFFFFFF); -} - -void ATTR_MS_ABI PPCRecompiler_getTBU(PPCInterpreter_t* hCPU, uint32 gprIndex) -{ - uint64 coreTime = coreinit::OSGetSystemTime(); - hCPU->gpr[gprIndex] = (uint32)((coreTime>>32)&0xFFFFFFFF); -} - -bool PPCRecompilerX64Gen_imlInstruction_macro(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) -{ - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - if( imlInstruction->operation == PPCREC_IML_MACRO_BLR || imlInstruction->operation == PPCREC_IML_MACRO_BLRL ) - { - uint32 currentInstructionAddress = imlInstruction->op_macro.param; - // MOV EDX, [SPR_LR] - x64Emit_mov_reg64_mem32(x64GenContext, REG_RDX, REG_RSP, offsetof(PPCInterpreter_t, spr.LR)); - // if BLRL, then update SPR LR - if (imlInstruction->operation == PPCREC_IML_MACRO_BLRL) - x64Gen_mov_mem32Reg64_imm32(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, spr.LR), currentInstructionAddress + 4); - // JMP [offset+RDX*(8/4)+R15] - x64Gen_writeU8(x64GenContext, 0x41); - x64Gen_writeU8(x64GenContext, 0xFF); - x64Gen_writeU8(x64GenContext, 0xA4); - x64Gen_writeU8(x64GenContext, 0x57); - x64Gen_writeU32(x64GenContext, (uint32)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); - return true; - } - else if( imlInstruction->operation == PPCREC_IML_MACRO_BCTR || imlInstruction->operation == PPCREC_IML_MACRO_BCTRL ) - { - uint32 currentInstructionAddress = imlInstruction->op_macro.param; - // MOV EDX, [SPR_CTR] - x64Emit_mov_reg64_mem32(x64GenContext, REG_RDX, REG_RSP, offsetof(PPCInterpreter_t, spr.CTR)); - // if BCTRL, then update SPR LR - if (imlInstruction->operation == PPCREC_IML_MACRO_BCTRL) - x64Gen_mov_mem32Reg64_imm32(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, spr.LR), currentInstructionAddress + 4); - // JMP [offset+RDX*(8/4)+R15] - x64Gen_writeU8(x64GenContext, 0x41); - x64Gen_writeU8(x64GenContext, 0xFF); - x64Gen_writeU8(x64GenContext, 0xA4); - x64Gen_writeU8(x64GenContext, 0x57); - x64Gen_writeU32(x64GenContext, (uint32)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); - return true; - } - else if( imlInstruction->operation == PPCREC_IML_MACRO_BL ) - { - // MOV DWORD [SPR_LinkRegister], newLR - uint32 newLR = imlInstruction->op_macro.param + 4; - x64Gen_mov_mem32Reg64_imm32(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, spr.LR), newLR); - // remember new instruction pointer in RDX - uint32 newIP = imlInstruction->op_macro.param2; - x64Gen_mov_reg64Low32_imm32(x64GenContext, REG_RDX, newIP); - // since RDX is constant we can use JMP [R15+const_offset] if jumpTableOffset+RDX*2 does not exceed the 2GB boundary - uint64 lookupOffset = (uint64)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable) + (uint64)newIP * 2ULL; - if (lookupOffset >= 0x80000000ULL) - { - // JMP [offset+RDX*(8/4)+R15] - x64Gen_writeU8(x64GenContext, 0x41); - x64Gen_writeU8(x64GenContext, 0xFF); - x64Gen_writeU8(x64GenContext, 0xA4); - x64Gen_writeU8(x64GenContext, 0x57); - x64Gen_writeU32(x64GenContext, (uint32)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); - } - else - { - x64Gen_writeU8(x64GenContext, 0x41); - x64Gen_writeU8(x64GenContext, 0xFF); - x64Gen_writeU8(x64GenContext, 0xA7); - x64Gen_writeU32(x64GenContext, (uint32)lookupOffset); - } - return true; - } - else if( imlInstruction->operation == PPCREC_IML_MACRO_B_FAR ) - { - // remember new instruction pointer in RDX - uint32 newIP = imlInstruction->op_macro.param2; - x64Gen_mov_reg64Low32_imm32(x64GenContext, REG_RDX, newIP); - // Since RDX is constant we can use JMP [R15+const_offset] if jumpTableOffset+RDX*2 does not exceed the 2GB boundary - uint64 lookupOffset = (uint64)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable) + (uint64)newIP * 2ULL; - if (lookupOffset >= 0x80000000ULL) - { - // JMP [offset+RDX*(8/4)+R15] - x64Gen_writeU8(x64GenContext, 0x41); - x64Gen_writeU8(x64GenContext, 0xFF); - x64Gen_writeU8(x64GenContext, 0xA4); - x64Gen_writeU8(x64GenContext, 0x57); - x64Gen_writeU32(x64GenContext, (uint32)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); - } - else - { - x64Gen_writeU8(x64GenContext, 0x41); - x64Gen_writeU8(x64GenContext, 0xFF); - x64Gen_writeU8(x64GenContext, 0xA7); - x64Gen_writeU32(x64GenContext, (uint32)lookupOffset); - } - return true; - } - else if( imlInstruction->operation == PPCREC_IML_MACRO_LEAVE ) - { - uint32 currentInstructionAddress = imlInstruction->op_macro.param; - // remember PC value in REG_EDX - x64Gen_mov_reg64Low32_imm32(x64GenContext, REG_RDX, currentInstructionAddress); - - uint32 newIP = 0; // special value for recompiler exit - uint64 lookupOffset = (uint64)&(((PPCRecompilerInstanceData_t*)NULL)->ppcRecompilerDirectJumpTable) + (uint64)newIP * 2ULL; - // JMP [R15+offset] - x64Gen_writeU8(x64GenContext, 0x41); - x64Gen_writeU8(x64GenContext, 0xFF); - x64Gen_writeU8(x64GenContext, 0xA7); - x64Gen_writeU32(x64GenContext, (uint32)lookupOffset); - return true; - } - else if( imlInstruction->operation == PPCREC_IML_MACRO_DEBUGBREAK ) - { - x64Gen_mov_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, imlInstruction->op_macro.param2); - x64Gen_int3(x64GenContext); - return true; - } - else if( imlInstruction->operation == PPCREC_IML_MACRO_COUNT_CYCLES ) - { - uint32 cycleCount = imlInstruction->op_macro.param; - x64Gen_sub_mem32reg64_imm32(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, remainingCycles), cycleCount); - return true; - } - else if( imlInstruction->operation == PPCREC_IML_MACRO_HLE ) - { - uint32 ppcAddress = imlInstruction->op_macro.param; - uint32 funcId = imlInstruction->op_macro.param2; - //x64Gen_int3(x64GenContext); - // update instruction pointer - x64Gen_mov_mem32Reg64_imm32(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, instructionPointer), ppcAddress); - //// save hCPU (RSP) - //x64Gen_mov_reg64_imm64(x64GenContext, REG_RESV_TEMP, (uint64)&ppcRecompilerX64_hCPUTemp); - //x64Emit_mov_mem64_reg64(x64GenContext, REG_RESV_TEMP, 0, REG_RSP); - // set parameters - x64Gen_mov_reg64_reg64(x64GenContext, REG_RCX, REG_RSP); - x64Gen_mov_reg64_imm64(x64GenContext, REG_RDX, funcId); - // restore stackpointer from executionContext/hCPU->rspTemp - x64Emit_mov_reg64_mem64(x64GenContext, REG_RSP, REG_RESV_HCPU, offsetof(PPCInterpreter_t, rspTemp)); - //x64Emit_mov_reg64_mem64(x64GenContext, REG_RSP, REG_R14, 0); - //x64Gen_int3(x64GenContext); - // reserve space on stack for call parameters - x64Gen_sub_reg64_imm32(x64GenContext, REG_RSP, 8*11); // must be uneven number in order to retain stack 0x10 alignment - x64Gen_mov_reg64_imm64(x64GenContext, REG_RBP, 0); - // call HLE function - x64Gen_mov_reg64_imm64(x64GenContext, REG_RAX, (uint64)PPCRecompiler_virtualHLE); - x64Gen_call_reg64(x64GenContext, REG_RAX); - // restore RSP to hCPU (from RAX, result of PPCRecompiler_virtualHLE) - //x64Gen_mov_reg64_imm64(x64GenContext, REG_RESV_TEMP, (uint64)&ppcRecompilerX64_hCPUTemp); - //x64Emit_mov_reg64_mem64Reg64(x64GenContext, REG_RSP, REG_RESV_TEMP, 0); - x64Gen_mov_reg64_reg64(x64GenContext, REG_RSP, REG_RAX); - // MOV R15, ppcRecompilerInstanceData - x64Gen_mov_reg64_imm64(x64GenContext, REG_R15, (uint64)ppcRecompilerInstanceData); - // MOV R13, memory_base - x64Gen_mov_reg64_imm64(x64GenContext, REG_R13, (uint64)memory_base); - // check if cycles where decreased beyond zero, if yes -> leave recompiler - x64Gen_bt_mem8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, remainingCycles), 31); // check if negative - sint32 jumpInstructionOffset1 = x64GenContext->codeBufferIndex; - x64Gen_jmpc_near(x64GenContext, X86_CONDITION_NOT_CARRY, 0); - //x64Gen_int3(x64GenContext); - //x64Gen_mov_reg64Low32_imm32(x64GenContext, REG_RDX, ppcAddress); - - x64Emit_mov_reg64_mem32(x64GenContext, REG_RDX, REG_RSP, offsetof(PPCInterpreter_t, instructionPointer)); - // set EAX to 0 (we assume that ppcRecompilerDirectJumpTable[0] will be a recompiler escape function) - x64Gen_xor_reg32_reg32(x64GenContext, REG_RAX, REG_RAX); - // ADD RAX, R15 (R15 -> Pointer to ppcRecompilerInstanceData - x64Gen_add_reg64_reg64(x64GenContext, REG_RAX, REG_R15); - //// JMP [recompilerCallTable+EAX/4*8] - //x64Gen_int3(x64GenContext); - x64Gen_jmp_memReg64(x64GenContext, REG_RAX, (uint32)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset1, x64GenContext->codeBufferIndex); - // check if instruction pointer was changed - // assign new instruction pointer to EAX - x64Emit_mov_reg64_mem32(x64GenContext, REG_RAX, REG_RSP, offsetof(PPCInterpreter_t, instructionPointer)); - // remember instruction pointer in REG_EDX - x64Gen_mov_reg64_reg64(x64GenContext, REG_RDX, REG_RAX); - // EAX *= 2 - x64Gen_add_reg64_reg64(x64GenContext, REG_RAX, REG_RAX); - // ADD RAX, R15 (R15 -> Pointer to ppcRecompilerInstanceData - x64Gen_add_reg64_reg64(x64GenContext, REG_RAX, REG_R15); - // JMP [ppcRecompilerDirectJumpTable+RAX/4*8] - x64Gen_jmp_memReg64(x64GenContext, REG_RAX, (uint32)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); - return true; - } - else if( imlInstruction->operation == PPCREC_IML_MACRO_MFTB ) - { - uint32 ppcAddress = imlInstruction->op_macro.param; - uint32 sprId = imlInstruction->op_macro.param2&0xFFFF; - uint32 gprIndex = (imlInstruction->op_macro.param2>>16)&0x1F; - // update instruction pointer - x64Gen_mov_mem32Reg64_imm32(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, instructionPointer), ppcAddress); - // set parameters - x64Gen_mov_reg64_reg64(x64GenContext, REG_RCX, REG_RSP); - x64Gen_mov_reg64_imm64(x64GenContext, REG_RDX, gprIndex); - // restore stackpointer to original RSP - x64Emit_mov_reg64_mem64(x64GenContext, REG_RSP, REG_RESV_HCPU, offsetof(PPCInterpreter_t, rspTemp)); - // push hCPU on stack - x64Gen_push_reg64(x64GenContext, REG_RCX); - // reserve space on stack for call parameters - x64Gen_sub_reg64_imm32(x64GenContext, REG_RSP, 8*11 + 8); - x64Gen_mov_reg64_imm64(x64GenContext, REG_RBP, 0); - // call HLE function - if( sprId == SPR_TBL ) - x64Gen_mov_reg64_imm64(x64GenContext, REG_RAX, (uint64)PPCRecompiler_getTBL); - else if( sprId == SPR_TBU ) - x64Gen_mov_reg64_imm64(x64GenContext, REG_RAX, (uint64)PPCRecompiler_getTBU); - else - assert_dbg(); - x64Gen_call_reg64(x64GenContext, REG_RAX); - // restore hCPU from stack - x64Gen_add_reg64_imm32(x64GenContext, REG_RSP, 8 * 11 + 8); - x64Gen_pop_reg64(x64GenContext, REG_RSP); - // MOV R15, ppcRecompilerInstanceData - x64Gen_mov_reg64_imm64(x64GenContext, REG_R15, (uint64)ppcRecompilerInstanceData); - // MOV R13, memory_base - x64Gen_mov_reg64_imm64(x64GenContext, REG_R13, (uint64)memory_base); - return true; - } - else - { - debug_printf("Unknown recompiler macro operation %d\n", imlInstruction->operation); - assert_dbg(); - } - return false; -} - -/* -* Load from memory -*/ -bool PPCRecompilerX64Gen_imlInstruction_load(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction, bool indexed) -{ - sint32 realRegisterData = tempToRealRegister(imlInstruction->op_storeLoad.registerData); - sint32 realRegisterMem = tempToRealRegister(imlInstruction->op_storeLoad.registerMem); - sint32 realRegisterMem2 = PPC_REC_INVALID_REGISTER; - if( indexed ) - realRegisterMem2 = tempToRealRegister(imlInstruction->op_storeLoad.registerMem2); - if( false )//imlInstruction->op_storeLoad.flags & PPCREC_IML_OP_FLAG_FASTMEMACCESS ) - { - // load u8/u16/u32 via direct memory access + optional sign extend - assert_dbg(); // todo - } - else - { - if( indexed && realRegisterMem == realRegisterMem2 ) - { - return false; - } - if( indexed && realRegisterData == realRegisterMem2 ) - { - // for indexed memory access realRegisterData must not be the same register as the second memory register, - // this can easily be fixed by swapping the logic of realRegisterMem and realRegisterMem2 - sint32 temp = realRegisterMem; - realRegisterMem = realRegisterMem2; - realRegisterMem2 = temp; - } - - bool signExtend = imlInstruction->op_storeLoad.flags2.signExtend; - bool switchEndian = imlInstruction->op_storeLoad.flags2.swapEndian; - if( imlInstruction->op_storeLoad.copyWidth == 32 ) - { - //if( indexed ) - // PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - if (indexed) - { - x64Gen_lea_reg64Low32_reg64Low32PlusReg64Low32(x64GenContext, REG_RESV_TEMP, realRegisterMem, realRegisterMem2); - } - if( g_CPUFeatures.x86.movbe && switchEndian ) - { - if (indexed) - { - x64Gen_movBEZeroExtend_reg64_mem32Reg64PlusReg64(x64GenContext, realRegisterData, REG_R13, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32); - //if (indexed && realRegisterMem != realRegisterData) - // x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); - } - else - { - x64Gen_movBEZeroExtend_reg64_mem32Reg64PlusReg64(x64GenContext, realRegisterData, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32); - } - } - else - { - if (indexed) - { - x64Emit_mov_reg32_mem32(x64GenContext, realRegisterData, REG_R13, REG_RESV_TEMP, imlInstruction->op_storeLoad.immS32); - //if (realRegisterMem != realRegisterData) - // x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); - if (switchEndian) - x64Gen_bswap_reg64Lower32bit(x64GenContext, realRegisterData); - } - else - { - x64Emit_mov_reg32_mem32(x64GenContext, realRegisterData, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32); - if (switchEndian) - x64Gen_bswap_reg64Lower32bit(x64GenContext, realRegisterData); - } - } - } - else if( imlInstruction->op_storeLoad.copyWidth == 16 ) - { - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); // todo: We can avoid this if MOVBE is available - if (indexed) - { - x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); - } - if( g_CPUFeatures.x86.movbe && switchEndian ) - { - x64Gen_movBEZeroExtend_reg64Low16_mem16Reg64PlusReg64(x64GenContext, realRegisterData, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32); - if( indexed && realRegisterMem != realRegisterData ) - x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); - } - else - { - x64Gen_movZeroExtend_reg64Low16_mem16Reg64PlusReg64(x64GenContext, realRegisterData, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32); - if( indexed && realRegisterMem != realRegisterData ) - x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); - if( switchEndian ) - x64Gen_rol_reg64Low16_imm8(x64GenContext, realRegisterData, 8); - } - if( signExtend ) - x64Gen_movSignExtend_reg64Low32_reg64Low16(x64GenContext, realRegisterData, realRegisterData); - else - x64Gen_movZeroExtend_reg64Low32_reg64Low16(x64GenContext, realRegisterData, realRegisterData); - } - else if( imlInstruction->op_storeLoad.copyWidth == 8 ) - { - if( indexed ) - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - // todo: Optimize by using only MOVZX/MOVSX - if( indexed ) - x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); - // todo: Use sign extend move from memory instead of separate sign-extend? - if( signExtend ) - x64Gen_movSignExtend_reg64Low32_mem8Reg64PlusReg64(x64GenContext, realRegisterData, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32); - else - x64Emit_movZX_reg32_mem8(x64GenContext, realRegisterData, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32); - if( indexed && realRegisterMem != realRegisterData ) - x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); - } - else if( imlInstruction->op_storeLoad.copyWidth == PPC_REC_LOAD_LWARX_MARKER ) - { - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - if( imlInstruction->op_storeLoad.immS32 != 0 ) - assert_dbg(); // not supported - if( indexed ) - x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); - x64Emit_mov_mem32_reg32(x64GenContext, REG_RSP, (uint32)offsetof(PPCInterpreter_t, reservedMemAddr), realRegisterMem); // remember EA for reservation - x64Emit_mov_reg32_mem32(x64GenContext, realRegisterData, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32); - if( indexed && realRegisterMem != realRegisterData ) - x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); - if( switchEndian ) - x64Gen_bswap_reg64Lower32bit(x64GenContext, realRegisterData); - x64Emit_mov_mem32_reg32(x64GenContext, REG_RSP, (uint32)offsetof(PPCInterpreter_t, reservedMemValue), realRegisterData); // remember value for reservation - // LWARX instruction costs extra cycles (this speeds up busy loops) - x64Gen_sub_mem32reg64_imm32(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, remainingCycles), 20); - } - else if( imlInstruction->op_storeLoad.copyWidth == PPC_REC_STORE_LSWI_3 ) - { - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - if( switchEndian == false ) - assert_dbg(); - if( indexed ) - x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); // can be replaced with LEA temp, [memReg1+memReg2] (this way we can avoid the SUB instruction after the move) - if( g_CPUFeatures.x86.movbe ) - { - x64Gen_movBEZeroExtend_reg64_mem32Reg64PlusReg64(x64GenContext, realRegisterData, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32); - if( indexed && realRegisterMem != realRegisterData ) - x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); - } - else - { - x64Emit_mov_reg32_mem32(x64GenContext, realRegisterData, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32); - if( indexed && realRegisterMem != realRegisterData ) - x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); - x64Gen_bswap_reg64Lower32bit(x64GenContext, realRegisterData); - } - x64Gen_and_reg64Low32_imm32(x64GenContext, realRegisterData, 0xFFFFFF00); - } - else - return false; - return true; - } - return false; -} - -/* -* Write to memory -*/ -bool PPCRecompilerX64Gen_imlInstruction_store(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction, bool indexed) -{ - sint32 realRegisterData = tempToRealRegister(imlInstruction->op_storeLoad.registerData); - sint32 realRegisterMem = tempToRealRegister(imlInstruction->op_storeLoad.registerMem); - sint32 realRegisterMem2 = PPC_REC_INVALID_REGISTER; - if (indexed) - realRegisterMem2 = tempToRealRegister(imlInstruction->op_storeLoad.registerMem2); - - if (false)//imlInstruction->op_storeLoad.flags & PPCREC_IML_OP_FLAG_FASTMEMACCESS ) - { - // load u8/u16/u32 via direct memory access + optional sign extend - assert_dbg(); // todo - } - else - { - if (indexed && realRegisterMem == realRegisterMem2) - { - return false; - } - if (indexed && realRegisterData == realRegisterMem2) - { - // for indexed memory access realRegisterData must not be the same register as the second memory register, - // this can easily be fixed by swapping the logic of realRegisterMem and realRegisterMem2 - sint32 temp = realRegisterMem; - realRegisterMem = realRegisterMem2; - realRegisterMem2 = temp; - } - - bool signExtend = imlInstruction->op_storeLoad.flags2.signExtend; - bool swapEndian = imlInstruction->op_storeLoad.flags2.swapEndian; - if (imlInstruction->op_storeLoad.copyWidth == 32) - { - if (indexed) - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - uint32 valueRegister; - if ((swapEndian == false || g_CPUFeatures.x86.movbe) && realRegisterMem != realRegisterData) - { - valueRegister = realRegisterData; - } - else - { - x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, realRegisterData); - valueRegister = REG_RESV_TEMP; - } - if (g_CPUFeatures.x86.movbe == false && swapEndian) - x64Gen_bswap_reg64Lower32bit(x64GenContext, valueRegister); - if (indexed) - x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); - if (g_CPUFeatures.x86.movbe && swapEndian) - x64Gen_movBETruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32, valueRegister); - else - x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32, valueRegister); - if (indexed) - x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); - } - else if (imlInstruction->op_storeLoad.copyWidth == 16) - { - if (indexed || swapEndian) - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, realRegisterData); - if (swapEndian) - x64Gen_rol_reg64Low16_imm8(x64GenContext, REG_RESV_TEMP, 8); - if (indexed) - x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); - x64Gen_movTruncate_mem16Reg64PlusReg64_reg64(x64GenContext, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP); - if (indexed) - x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); - // todo: Optimize this, e.g. by using MOVBE - } - else if (imlInstruction->op_storeLoad.copyWidth == 8) - { - if (indexed) - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - if (indexed && realRegisterMem == realRegisterData) - { - x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, realRegisterData); - realRegisterData = REG_RESV_TEMP; - } - if (indexed) - x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); - x64Gen_movTruncate_mem8Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32, realRegisterData); - if (indexed) - x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); - } - else if (imlInstruction->op_storeLoad.copyWidth == PPC_REC_STORE_STWCX_MARKER) - { - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - if (imlInstruction->op_storeLoad.immS32 != 0) - assert_dbg(); // todo - // reset cr0 LT, GT and EQ - sint32 crRegister = 0; - x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, cr) + sizeof(uint8)*(crRegister * 4 + PPCREC_CR_BIT_LT), 0); - x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, cr) + sizeof(uint8)*(crRegister * 4 + PPCREC_CR_BIT_GT), 0); - x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, cr) + sizeof(uint8)*(crRegister * 4 + PPCREC_CR_BIT_EQ), 0); - // calculate effective address - x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, realRegisterData); - if (swapEndian) - x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); - if (indexed) - x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); - // realRegisterMem now holds EA - x64Gen_cmp_reg64Low32_mem32reg64(x64GenContext, realRegisterMem, REG_RESV_HCPU, offsetof(PPCInterpreter_t, reservedMemAddr)); - sint32 jumpInstructionOffsetJumpToEnd = x64GenContext->codeBufferIndex; - x64Gen_jmpc_near(x64GenContext, X86_CONDITION_NOT_EQUAL, 0); - // EA matches reservation - // backup EAX (since it's an explicit operand of CMPXCHG and will be overwritten) - x64Emit_mov_mem32_reg32(x64GenContext, REG_RSP, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[0]), REG_EAX); - // backup REG_RESV_MEMBASE - x64Emit_mov_mem64_reg64(x64GenContext, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[2]), REG_RESV_MEMBASE); - // add mem register to REG_RESV_MEMBASE - x64Gen_add_reg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem); - // load reserved value in EAX - x64Emit_mov_reg64_mem32(x64GenContext, REG_EAX, REG_RESV_HCPU, offsetof(PPCInterpreter_t, reservedMemValue)); - // bswap EAX - x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_EAX); - - //x64Gen_lock_cmpxchg_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, 0, REG_RESV_TEMP); - x64Gen_lock_cmpxchg_mem32Reg64_reg64(x64GenContext, REG_RESV_MEMBASE, 0, REG_RESV_TEMP); - - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_EQUAL, REG_RSP, offsetof(PPCInterpreter_t, cr) + sizeof(uint8)*(crRegister * 4 + PPCREC_CR_BIT_EQ)); - - // reset reservation - x64Gen_mov_mem32Reg64_imm32(x64GenContext, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, reservedMemAddr), 0); - x64Gen_mov_mem32Reg64_imm32(x64GenContext, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, reservedMemValue), 0); - - // restore EAX - x64Emit_mov_reg64_mem32(x64GenContext, REG_EAX, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[0])); - // restore REG_RESV_MEMBASE - x64Emit_mov_reg64_mem64(x64GenContext, REG_RESV_MEMBASE, REG_RESV_HCPU, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[2])); - - // copy XER SO to CR0 SO - x64Gen_bt_mem8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, spr.XER), 31); - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_CARRY, REG_RESV_HCPU, offsetof(PPCInterpreter_t, cr) + sizeof(uint8)*(crRegister * 4 + PPCREC_CR_BIT_SO)); - // end - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffsetJumpToEnd, x64GenContext->codeBufferIndex); - } - else if (imlInstruction->op_storeLoad.copyWidth == PPC_REC_STORE_STSWI_2) - { - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, realRegisterData); - x64Gen_shr_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, 16); // store upper 2 bytes .. - x64Gen_rol_reg64Low16_imm8(x64GenContext, REG_RESV_TEMP, 8); // .. as big-endian - if (indexed) - x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); - - x64Gen_movTruncate_mem16Reg64PlusReg64_reg64(x64GenContext, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP); - if (indexed) - x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); - } - else if (imlInstruction->op_storeLoad.copyWidth == PPC_REC_STORE_STSWI_3) - { - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, realRegisterData); - if (indexed) - x64Gen_add_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); - - x64Gen_shr_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, 8); - x64Gen_movTruncate_mem8Reg64PlusReg64_reg64(x64GenContext, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32 + 2, REG_RESV_TEMP); - x64Gen_shr_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, 8); - x64Gen_movTruncate_mem8Reg64PlusReg64_reg64(x64GenContext, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32 + 1, REG_RESV_TEMP); - x64Gen_shr_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, 8); - x64Gen_movTruncate_mem8Reg64PlusReg64_reg64(x64GenContext, REG_R13, realRegisterMem, imlInstruction->op_storeLoad.immS32 + 0, REG_RESV_TEMP); - - if (indexed) - x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); - } - else - return false; - return true; - } - return false; -} - -/* - * Copy byte/word/dword from memory to memory - */ -void PPCRecompilerX64Gen_imlInstruction_mem2mem(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) -{ - sint32 realSrcMemReg = tempToRealRegister(imlInstruction->op_mem2mem.src.registerMem); - sint32 realSrcMemImm = imlInstruction->op_mem2mem.src.immS32; - sint32 realDstMemReg = tempToRealRegister(imlInstruction->op_mem2mem.dst.registerMem); - sint32 realDstMemImm = imlInstruction->op_mem2mem.dst.immS32; - // PPCRecompilerX64Gen_crConditionFlags_forget() is not needed here, since MOVs don't affect eflags - if (imlInstruction->op_mem2mem.copyWidth == 32) - { - x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_R13, realSrcMemReg, realSrcMemImm); - x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_R13, realDstMemReg, realDstMemImm, REG_RESV_TEMP); - } - else - { - assert_dbg(); - } -} - -bool PPCRecompilerX64Gen_imlInstruction_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) -{ - if (imlInstruction->operation == PPCREC_IML_OP_ASSIGN) - { - // registerResult = registerA - if (imlInstruction->crRegister != PPC_REC_INVALID_REGISTER) - { - x64Gen_mov_reg64_reg64(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerA)); - if (imlInstruction->crMode == PPCREC_CR_MODE_LOGICAL) - { - // since MOV doesn't set eflags we need another test instruction - x64Gen_test_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerResult)); - // set cr bits - PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); - } - else - { - assert_dbg(); - } - } - else - { - x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerA)); - } - } - else if (imlInstruction->operation == PPCREC_IML_OP_ENDIAN_SWAP) - { - // registerResult = endianSwap32(registerA) - if (imlInstruction->op_r_r.registerA != imlInstruction->op_r_r.registerResult) - assert_dbg(); - x64Gen_bswap_reg64Lower32bit(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult)); - } - else if( imlInstruction->operation == PPCREC_IML_OP_ADD ) - { - // registerResult += registerA - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); - x64Gen_add_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerA)); - } - else if( imlInstruction->operation == PPCREC_IML_OP_ASSIGN_S8_TO_S32 ) - { - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - x64Gen_movSignExtend_reg64Low32_reg64Low8(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerA)); - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - if( imlInstruction->crMode == PPCREC_CR_MODE_ARITHMETIC ) - { - x64Gen_test_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerResult)); - // set cr bits - PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); - } - else - { - debug_printf("PPCRecompilerX64Gen_imlInstruction_r_r(): Unsupported operation\n"); - assert_dbg(); - } - } - } - else if( imlInstruction->operation == PPCREC_IML_OP_OR || imlInstruction->operation == PPCREC_IML_OP_AND || imlInstruction->operation == PPCREC_IML_OP_XOR ) - { - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - if( imlInstruction->operation == PPCREC_IML_OP_OR ) - { - // registerResult |= registerA - x64Gen_or_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerA)); - } - else if( imlInstruction->operation == PPCREC_IML_OP_AND ) - { - // registerResult &= registerA - x64Gen_and_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerA)); - } - else - { - // registerResult ^= registerA - x64Gen_xor_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerA)); - } - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - // set cr bits - PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); - } - } - else if( imlInstruction->operation == PPCREC_IML_OP_NOT ) - { - // copy register content if different registers - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - if( imlInstruction->op_r_r.registerResult != imlInstruction->op_r_r.registerA ) - { - x64Gen_mov_reg64_reg64(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerA)); - } - // NOT destination register - x64Gen_not_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult)); - // update cr bits - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - // NOT instruction does not update flags, so we have to generate an additional TEST instruction - x64Gen_test_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerResult)); - // set cr bits - PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); - } - } - else if( imlInstruction->operation == PPCREC_IML_OP_CNTLZW ) - { - // count leading zeros - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); - if( g_CPUFeatures.x86.lzcnt ) - { - x64Gen_lzcnt_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerA)); - } - else - { - x64Gen_test_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerA), tempToRealRegister(imlInstruction->op_r_r.registerA)); - sint32 jumpInstructionOffset1 = x64GenContext->codeBufferIndex; - x64Gen_jmpc_near(x64GenContext, X86_CONDITION_EQUAL, 0); - x64Gen_bsr_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerA)); - x64Gen_neg_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult)); - x64Gen_add_reg64Low32_imm32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), 32-1); - sint32 jumpInstructionOffset2 = x64GenContext->codeBufferIndex; - x64Gen_jmpc_near(x64GenContext, X86_CONDITION_NONE, 0); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset1, x64GenContext->codeBufferIndex); - x64Gen_mov_reg64Low32_imm32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), 32); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset2, x64GenContext->codeBufferIndex); - } - } - else if( imlInstruction->operation == PPCREC_IML_OP_COMPARE_SIGNED || imlInstruction->operation == PPCREC_IML_OP_COMPARE_UNSIGNED ) - { - // registerA CMP registerB (arithmetic compare) - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - if( imlInstruction->crRegister == PPC_REC_INVALID_REGISTER ) - { - return false; // a NO-OP instruction - } - if( imlInstruction->crRegister >= 8 ) - { - return false; - } - // update state of cr register - if( imlInstruction->operation == PPCREC_IML_OP_COMPARE_SIGNED ) - PPCRecompilerX64Gen_crConditionFlags_set(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction->crRegister, PPCREC_CR_STATE_TYPE_SIGNED_ARITHMETIC); - else - PPCRecompilerX64Gen_crConditionFlags_set(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction->crRegister, PPCREC_CR_STATE_TYPE_UNSIGNED_ARITHMETIC); - // create compare instruction - x64Gen_cmp_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerA)); - // set cr bits - sint32 crRegister = imlInstruction->crRegister; - if( imlInstruction->operation == PPCREC_IML_OP_COMPARE_SIGNED ) - { - if( (imlInstruction->crIgnoreMask&(1<<(crRegister*4+PPCREC_CR_BIT_LT))) == 0 ) - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_SIGNED_LESS, REG_ESP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_LT)); - if( (imlInstruction->crIgnoreMask&(1<<(crRegister*4+PPCREC_CR_BIT_GT))) == 0 ) - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_SIGNED_GREATER, REG_ESP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_GT)); - if( (imlInstruction->crIgnoreMask&(1<<(crRegister*4+PPCREC_CR_BIT_EQ))) == 0 ) - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_EQUAL, REG_ESP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_EQ)); - // todo: Also set summary overflow if xer bit is set - } - else if( imlInstruction->operation == PPCREC_IML_OP_COMPARE_UNSIGNED ) - { - if( (imlInstruction->crIgnoreMask&(1<<(crRegister*4+PPCREC_CR_BIT_LT))) == 0 ) - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_UNSIGNED_BELOW, REG_ESP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_LT)); - if( (imlInstruction->crIgnoreMask&(1<<(crRegister*4+PPCREC_CR_BIT_GT))) == 0 ) - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_UNSIGNED_ABOVE, REG_ESP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_GT)); - if( (imlInstruction->crIgnoreMask&(1<<(crRegister*4+PPCREC_CR_BIT_EQ))) == 0 ) - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_EQUAL, REG_ESP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_EQ)); - // todo: Also set summary overflow if xer bit is set - } - else - assert_dbg(); - } - else if( imlInstruction->operation == PPCREC_IML_OP_NEG ) - { - // copy register content if different registers - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - if( imlInstruction->op_r_r.registerResult != imlInstruction->op_r_r.registerA ) - { - x64Gen_mov_reg64_reg64(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerA)); - } - // NEG destination register - x64Gen_neg_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult)); - // update cr bits - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - // set cr bits - PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); - } - } - else if( imlInstruction->operation == PPCREC_IML_OP_ADD_CARRY ) - { - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - // copy operand to result if different registers - if( imlInstruction->op_r_r.registerResult != imlInstruction->op_r_r.registerA ) - { - x64Gen_mov_reg64_reg64(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerA)); - } - // copy xer_ca to eflags carry - x64Gen_bt_mem8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 0); - // add carry bit - x64Gen_adc_reg64Low32_imm32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), 0); - // update xer carry - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_CARRY, REG_RSP, offsetof(PPCInterpreter_t, xer_ca)); - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - // set cr bits - sint32 crRegister = imlInstruction->crRegister; - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_SIGN, REG_RSP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_LT)); // check for sign instead of _BELOW (CF) which is not set by AND/OR - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_UNSIGNED_ABOVE, REG_RSP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_GT)); - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_EQUAL, REG_RSP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_EQ)); - // todo: Use different version of PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction) - // todo: Also set summary overflow if xer bit is set - } - } - else if( imlInstruction->operation == PPCREC_IML_OP_ADD_CARRY_ME ) - { - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - // copy operand to result if different registers - if( imlInstruction->op_r_r.registerResult != imlInstruction->op_r_r.registerA ) - { - x64Gen_mov_reg64_reg64(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerA)); - } - // copy xer_ca to eflags carry - x64Gen_bt_mem8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 0); - // add carry bit - x64Gen_adc_reg64Low32_imm32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), (uint32)-1); - // update xer carry - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_CARRY, REG_RSP, offsetof(PPCInterpreter_t, xer_ca)); - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - // set cr bits - sint32 crRegister = imlInstruction->crRegister; - x64Gen_test_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerResult)); - PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); - } - } - else if( imlInstruction->operation == PPCREC_IML_OP_SUB_CARRY_UPDATE_CARRY ) - { - // registerResult = ~registerOperand1 + carry - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - sint32 rRegResult = tempToRealRegister(imlInstruction->op_r_r.registerResult); - sint32 rRegOperand1 = tempToRealRegister(imlInstruction->op_r_r.registerA); - // copy operand to result register - x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand1); - // execute NOT on result - x64Gen_not_reg64Low32(x64GenContext, rRegResult); - // copy xer_ca to eflags carry - x64Gen_bt_mem8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 0); - // add carry - x64Gen_adc_reg64Low32_imm32(x64GenContext, rRegResult, 0); - // update carry - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_CARRY, REG_RSP, offsetof(PPCInterpreter_t, xer_ca)); - // update cr if requested - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - if( imlInstruction->crMode == PPCREC_CR_MODE_LOGICAL ) - { - x64Gen_test_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegResult); - // set cr bits - PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); - } - else - { - assert_dbg(); - } - } - } - else if( imlInstruction->operation == PPCREC_IML_OP_ASSIGN_S16_TO_S32 ) - { - // registerResult = (uint32)(sint32)(sint16)registerA - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - x64Gen_movSignExtend_reg64Low32_reg64Low16(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), reg32ToReg16(tempToRealRegister(imlInstruction->op_r_r.registerA))); - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - if( imlInstruction->crMode == PPCREC_CR_MODE_ARITHMETIC ) - { - x64Gen_test_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r.registerResult), tempToRealRegister(imlInstruction->op_r_r.registerResult)); - // set cr bits - PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); - } - else - { - debug_printf("PPCRecompilerX64Gen_imlInstruction_r_r(): Unsupported operation\n"); - assert_dbg(); - } - } - } - else if( imlInstruction->operation == PPCREC_IML_OP_DCBZ ) - { - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - if( imlInstruction->op_r_r.registerResult != imlInstruction->op_r_r.registerA ) - { - x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, tempToRealRegister(imlInstruction->op_r_r.registerA)); - x64Gen_add_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, tempToRealRegister(imlInstruction->op_r_r.registerResult)); - x64Gen_and_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, ~0x1F); - x64Gen_add_reg64_reg64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE); - for(sint32 f=0; f<0x20; f+=8) - x64Gen_mov_mem64Reg64_imm32(x64GenContext, REG_RESV_TEMP, f, 0); - } - else - { - // calculate effective address - x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, tempToRealRegister(imlInstruction->op_r_r.registerA)); - x64Gen_and_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, ~0x1F); - x64Gen_add_reg64_reg64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE); - for(sint32 f=0; f<0x20; f+=8) - x64Gen_mov_mem64Reg64_imm32(x64GenContext, REG_RESV_TEMP, f, 0); - } - } - else - { - debug_printf("PPCRecompilerX64Gen_imlInstruction_r_r(): Unsupported operation 0x%x\n", imlInstruction->operation); - return false; - } - return true; -} - -bool PPCRecompilerX64Gen_imlInstruction_r_s32(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) -{ - if( imlInstruction->operation == PPCREC_IML_OP_ASSIGN ) - { - // registerResult = immS32 - cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); - x64Gen_mov_reg64Low32_imm32(x64GenContext, tempToRealRegister(imlInstruction->op_r_immS32.registerIndex), (uint32)imlInstruction->op_r_immS32.immS32); - } - else if( imlInstruction->operation == PPCREC_IML_OP_ADD ) - { - // registerResult += immS32 - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - assert_dbg(); - } - x64Gen_add_reg64Low32_imm32(x64GenContext, tempToRealRegister(imlInstruction->op_r_immS32.registerIndex), (uint32)imlInstruction->op_r_immS32.immS32); - } - else if( imlInstruction->operation == PPCREC_IML_OP_SUB ) - { - // registerResult -= immS32 - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - if (imlInstruction->crRegister == PPCREC_CR_REG_TEMP) - { - // do nothing -> SUB is for BDNZ instruction - } - else if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - // update cr register - assert_dbg(); - } - x64Gen_sub_reg64Low32_imm32(x64GenContext, tempToRealRegister(imlInstruction->op_r_immS32.registerIndex), (uint32)imlInstruction->op_r_immS32.immS32); - } - else if( imlInstruction->operation == PPCREC_IML_OP_AND ) - { - // registerResult &= immS32 - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - x64Gen_and_reg64Low32_imm32(x64GenContext, tempToRealRegister(imlInstruction->op_r_immS32.registerIndex), (uint32)imlInstruction->op_r_immS32.immS32); - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - if( imlInstruction->crMode != PPCREC_CR_MODE_LOGICAL ) - { - assert_dbg(); - } - // set cr bits - sint32 crRegister = imlInstruction->crRegister; - PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); - // todo: Set CR SO if XER SO bit is set - } - } - else if( imlInstruction->operation == PPCREC_IML_OP_OR ) - { - // registerResult |= immS32 - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); - x64Gen_or_reg64Low32_imm32(x64GenContext, tempToRealRegister(imlInstruction->op_r_immS32.registerIndex), (uint32)imlInstruction->op_r_immS32.immS32); - } - else if( imlInstruction->operation == PPCREC_IML_OP_XOR ) - { - // registerResult ^= immS32 - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); - x64Gen_xor_reg64Low32_imm32(x64GenContext, tempToRealRegister(imlInstruction->op_r_immS32.registerIndex), (uint32)imlInstruction->op_r_immS32.immS32); - } - else if( imlInstruction->operation == PPCREC_IML_OP_LEFT_ROTATE ) - { - // registerResult <<<= immS32 - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); - if( (imlInstruction->op_r_immS32.immS32&0x80) ) - assert_dbg(); // should not happen - x64Gen_rol_reg64Low32_imm8(x64GenContext, tempToRealRegister(imlInstruction->op_r_immS32.registerIndex), (uint8)imlInstruction->op_r_immS32.immS32); - } - else if( imlInstruction->operation == PPCREC_IML_OP_COMPARE_SIGNED || imlInstruction->operation == PPCREC_IML_OP_COMPARE_UNSIGNED ) - { - // registerResult CMP immS32 (arithmetic compare) - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - if( imlInstruction->crRegister == PPC_REC_INVALID_REGISTER ) - { - debug_printf("PPCRecompilerX64Gen_imlInstruction_r_s32(): No-Op CMP found\n"); - return true; // a NO-OP instruction - } - if( imlInstruction->crRegister >= 8 ) - { - debug_printf("PPCRecompilerX64Gen_imlInstruction_r_s32(): Unsupported CMP with crRegister = 8\n"); - return false; - } - // update state of cr register - if( imlInstruction->operation == PPCREC_IML_OP_COMPARE_SIGNED ) - PPCRecompilerX64Gen_crConditionFlags_set(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction->crRegister, PPCREC_CR_STATE_TYPE_SIGNED_ARITHMETIC); - else - PPCRecompilerX64Gen_crConditionFlags_set(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction->crRegister, PPCREC_CR_STATE_TYPE_UNSIGNED_ARITHMETIC); - // create compare instruction - x64Gen_cmp_reg64Low32_imm32(x64GenContext, tempToRealRegister(imlInstruction->op_r_immS32.registerIndex), imlInstruction->op_r_immS32.immS32); - // set cr bits - uint32 crRegister = imlInstruction->crRegister; - if( imlInstruction->operation == PPCREC_IML_OP_COMPARE_SIGNED ) - { - if( (imlInstruction->crIgnoreMask&(1<<(crRegister*4+PPCREC_CR_BIT_LT))) == 0 ) - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_SIGNED_LESS, REG_ESP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_LT)); - if( (imlInstruction->crIgnoreMask&(1<<(crRegister*4+PPCREC_CR_BIT_GT))) == 0 ) - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_SIGNED_GREATER, REG_ESP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_GT)); - if( (imlInstruction->crIgnoreMask&(1<<(crRegister*4+PPCREC_CR_BIT_EQ))) == 0 ) - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_EQUAL, REG_ESP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_EQ)); - } - else if( imlInstruction->operation == PPCREC_IML_OP_COMPARE_UNSIGNED ) - { - if( (imlInstruction->crIgnoreMask&(1<<(crRegister*4+PPCREC_CR_BIT_LT))) == 0 ) - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_UNSIGNED_BELOW, REG_ESP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_LT)); - if( (imlInstruction->crIgnoreMask&(1<<(crRegister*4+PPCREC_CR_BIT_GT))) == 0 ) - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_UNSIGNED_ABOVE, REG_ESP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_GT)); - if( (imlInstruction->crIgnoreMask&(1<<(crRegister*4+PPCREC_CR_BIT_EQ))) == 0 ) - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_EQUAL, REG_ESP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_EQ)); - } - else - assert_dbg(); - // todo: Also set summary overflow if xer bit is set? - } - else if( imlInstruction->operation == PPCREC_IML_OP_MFCR ) - { - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - uint32 destRegister = tempToRealRegister(imlInstruction->op_r_immS32.registerIndex); - x64Gen_xor_reg64Low32_reg64Low32(x64GenContext, destRegister, destRegister); - for(sint32 f=0; f<32; f++) - { - x64Gen_bt_mem8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, cr)+f, 0); - x64Gen_adc_reg64Low32_reg64Low32(x64GenContext, destRegister, destRegister); - } - } - else if (imlInstruction->operation == PPCREC_IML_OP_MTCRF) - { - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - uint32 srcRegister = tempToRealRegister(imlInstruction->op_r_immS32.registerIndex); - uint32 crBitMask = ppc_MTCRFMaskToCRBitMask((uint32)imlInstruction->op_r_immS32.immS32); - for (sint32 f = 0; f < 32; f++) - { - if(((crBitMask >> f) & 1) == 0) - continue; - x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_ESP, offsetof(PPCInterpreter_t, cr) + sizeof(uint8) * (f), 0); - x64Gen_test_reg64Low32_imm32(x64GenContext, srcRegister, 0x80000000>>f); - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_NOT_EQUAL, REG_ESP, offsetof(PPCInterpreter_t, cr) + sizeof(uint8) * (f)); - } - } - else - { - debug_printf("PPCRecompilerX64Gen_imlInstruction_r_s32(): Unsupported operation 0x%x\n", imlInstruction->operation); - return false; - } - return true; -} - -bool PPCRecompilerX64Gen_imlInstruction_conditional_r_s32(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) -{ - if (imlInstruction->operation == PPCREC_IML_OP_ASSIGN) - { - // registerResult = immS32 (conditional) - if (imlInstruction->crRegister != PPC_REC_INVALID_REGISTER) - { - assert_dbg(); - } - - x64Gen_mov_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, (uint32)imlInstruction->op_conditional_r_s32.immS32); - - uint8 crBitIndex = imlInstruction->op_conditional_r_s32.crRegisterIndex * 4 + imlInstruction->op_conditional_r_s32.crBitIndex; - if (imlInstruction->op_conditional_r_s32.crRegisterIndex == x64GenContext->activeCRRegister) - { - if (x64GenContext->activeCRState == PPCREC_CR_STATE_TYPE_UNSIGNED_ARITHMETIC) - { - if (imlInstruction->op_conditional_r_s32.crBitIndex == CR_BIT_LT) - { - x64Gen_cmovcc_reg64Low32_reg64Low32(x64GenContext, imlInstruction->op_conditional_r_s32.bitMustBeSet ? X86_CONDITION_CARRY : X86_CONDITION_NOT_CARRY, tempToRealRegister(imlInstruction->op_conditional_r_s32.registerIndex), REG_RESV_TEMP); - return true; - } - else if (imlInstruction->op_conditional_r_s32.crBitIndex == CR_BIT_EQ) - { - x64Gen_cmovcc_reg64Low32_reg64Low32(x64GenContext, imlInstruction->op_conditional_r_s32.bitMustBeSet ? X86_CONDITION_EQUAL : X86_CONDITION_NOT_EQUAL, tempToRealRegister(imlInstruction->op_conditional_r_s32.registerIndex), REG_RESV_TEMP); - return true; - } - else if (imlInstruction->op_conditional_r_s32.crBitIndex == CR_BIT_GT) - { - x64Gen_cmovcc_reg64Low32_reg64Low32(x64GenContext, imlInstruction->op_conditional_r_s32.bitMustBeSet ? X86_CONDITION_UNSIGNED_ABOVE : X86_CONDITION_UNSIGNED_BELOW_EQUAL, tempToRealRegister(imlInstruction->op_conditional_r_s32.registerIndex), REG_RESV_TEMP); - return true; - } - } - else if (x64GenContext->activeCRState == PPCREC_CR_STATE_TYPE_SIGNED_ARITHMETIC) - { - if (imlInstruction->op_conditional_r_s32.crBitIndex == CR_BIT_LT) - { - x64Gen_cmovcc_reg64Low32_reg64Low32(x64GenContext, imlInstruction->op_conditional_r_s32.bitMustBeSet ? X86_CONDITION_SIGNED_LESS : X86_CONDITION_SIGNED_GREATER_EQUAL, tempToRealRegister(imlInstruction->op_conditional_r_s32.registerIndex), REG_RESV_TEMP); - return true; - } - else if (imlInstruction->op_conditional_r_s32.crBitIndex == CR_BIT_EQ) - { - x64Gen_cmovcc_reg64Low32_reg64Low32(x64GenContext, imlInstruction->op_conditional_r_s32.bitMustBeSet ? X86_CONDITION_EQUAL : X86_CONDITION_NOT_EQUAL, tempToRealRegister(imlInstruction->op_conditional_r_s32.registerIndex), REG_RESV_TEMP); - return true; - } - else if (imlInstruction->op_conditional_r_s32.crBitIndex == CR_BIT_GT) - { - x64Gen_cmovcc_reg64Low32_reg64Low32(x64GenContext, imlInstruction->op_conditional_r_s32.bitMustBeSet ? X86_CONDITION_SIGNED_GREATER : X86_CONDITION_SIGNED_LESS_EQUAL, tempToRealRegister(imlInstruction->op_conditional_r_s32.registerIndex), REG_RESV_TEMP); - return true; - } - } - else if (x64GenContext->activeCRState == PPCREC_CR_STATE_TYPE_LOGICAL) - { - if (imlInstruction->op_conditional_r_s32.crBitIndex == CR_BIT_LT) - { - x64Gen_cmovcc_reg64Low32_reg64Low32(x64GenContext, imlInstruction->op_conditional_r_s32.bitMustBeSet ? X86_CONDITION_SIGN : X86_CONDITION_NOT_SIGN, tempToRealRegister(imlInstruction->op_conditional_r_s32.registerIndex), REG_RESV_TEMP); - return true; - } - else if (imlInstruction->op_conditional_r_s32.crBitIndex == CR_BIT_EQ) - { - x64Gen_cmovcc_reg64Low32_reg64Low32(x64GenContext, imlInstruction->op_conditional_r_s32.bitMustBeSet ? X86_CONDITION_EQUAL : X86_CONDITION_NOT_EQUAL, tempToRealRegister(imlInstruction->op_conditional_r_s32.registerIndex), REG_RESV_TEMP); - return true; - } - else if (imlInstruction->op_conditional_r_s32.crBitIndex == CR_BIT_GT) - { - x64Gen_cmovcc_reg64Low32_reg64Low32(x64GenContext, imlInstruction->op_conditional_r_s32.bitMustBeSet ? X86_CONDITION_SIGNED_GREATER : X86_CONDITION_SIGNED_LESS_EQUAL, tempToRealRegister(imlInstruction->op_conditional_r_s32.registerIndex), REG_RESV_TEMP); - return true; - } - } - } - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - x64Gen_bt_mem8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, cr) + crBitIndex * sizeof(uint8), 0); - if (imlInstruction->op_conditional_r_s32.bitMustBeSet) - x64Gen_cmovcc_reg64Low32_reg64Low32(x64GenContext, X86_CONDITION_CARRY, tempToRealRegister(imlInstruction->op_conditional_r_s32.registerIndex), REG_RESV_TEMP); - else - x64Gen_cmovcc_reg64Low32_reg64Low32(x64GenContext, X86_CONDITION_NOT_CARRY, tempToRealRegister(imlInstruction->op_conditional_r_s32.registerIndex), REG_RESV_TEMP); - return true; - } - return false; -} - -bool PPCRecompilerX64Gen_imlInstruction_r_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) -{ - if( imlInstruction->operation == PPCREC_IML_OP_ADD || imlInstruction->operation == PPCREC_IML_OP_ADD_UPDATE_CARRY || imlInstruction->operation == PPCREC_IML_OP_ADD_CARRY_UPDATE_CARRY ) - { - // registerResult = registerOperand1 + registerOperand2 - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - sint32 rRegResult = tempToRealRegister(imlInstruction->op_r_r_r.registerResult); - sint32 rRegOperand1 = tempToRealRegister(imlInstruction->op_r_r_r.registerA); - sint32 rRegOperand2 = tempToRealRegister(imlInstruction->op_r_r_r.registerB); - - bool addCarry = imlInstruction->operation == PPCREC_IML_OP_ADD_CARRY_UPDATE_CARRY; - if( (rRegResult == rRegOperand1) || (rRegResult == rRegOperand2) ) - { - // be careful not to overwrite the operand before we use it - if( rRegResult == rRegOperand1 ) - { - if( addCarry ) - { - x64Gen_bt_mem8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 0); - x64Gen_adc_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); - } - else - x64Gen_add_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); - } - else - { - if( addCarry ) - { - x64Gen_bt_mem8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 0); - x64Gen_adc_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand1); - } - else - x64Gen_add_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand1); - } - } - else - { - // copy operand1 to destination register before doing addition - x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, rRegOperand1); - // add operand2 - if( addCarry ) - { - x64Gen_bt_mem8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 0); - x64Gen_adc_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); - } - else - x64Gen_add_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); - } - // update carry - if( imlInstruction->operation == PPCREC_IML_OP_ADD_UPDATE_CARRY || imlInstruction->operation == PPCREC_IML_OP_ADD_CARRY_UPDATE_CARRY ) - { - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_CARRY, REG_RSP, offsetof(PPCInterpreter_t, xer_ca)); - } - // set cr bits if enabled - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - if( imlInstruction->crMode != PPCREC_CR_MODE_LOGICAL ) - { - assert_dbg(); - } - sint32 crRegister = imlInstruction->crRegister; - PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); - return true; - } - } - else if( imlInstruction->operation == PPCREC_IML_OP_SUB ) - { - // registerResult = registerOperand1 - registerOperand2 - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - sint32 rRegResult = tempToRealRegister(imlInstruction->op_r_r_r.registerResult); - sint32 rRegOperand1 = tempToRealRegister(imlInstruction->op_r_r_r.registerA); - sint32 rRegOperand2 = tempToRealRegister(imlInstruction->op_r_r_r.registerB); - if( rRegOperand1 == rRegOperand2 ) - { - // result = operand1 - operand1 -> 0 - x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegResult); - } - else if( rRegResult == rRegOperand1 ) - { - // result = result - operand2 - x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); - } - else if ( rRegResult == rRegOperand2 ) - { - // result = operand1 - result - // NEG result - x64Gen_neg_reg64Low32(x64GenContext, rRegResult); - // ADD result, operand1 - x64Gen_add_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand1); - } - else - { - // copy operand1 to destination register before doing addition - x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, rRegOperand1); - // sub operand2 - x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); - } - // set cr bits if enabled - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - if( imlInstruction->crMode != PPCREC_CR_MODE_LOGICAL ) - { - assert_dbg(); - } - sint32 crRegister = imlInstruction->crRegister; - PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); - return true; - } - } - else if( imlInstruction->operation == PPCREC_IML_OP_SUB_CARRY_UPDATE_CARRY ) - { - // registerResult = registerOperand1 - registerOperand2 + carry - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - sint32 rRegResult = tempToRealRegister(imlInstruction->op_r_r_r.registerResult); - sint32 rRegOperand1 = tempToRealRegister(imlInstruction->op_r_r_r.registerA); - sint32 rRegOperand2 = tempToRealRegister(imlInstruction->op_r_r_r.registerB); - if( rRegOperand1 == rRegOperand2 ) - { - // copy xer_ca to eflags carry - x64Gen_bt_mem8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 0); - x64Gen_cmc(x64GenContext); - // result = operand1 - operand1 -> 0 - x64Gen_sbb_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegResult); - } - else if( rRegResult == rRegOperand1 ) - { - // copy inverted xer_ca to eflags carry - x64Gen_bt_mem8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 0); - x64Gen_cmc(x64GenContext); - // result = result - operand2 - x64Gen_sbb_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); - } - else if ( rRegResult == rRegOperand2 ) - { - // result = operand1 - result - // NOT result - x64Gen_not_reg64Low32(x64GenContext, rRegResult); - // copy xer_ca to eflags carry - x64Gen_bt_mem8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 0); - // ADC result, operand1 - x64Gen_adc_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand1); - } - else - { - // copy operand1 to destination register before doing addition - x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, rRegOperand1); - // copy xer_ca to eflags carry - x64Gen_bt_mem8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 0); - x64Gen_cmc(x64GenContext); - // sub operand2 - x64Gen_sbb_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); - } - // update carry flag (todo: is this actually correct in all cases?) - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_CARRY, REG_RSP, offsetof(PPCInterpreter_t, xer_ca)); - // update cr0 if requested - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - if( imlInstruction->crMode != PPCREC_CR_MODE_LOGICAL ) - assert_dbg(); - x64Gen_test_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegResult); - PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); - } - } - else if( imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_SIGNED ) - { - // registerResult = registerOperand1 * registerOperand2 - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - sint32 rRegResult = tempToRealRegister(imlInstruction->op_r_r_r.registerResult); - sint32 rRegOperand1 = tempToRealRegister(imlInstruction->op_r_r_r.registerA); - sint32 rRegOperand2 = tempToRealRegister(imlInstruction->op_r_r_r.registerB); - if( (rRegResult == rRegOperand1) || (rRegResult == rRegOperand2) ) - { - // be careful not to overwrite the operand before we use it - if( rRegResult == rRegOperand1 ) - x64Gen_imul_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); - else - x64Gen_imul_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand1); - } - else - { - // copy operand1 to destination register before doing multiplication - x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, rRegOperand1); - // add operand2 - x64Gen_imul_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand2); - } - // set cr bits if enabled - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - if( imlInstruction->crMode != PPCREC_CR_MODE_LOGICAL ) - { - assert_dbg(); - } - // since IMUL instruction leaves relevant flags undefined, we have to use another TEST instruction to get the correct results - x64Gen_test_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegResult); - PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); - } - } - else if( imlInstruction->operation == PPCREC_IML_OP_SUBFC ) - { - // registerResult = registerOperand2(rB) - registerOperand1(rA) - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - // updates carry flag - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - return false; - } - sint32 rRegResult = tempToRealRegister(imlInstruction->op_r_r_r.registerResult); - sint32 rRegOperandA = tempToRealRegister(imlInstruction->op_r_r_r.registerA); - sint32 rRegOperandB = tempToRealRegister(imlInstruction->op_r_r_r.registerB); - // update carry flag - // carry flag is detected this way: - //if ((~a+b) < a) { - // return true; - //} - //if ((~a+b+1) < 1) { - // return true; - //} - // set carry to zero - x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 0); - // ((~a+b)<~a) == true -> ca = 1 - x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperandA); - x64Gen_not_reg64Low32(x64GenContext, REG_RESV_TEMP); - x64Gen_add_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, rRegOperandB); - x64Gen_not_reg64Low32(x64GenContext, rRegOperandA); - x64Gen_cmp_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, rRegOperandA); - x64Gen_not_reg64Low32(x64GenContext, rRegOperandA); - sint32 jumpInstructionOffset1 = x64GenContext->codeBufferIndex; - x64Gen_jmpc_near(x64GenContext, X86_CONDITION_UNSIGNED_ABOVE_EQUAL, 0); - // reset carry flag + jump destination afterwards - x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 1); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset1, x64GenContext->codeBufferIndex); - // OR ((~a+b+1)<1) == true -> ca = 1 - x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperandA); - // todo: Optimize by reusing result in REG_RESV_TEMP from above and only add 1 - x64Gen_not_reg64Low32(x64GenContext, REG_RESV_TEMP); - x64Gen_add_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, rRegOperandB); - x64Gen_add_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 1); - x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 1); - sint32 jumpInstructionOffset2 = x64GenContext->codeBufferIndex; - x64Gen_jmpc_near(x64GenContext, X86_CONDITION_UNSIGNED_ABOVE_EQUAL, 0); - // reset carry flag + jump destination afterwards - x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 1); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset2, x64GenContext->codeBufferIndex); - // do subtraction - if( rRegOperandB == rRegOperandA ) - { - // result = operandA - operandA -> 0 - x64Gen_xor_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegResult); - } - else if( rRegResult == rRegOperandB ) - { - // result = result - operandA - x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperandA); - } - else if ( rRegResult == rRegOperandA ) - { - // result = operandB - result - // NEG result - x64Gen_neg_reg64Low32(x64GenContext, rRegResult); - // ADD result, operandB - x64Gen_add_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperandB); - } - else - { - // copy operand1 to destination register before doing addition - x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, rRegOperandB); - // sub operand2 - x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperandA); - } - } - else if( imlInstruction->operation == PPCREC_IML_OP_SLW || imlInstruction->operation == PPCREC_IML_OP_SRW ) - { - // registerResult = registerOperand1(rA) >> registerOperand2(rB) (up to 63 bits) - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - sint32 rRegResult = tempToRealRegister(imlInstruction->op_r_r_r.registerResult); - sint32 rRegOperand1 = tempToRealRegister(imlInstruction->op_r_r_r.registerA); - sint32 rRegOperand2 = tempToRealRegister(imlInstruction->op_r_r_r.registerB); - - if (g_CPUFeatures.x86.bmi2 && imlInstruction->operation == PPCREC_IML_OP_SRW) - { - // use BMI2 SHRX if available - x64Gen_shrx_reg64_reg64_reg64(x64GenContext, rRegResult, rRegOperand1, rRegOperand2); - } - else if (g_CPUFeatures.x86.bmi2 && imlInstruction->operation == PPCREC_IML_OP_SLW) - { - // use BMI2 SHLX if available - x64Gen_shlx_reg64_reg64_reg64(x64GenContext, rRegResult, rRegOperand1, rRegOperand2); - x64Gen_and_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegResult); // trim result to 32bit - } - else - { - // lazy and slow way to do shift by register without relying on ECX/CL or BMI2 - x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperand1); - for (sint32 b = 0; b < 6; b++) - { - x64Gen_test_reg64Low32_imm32(x64GenContext, rRegOperand2, (1 << b)); - sint32 jumpInstructionOffset = x64GenContext->codeBufferIndex; - x64Gen_jmpc_near(x64GenContext, X86_CONDITION_EQUAL, 0); // jump if bit not set - if (b == 5) - { - x64Gen_xor_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, REG_RESV_TEMP); - } - else - { - if (imlInstruction->operation == PPCREC_IML_OP_SLW) - x64Gen_shl_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, (1 << b)); - else - x64Gen_shr_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, (1 << b)); - } - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset, x64GenContext->codeBufferIndex); - } - x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, REG_RESV_TEMP); - } - // set cr bits if enabled - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - if( imlInstruction->crMode != PPCREC_CR_MODE_LOGICAL ) - { - assert_dbg(); - } - x64Gen_test_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegResult); - PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); - } - } - else if( imlInstruction->operation == PPCREC_IML_OP_LEFT_ROTATE ) - { - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - sint32 rRegResult = tempToRealRegister(imlInstruction->op_r_r_r.registerResult); - sint32 rRegOperand1 = tempToRealRegister(imlInstruction->op_r_r_r.registerA); - sint32 rRegOperand2 = tempToRealRegister(imlInstruction->op_r_r_r.registerB); - // todo: Use BMI2 rotate if available - // check if CL/ECX/RCX is available - if( rRegResult != REG_RCX && rRegOperand1 != REG_RCX && rRegOperand2 != REG_RCX ) - { - // swap operand 2 with RCX - x64Gen_xchg_reg64_reg64(x64GenContext, REG_RCX, rRegOperand2); - // move operand 1 to temp register - x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperand1); - // rotate - x64Gen_rol_reg64Low32_cl(x64GenContext, REG_RESV_TEMP); - // undo swap operand 2 with RCX - x64Gen_xchg_reg64_reg64(x64GenContext, REG_RCX, rRegOperand2); - // copy to result register - x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, REG_RESV_TEMP); - } - else - { - x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperand1); - // lazy and slow way to do shift by register without relying on ECX/CL - for(sint32 b=0; b<5; b++) - { - x64Gen_test_reg64Low32_imm32(x64GenContext, rRegOperand2, (1<<b)); - sint32 jumpInstructionOffset = x64GenContext->codeBufferIndex; - x64Gen_jmpc_near(x64GenContext, X86_CONDITION_EQUAL, 0); // jump if bit not set - x64Gen_rol_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, (1<<b)); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset, x64GenContext->codeBufferIndex); - } - x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, REG_RESV_TEMP); - } - // set cr bits if enabled - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - if( imlInstruction->crMode != PPCREC_CR_MODE_LOGICAL ) - { - assert_dbg(); - } - x64Gen_test_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegResult); - PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); - } - } - else if( imlInstruction->operation == PPCREC_IML_OP_SRAW ) - { - // registerResult = (sint32)registerOperand1(rA) >> (sint32)registerOperand2(rB) (up to 63 bits) - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - sint32 rRegResult = tempToRealRegister(imlInstruction->op_r_r_r.registerResult); - sint32 rRegOperand1 = tempToRealRegister(imlInstruction->op_r_r_r.registerA); - sint32 rRegOperand2 = tempToRealRegister(imlInstruction->op_r_r_r.registerB); - // save cr - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - return false; - } - // todo: Use BMI instructions if available? - // MOV registerResult, registerOperand (if different) - x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperand1); - // reset carry - x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 0); - // we use the same shift by register approach as in SLW/SRW, but we have to differentiate by signed/unsigned shift since it influences how the carry flag is set - x64Gen_test_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 0x80000000); - sint32 jumpInstructionJumpToSignedShift = x64GenContext->codeBufferIndex; - x64Gen_jmpc_far(x64GenContext, X86_CONDITION_NOT_EQUAL, 0); - //sint32 jumpInstructionJumpToEnd = x64GenContext->codeBufferIndex; - //x64Gen_jmpc(x64GenContext, X86_CONDITION_EQUAL, 0); - // unsigned shift (MSB of input register is not set) - for(sint32 b=0; b<6; b++) - { - x64Gen_test_reg64Low32_imm32(x64GenContext, rRegOperand2, (1<<b)); - sint32 jumpInstructionOffset = x64GenContext->codeBufferIndex; - x64Gen_jmpc_near(x64GenContext, X86_CONDITION_EQUAL, 0); // jump if bit not set - if( b == 5 ) - { - x64Gen_sar_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, (1<<b)/2); - x64Gen_sar_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, (1<<b)/2); - } - else - { - x64Gen_sar_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, (1<<b)); - } - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset, x64GenContext->codeBufferIndex); - } - sint32 jumpInstructionJumpToEnd = x64GenContext->codeBufferIndex; - x64Gen_jmpc_far(x64GenContext, X86_CONDITION_NONE, 0); - // signed shift - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionJumpToSignedShift, x64GenContext->codeBufferIndex); - for(sint32 b=0; b<6; b++) - { - // check if we need to shift by (1<<bit) - x64Gen_test_reg64Low32_imm32(x64GenContext, rRegOperand2, (1<<b)); - sint32 jumpInstructionOffset = x64GenContext->codeBufferIndex; - x64Gen_jmpc_near(x64GenContext, X86_CONDITION_EQUAL, 0); // jump if bit not set - // set ca if any non-zero bit is shifted out - x64Gen_test_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, (1<<(1<<b))-1); - sint32 jumpInstructionJumpToAfterCa = x64GenContext->codeBufferIndex; - x64Gen_jmpc_near(x64GenContext, X86_CONDITION_EQUAL, 0); // jump if no bit is set - x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 1); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionJumpToAfterCa, x64GenContext->codeBufferIndex); - // arithmetic shift - if( b == 5 ) - { - // copy sign bit into all bits - x64Gen_sar_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, (1<<b)/2); - x64Gen_sar_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, (1<<b)/2); - } - else - { - x64Gen_sar_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, (1<<b)); - } - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset, x64GenContext->codeBufferIndex); - } - // end - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionJumpToEnd, x64GenContext->codeBufferIndex); - x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, REG_RESV_TEMP); - // update CR if requested - // todo - } - else if( imlInstruction->operation == PPCREC_IML_OP_DIVIDE_SIGNED || imlInstruction->operation == PPCREC_IML_OP_DIVIDE_UNSIGNED ) - { - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - sint32 rRegResult = tempToRealRegister(imlInstruction->op_r_r_r.registerResult); - sint32 rRegOperand1 = tempToRealRegister(imlInstruction->op_r_r_r.registerA); - sint32 rRegOperand2 = tempToRealRegister(imlInstruction->op_r_r_r.registerB); - - x64Emit_mov_mem32_reg32(x64GenContext, REG_RSP, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[0]), REG_EAX); - x64Emit_mov_mem32_reg32(x64GenContext, REG_RSP, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[1]), REG_EDX); - // mov operand 2 to temp register - x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperand2); - // mov operand1 to EAX - x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, REG_EAX, rRegOperand1); - // sign or zero extend EAX to EDX:EAX based on division sign mode - if( imlInstruction->operation == PPCREC_IML_OP_DIVIDE_SIGNED ) - x64Gen_cdq(x64GenContext); - else - x64Gen_xor_reg64Low32_reg64Low32(x64GenContext, REG_EDX, REG_EDX); - // make sure we avoid division by zero - x64Gen_test_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, REG_RESV_TEMP); - x64Gen_jmpc_near(x64GenContext, X86_CONDITION_EQUAL, 3); - // divide - if( imlInstruction->operation == PPCREC_IML_OP_DIVIDE_SIGNED ) - x64Gen_idiv_reg64Low32(x64GenContext, REG_RESV_TEMP); - else - x64Gen_div_reg64Low32(x64GenContext, REG_RESV_TEMP); - // result of division is now stored in EAX, move it to result register - if( rRegResult != REG_EAX ) - x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, REG_EAX); - // restore EAX / EDX - if( rRegResult != REG_RAX ) - x64Emit_mov_reg64_mem32(x64GenContext, REG_EAX, REG_RSP, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[0])); - if( rRegResult != REG_RDX ) - x64Emit_mov_reg64_mem32(x64GenContext, REG_EDX, REG_RSP, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[1])); - // set cr bits if requested - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - if( imlInstruction->crMode != PPCREC_CR_MODE_ARITHMETIC ) - { - assert_dbg(); - } - x64Gen_test_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegResult); - PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); - } - } - else if( imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_HIGH_SIGNED || imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_HIGH_UNSIGNED ) - { - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - sint32 rRegResult = tempToRealRegister(imlInstruction->op_r_r_r.registerResult); - sint32 rRegOperand1 = tempToRealRegister(imlInstruction->op_r_r_r.registerA); - sint32 rRegOperand2 = tempToRealRegister(imlInstruction->op_r_r_r.registerB); - - x64Emit_mov_mem32_reg32(x64GenContext, REG_RSP, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[0]), REG_EAX); - x64Emit_mov_mem32_reg32(x64GenContext, REG_RSP, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[1]), REG_EDX); - // mov operand 2 to temp register - x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperand2); - // mov operand1 to EAX - x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, REG_EAX, rRegOperand1); - if( imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_HIGH_SIGNED ) - { - // zero extend EAX to EDX:EAX - x64Gen_xor_reg64Low32_reg64Low32(x64GenContext, REG_EDX, REG_EDX); - } - else - { - // sign extend EAX to EDX:EAX - x64Gen_cdq(x64GenContext); - } - // multiply - if( imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_HIGH_SIGNED ) - x64Gen_imul_reg64Low32(x64GenContext, REG_RESV_TEMP); - else - x64Gen_mul_reg64Low32(x64GenContext, REG_RESV_TEMP); - // result of multiplication is now stored in EDX:EAX, move it to result register - if( rRegResult != REG_EDX ) - x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, REG_EDX); - // restore EAX / EDX - if( rRegResult != REG_RAX ) - x64Emit_mov_reg64_mem32(x64GenContext, REG_EAX, REG_RSP, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[0])); - if( rRegResult != REG_RDX ) - x64Emit_mov_reg64_mem32(x64GenContext, REG_EDX, REG_RSP, (uint32)offsetof(PPCInterpreter_t, temporaryGPR[1])); - // set cr bits if requested - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - if( imlInstruction->crMode != PPCREC_CR_MODE_LOGICAL ) - { - assert_dbg(); - } - x64Gen_test_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegResult); - PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); - } - } - else if( imlInstruction->operation == PPCREC_IML_OP_ORC ) - { - // registerResult = registerOperand1 | ~registerOperand2 - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - sint32 rRegResult = tempToRealRegister(imlInstruction->op_r_r_r.registerResult); - sint32 rRegOperand1 = tempToRealRegister(imlInstruction->op_r_r_r.registerA); - sint32 rRegOperand2 = tempToRealRegister(imlInstruction->op_r_r_r.registerB); - - x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperand2); - x64Gen_not_reg64Low32(x64GenContext, REG_RESV_TEMP); - if( rRegResult != rRegOperand1 ) - x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, rRegResult, rRegOperand1); - x64Gen_or_reg64Low32_reg64Low32(x64GenContext, rRegResult, REG_RESV_TEMP); - - // set cr bits if enabled - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - if( imlInstruction->crMode != PPCREC_CR_MODE_LOGICAL ) - { - assert_dbg(); - } - PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); - return true; - } - } - else - { - debug_printf("PPCRecompilerX64Gen_imlInstruction_r_r_r(): Unsupported operation 0x%x\n", imlInstruction->operation); - return false; - } - return true; -} - -bool PPCRecompilerX64Gen_imlInstruction_r_r_s32(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) -{ - if( imlInstruction->operation == PPCREC_IML_OP_ADD ) - { - // registerResult = registerOperand + immS32 - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); - sint32 rRegResult = tempToRealRegister(imlInstruction->op_r_r_s32.registerResult); - sint32 rRegOperand = tempToRealRegister(imlInstruction->op_r_r_s32.registerA); - uint32 immU32 = (uint32)imlInstruction->op_r_r_s32.immS32; - if( rRegResult != rRegOperand ) - { - // copy value to destination register before doing addition - x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, rRegOperand); - } - x64Gen_add_reg64Low32_imm32(x64GenContext, rRegResult, (uint32)immU32); - } - else if( imlInstruction->operation == PPCREC_IML_OP_ADD_UPDATE_CARRY ) - { - // registerResult = registerOperand + immS32 - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - sint32 rRegResult = tempToRealRegister(imlInstruction->op_r_r_s32.registerResult); - sint32 rRegOperand = tempToRealRegister(imlInstruction->op_r_r_s32.registerA); - uint32 immU32 = (uint32)imlInstruction->op_r_r_s32.immS32; - if( rRegResult != rRegOperand ) - { - // copy value to destination register before doing addition - x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, rRegOperand); - } - x64Gen_add_reg64Low32_imm32(x64GenContext, rRegResult, (uint32)immU32); - // update carry flag - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_CARRY, REG_RSP, offsetof(PPCInterpreter_t, xer_ca)); - // set cr bits if enabled - if( imlInstruction->crRegister != PPC_REC_INVALID_REGISTER ) - { - if( imlInstruction->crMode != PPCREC_CR_MODE_LOGICAL ) - { - assert_dbg(); - } - sint32 crRegister = imlInstruction->crRegister; - //x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_SIGN, REG_RSP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_LT)); - //x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_SIGNED_GREATER, REG_RSP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_GT)); - //x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_EQUAL, REG_RSP, offsetof(PPCInterpreter_t, cr)+sizeof(uint8)*(crRegister*4+PPCREC_CR_BIT_EQ)); - PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); - } - } - else if( imlInstruction->operation == PPCREC_IML_OP_SUBFC ) - { - // registerResult = immS32 - registerOperand - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); - sint32 rRegResult = tempToRealRegister(imlInstruction->op_r_r_s32.registerResult); - sint32 rRegOperand = tempToRealRegister(imlInstruction->op_r_r_s32.registerA); - sint32 immS32 = (sint32)imlInstruction->op_r_r_s32.immS32; - if( rRegResult != rRegOperand ) - { - // copy value to destination register before doing addition - x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, rRegOperand); - } - // set carry to zero - x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 0); - // ((~a+b)<~a) == true -> ca = 1 - x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperand); - x64Gen_not_reg64Low32(x64GenContext, REG_RESV_TEMP); - x64Gen_add_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, (uint32)immS32); - x64Gen_not_reg64Low32(x64GenContext, rRegOperand); - x64Gen_cmp_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, rRegOperand); - x64Gen_not_reg64Low32(x64GenContext, rRegOperand); - sint32 jumpInstructionOffset1 = x64GenContext->codeBufferIndex; - x64Gen_jmpc_far(x64GenContext, X86_CONDITION_UNSIGNED_ABOVE_EQUAL, 0); - // reset carry flag + jump destination afterwards - x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 1); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset1, x64GenContext->codeBufferIndex); - // OR ((~a+b+1)<1) == true -> ca = 1 - x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, rRegOperand); - // todo: Optimize by reusing result in REG_RESV_TEMP from above and only add 1 - x64Gen_not_reg64Low32(x64GenContext, REG_RESV_TEMP); - x64Gen_add_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, (uint32)immS32); - x64Gen_add_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 1); - x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 1); - sint32 jumpInstructionOffset2 = x64GenContext->codeBufferIndex; - x64Gen_jmpc_far(x64GenContext, X86_CONDITION_UNSIGNED_ABOVE_EQUAL, 0); - // reset carry flag + jump destination afterwards - x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 1); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset2, x64GenContext->codeBufferIndex); - // do actual computation of value, note: a - b is equivalent to a + ~b + 1 - x64Gen_not_reg64Low32(x64GenContext, rRegResult); - x64Gen_add_reg64Low32_imm32(x64GenContext, rRegResult, (uint32)immS32 + 1); - } - else if( imlInstruction->operation == PPCREC_IML_OP_RLWIMI ) - { - // registerResult = ((registerResult<<<SH)&mask) | (registerOperand&~mask) - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - uint32 vImm = (uint32)imlInstruction->op_r_r_s32.immS32; - uint32 mb = (vImm>>0)&0xFF; - uint32 me = (vImm>>8)&0xFF; - uint32 sh = (vImm>>16)&0xFF; - uint32 mask = ppc_mask(mb, me); - // save cr - cemu_assert_debug(imlInstruction->crRegister == PPC_REC_INVALID_REGISTER); - // copy rS to temporary register - x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, tempToRealRegister(imlInstruction->op_r_r_s32.registerA)); - // rotate destination register - if( sh ) - x64Gen_rol_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, (uint8)sh&0x1F); - // AND destination register with inverted mask - x64Gen_and_reg64Low32_imm32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r_s32.registerResult), ~mask); - // AND temporary rS register with mask - x64Gen_and_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, mask); - // OR result with temporary - x64Gen_or_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r_s32.registerResult), REG_RESV_TEMP); - } - else if( imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_SIGNED ) - { - // registerResult = registerOperand * immS32 - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - sint32 rRegResult = tempToRealRegister(imlInstruction->op_r_r_s32.registerResult); - sint32 rRegOperand = tempToRealRegister(imlInstruction->op_r_r_s32.registerA); - sint32 immS32 = (uint32)imlInstruction->op_r_r_s32.immS32; - x64Gen_mov_reg64_imm64(x64GenContext, REG_RESV_TEMP, (sint64)immS32); // todo: Optimize - if( rRegResult != rRegOperand ) - x64Gen_mov_reg64_reg64(x64GenContext, rRegResult, rRegOperand); - x64Gen_imul_reg64Low32_reg64Low32(x64GenContext, rRegResult, REG_RESV_TEMP); - } - else if( imlInstruction->operation == PPCREC_IML_OP_SRAW ) - { - // registerResult = registerOperand>>SH and set xer ca flag - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - uint32 sh = (uint32)imlInstruction->op_r_r_s32.immS32; - // MOV registerResult, registerOperand (if different) - if( imlInstruction->op_r_r_s32.registerA != imlInstruction->op_r_r_s32.registerResult ) - x64Gen_mov_reg64_reg64(x64GenContext, tempToRealRegister(imlInstruction->op_r_r_s32.registerResult), tempToRealRegister(imlInstruction->op_r_r_s32.registerA)); - // todo: Detect if we don't need to update carry - // generic case - // TEST registerResult, (1<<(SH+1))-1 - uint32 caTestMask = 0; - if (sh >= 31) - caTestMask = 0x7FFFFFFF; - else - caTestMask = (1 << (sh)) - 1; - x64Gen_test_reg64Low32_imm32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r_s32.registerResult), caTestMask); - // SETNE/NZ [ESP+XER_CA] - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_NOT_EQUAL, REG_RSP, offsetof(PPCInterpreter_t, xer_ca)); - // SAR registerResult, SH - x64Gen_sar_reg64Low32_imm8(x64GenContext, tempToRealRegister(imlInstruction->op_r_r_s32.registerResult), sh); - // JNS <skipInstruction> (if sign not set) - sint32 jumpInstructionOffset = x64GenContext->codeBufferIndex; - x64Gen_jmpc_near(x64GenContext, X86_CONDITION_SIGN, 0); // todo: Can use 2-byte form of jump instruction here - // MOV BYTE [ESP+xer_ca], 0 - x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, xer_ca), 0); - // jump destination - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset, x64GenContext->codeBufferIndex); - // CR update - if (imlInstruction->crRegister != PPC_REC_INVALID_REGISTER) - { - sint32 crRegister = imlInstruction->crRegister; - x64Gen_test_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r_s32.registerResult), tempToRealRegister(imlInstruction->op_r_r_s32.registerResult)); - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_SIGN, REG_RSP, offsetof(PPCInterpreter_t, cr) + sizeof(uint8)*(crRegister * 4 + PPCREC_CR_BIT_LT)); - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_SIGNED_GREATER, REG_RSP, offsetof(PPCInterpreter_t, cr) + sizeof(uint8)*(crRegister * 4 + PPCREC_CR_BIT_GT)); - x64Gen_setcc_mem8(x64GenContext, X86_CONDITION_EQUAL, REG_RSP, offsetof(PPCInterpreter_t, cr) + sizeof(uint8)*(crRegister * 4 + PPCREC_CR_BIT_EQ)); - } - } - else if( imlInstruction->operation == PPCREC_IML_OP_LEFT_SHIFT || - imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT ) - { - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - // MOV registerResult, registerOperand (if different) - if( imlInstruction->op_r_r_s32.registerA != imlInstruction->op_r_r_s32.registerResult ) - x64Gen_mov_reg64_reg64(x64GenContext, tempToRealRegister(imlInstruction->op_r_r_s32.registerResult), tempToRealRegister(imlInstruction->op_r_r_s32.registerA)); - // Shift - if( imlInstruction->operation == PPCREC_IML_OP_LEFT_SHIFT ) - x64Gen_shl_reg64Low32_imm8(x64GenContext, tempToRealRegister(imlInstruction->op_r_r_s32.registerResult), imlInstruction->op_r_r_s32.immS32); - else - x64Gen_shr_reg64Low32_imm8(x64GenContext, tempToRealRegister(imlInstruction->op_r_r_s32.registerResult), imlInstruction->op_r_r_s32.immS32); - // CR update - if (imlInstruction->crRegister != PPC_REC_INVALID_REGISTER) - { - // since SHL/SHR only modifies the OF flag we need another TEST reg,reg here - x64Gen_test_reg64Low32_reg64Low32(x64GenContext, tempToRealRegister(imlInstruction->op_r_r_s32.registerResult), tempToRealRegister(imlInstruction->op_r_r_s32.registerResult)); - PPCRecompilerX64Gen_updateCRLogical(PPCRecFunction, ppcImlGenContext, x64GenContext, imlInstruction); - } - } - else - { - debug_printf("PPCRecompilerX64Gen_imlInstruction_r_r_s32(): Unsupported operation 0x%x\n", imlInstruction->operation); - return false; - } - return true; -} - -bool PPCRecompilerX64Gen_imlInstruction_conditionalJump(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlSegment_t* imlSegment, PPCRecImlInstruction_t* imlInstruction) -{ - if( imlInstruction->op_conditionalJump.condition == PPCREC_JUMP_CONDITION_NONE ) - { - // jump always - if (imlInstruction->op_conditionalJump.jumpAccordingToSegment) - { - // jump to segment - if (imlSegment->nextSegmentBranchTaken == nullptr) - assert_dbg(); - PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, X64_RELOC_LINK_TO_SEGMENT, imlSegment->nextSegmentBranchTaken); - x64Gen_jmp_imm32(x64GenContext, 0); - } - else - { - // deprecated (jump to jumpmark) - PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, X64_RELOC_LINK_TO_PPC, (void*)(size_t)imlInstruction->op_conditionalJump.jumpmarkAddress); - x64Gen_jmp_imm32(x64GenContext, 0); - } - } - else - { - if (imlInstruction->op_conditionalJump.jumpAccordingToSegment) - assert_dbg(); - // generate jump update marker - if( imlInstruction->op_conditionalJump.crRegisterIndex == PPCREC_CR_TEMPORARY || imlInstruction->op_conditionalJump.crRegisterIndex >= 8 ) - { - // temporary cr is used, which means we use the currently active eflags - PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, X64_RELOC_LINK_TO_PPC, (void*)(size_t)imlInstruction->op_conditionalJump.jumpmarkAddress); - sint32 condition = imlInstruction->op_conditionalJump.condition; - if( condition == PPCREC_JUMP_CONDITION_E ) - x64Gen_jmpc_far(x64GenContext, X86_CONDITION_EQUAL, 0); - else if( condition == PPCREC_JUMP_CONDITION_NE ) - x64Gen_jmpc_far(x64GenContext, X86_CONDITION_NOT_EQUAL, 0); - else - assert_dbg(); - } - else - { - uint8 crBitIndex = imlInstruction->op_conditionalJump.crRegisterIndex*4 + imlInstruction->op_conditionalJump.crBitIndex; - if (imlInstruction->op_conditionalJump.crRegisterIndex == x64GenContext->activeCRRegister ) - { - if (x64GenContext->activeCRState == PPCREC_CR_STATE_TYPE_UNSIGNED_ARITHMETIC) - { - if (imlInstruction->op_conditionalJump.crBitIndex == CR_BIT_LT) - { - PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, X64_RELOC_LINK_TO_PPC, (void*)(size_t)imlInstruction->op_conditionalJump.jumpmarkAddress); - x64Gen_jmpc_far(x64GenContext, imlInstruction->op_conditionalJump.bitMustBeSet ? X86_CONDITION_CARRY : X86_CONDITION_NOT_CARRY, 0); - return true; - } - else if (imlInstruction->op_conditionalJump.crBitIndex == CR_BIT_EQ) - { - PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, X64_RELOC_LINK_TO_PPC, (void*)(size_t)imlInstruction->op_conditionalJump.jumpmarkAddress); - x64Gen_jmpc_far(x64GenContext, imlInstruction->op_conditionalJump.bitMustBeSet ? X86_CONDITION_EQUAL : X86_CONDITION_NOT_EQUAL, 0); - return true; - } - else if (imlInstruction->op_conditionalJump.crBitIndex == CR_BIT_GT) - { - PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, X64_RELOC_LINK_TO_PPC, (void*)(size_t)imlInstruction->op_conditionalJump.jumpmarkAddress); - x64Gen_jmpc_far(x64GenContext, imlInstruction->op_conditionalJump.bitMustBeSet ? X86_CONDITION_UNSIGNED_ABOVE : X86_CONDITION_UNSIGNED_BELOW_EQUAL, 0); - return true; - } - } - else if (x64GenContext->activeCRState == PPCREC_CR_STATE_TYPE_SIGNED_ARITHMETIC) - { - if (imlInstruction->op_conditionalJump.crBitIndex == CR_BIT_LT) - { - PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, X64_RELOC_LINK_TO_PPC, (void*)(size_t)imlInstruction->op_conditionalJump.jumpmarkAddress); - x64Gen_jmpc_far(x64GenContext, imlInstruction->op_conditionalJump.bitMustBeSet ? X86_CONDITION_SIGNED_LESS : X86_CONDITION_SIGNED_GREATER_EQUAL, 0); - return true; - } - else if (imlInstruction->op_conditionalJump.crBitIndex == CR_BIT_EQ) - { - PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, X64_RELOC_LINK_TO_PPC, (void*)(size_t)imlInstruction->op_conditionalJump.jumpmarkAddress); - x64Gen_jmpc_far(x64GenContext, imlInstruction->op_conditionalJump.bitMustBeSet ? X86_CONDITION_EQUAL : X86_CONDITION_NOT_EQUAL, 0); - return true; - } - else if (imlInstruction->op_conditionalJump.crBitIndex == CR_BIT_GT) - { - PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, X64_RELOC_LINK_TO_PPC, (void*)(size_t)imlInstruction->op_conditionalJump.jumpmarkAddress); - x64Gen_jmpc_far(x64GenContext, imlInstruction->op_conditionalJump.bitMustBeSet ? X86_CONDITION_SIGNED_GREATER : X86_CONDITION_SIGNED_LESS_EQUAL, 0); - return true; - } - } - else if (x64GenContext->activeCRState == PPCREC_CR_STATE_TYPE_LOGICAL) - { - if (imlInstruction->op_conditionalJump.crBitIndex == CR_BIT_LT) - { - PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, X64_RELOC_LINK_TO_PPC, (void*)(size_t)imlInstruction->op_conditionalJump.jumpmarkAddress); - x64Gen_jmpc_far(x64GenContext, imlInstruction->op_conditionalJump.bitMustBeSet ? X86_CONDITION_SIGN : X86_CONDITION_NOT_SIGN, 0); - return true; - } - else if (imlInstruction->op_conditionalJump.crBitIndex == CR_BIT_EQ) - { - PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, X64_RELOC_LINK_TO_PPC, (void*)(size_t)imlInstruction->op_conditionalJump.jumpmarkAddress); - x64Gen_jmpc_far(x64GenContext, imlInstruction->op_conditionalJump.bitMustBeSet ? X86_CONDITION_EQUAL : X86_CONDITION_NOT_EQUAL, 0); - return true; - } - else if (imlInstruction->op_conditionalJump.crBitIndex == CR_BIT_GT) - { - PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, X64_RELOC_LINK_TO_PPC, (void*)(size_t)imlInstruction->op_conditionalJump.jumpmarkAddress); - x64Gen_jmpc_far(x64GenContext, imlInstruction->op_conditionalJump.bitMustBeSet ? X86_CONDITION_SIGNED_GREATER : X86_CONDITION_SIGNED_LESS_EQUAL, 0); - return true; - } - } - } - x64Gen_bt_mem8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, cr) + crBitIndex * sizeof(uint8), 0); - PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, X64_RELOC_LINK_TO_PPC, (void*)(size_t)imlInstruction->op_conditionalJump.jumpmarkAddress); - if( imlInstruction->op_conditionalJump.bitMustBeSet ) - { - x64Gen_jmpc_far(x64GenContext, X86_CONDITION_CARRY, 0); - } - else - { - x64Gen_jmpc_far(x64GenContext, X86_CONDITION_NOT_CARRY, 0); - } - } - } - return true; -} - -bool PPCRecompilerX64Gen_imlInstruction_conditionalJumpCycleCheck(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) -{ - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); - // some tests (all performed on a i7-4790K) - // 1) DEC [mem] + JNS has significantly worse performance than BT + JNC (probably due to additional memory write) - // 2) CMP [mem], 0 + JG has about equal (or slightly worse) performance than BT + JNC - - // BT - x64Gen_bt_mem8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, remainingCycles), 31); // check if negative - PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, X64_RELOC_LINK_TO_PPC, (void*)(size_t)imlInstruction->op_conditionalJump.jumpmarkAddress); - x64Gen_jmpc_far(x64GenContext, X86_CONDITION_NOT_CARRY, 0); - return true; -} - -/* -* PPC condition register operation -*/ -bool PPCRecompilerX64Gen_imlInstruction_cr(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) -{ - PPCRecompilerX64Gen_crConditionFlags_forget(PPCRecFunction, ppcImlGenContext, x64GenContext); // while these instruction do not directly affect eflags, they change the CR bit - if (imlInstruction->operation == PPCREC_IML_OP_CR_CLEAR) - { - // clear cr bit - x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, cr) + sizeof(uint8)*imlInstruction->op_cr.crD, 0); - return true; - } - else if (imlInstruction->operation == PPCREC_IML_OP_CR_SET) - { - // set cr bit - x64Gen_mov_mem8Reg64_imm8(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, cr) + sizeof(uint8)*imlInstruction->op_cr.crD, 1); - return true; - } - else if(imlInstruction->operation == PPCREC_IML_OP_CR_OR || imlInstruction->operation == PPCREC_IML_OP_CR_ORC || - imlInstruction->operation == PPCREC_IML_OP_CR_AND || imlInstruction->operation == PPCREC_IML_OP_CR_ANDC ) - { - x64Emit_movZX_reg64_mem8(x64GenContext, REG_RESV_TEMP, REG_RSP, offsetof(PPCInterpreter_t, cr) + sizeof(uint8)*imlInstruction->op_cr.crB); - if (imlInstruction->operation == PPCREC_IML_OP_CR_ORC || imlInstruction->operation == PPCREC_IML_OP_CR_ANDC) - { - return false; // untested - x64Gen_int3(x64GenContext); - x64Gen_xor_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 1); // complement - } - if(imlInstruction->operation == PPCREC_IML_OP_CR_OR || imlInstruction->operation == PPCREC_IML_OP_CR_ORC) - x64Gen_or_reg64Low8_mem8Reg64(x64GenContext, REG_RESV_TEMP, REG_RSP, offsetof(PPCInterpreter_t, cr) + sizeof(uint8)*imlInstruction->op_cr.crA); - else - x64Gen_and_reg64Low8_mem8Reg64(x64GenContext, REG_RESV_TEMP, REG_RSP, offsetof(PPCInterpreter_t, cr) + sizeof(uint8)*imlInstruction->op_cr.crA); - - x64Gen_mov_mem8Reg64_reg64Low8(x64GenContext, REG_RESV_TEMP, REG_RSP, offsetof(PPCInterpreter_t, cr) + sizeof(uint8)*imlInstruction->op_cr.crD); - - return true; - } - else - { - assert_dbg(); - } - return false; -} - - -void PPCRecompilerX64Gen_imlInstruction_ppcEnter(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) -{ - imlInstruction->op_ppcEnter.x64Offset = x64GenContext->codeBufferIndex; - // generate code - if( ppcImlGenContext->hasFPUInstruction ) - { - // old FPU unavailable code - //PPCRecompilerX86_crConditionFlags_saveBeforeOverwrite(PPCRecFunction, ppcImlGenContext, x64GenContext); - //// skip if FP bit in MSR is set - //// #define MSR_FP (1<<13) - //x64Gen_bt_mem8(x64GenContext, REG_ESP, offsetof(PPCInterpreter_t, msr), 13); - //uint32 jmpCodeOffset = x64GenContext->codeBufferIndex; - //x64Gen_jmpc(x64GenContext, X86_CONDITION_CARRY, 0); - //x64Gen_mov_reg32_imm32(x64GenContext, REG_EAX, imlInstruction->op_ppcEnter.ppcAddress&0x7FFFFFFF); - //PPCRecompilerX64Gen_rememberRelocatableOffset(x64GenContext, X86_RELOC_MAKE_RELATIVE); - //x64Gen_jmp_imm32(x64GenContext, (uint32)PPCRecompiler_recompilerCallEscapeAndCallFPUUnavailable); - //// patch jump - //*(uint32*)(x64GenContext->codeBuffer+jmpCodeOffset+2) = x64GenContext->codeBufferIndex-jmpCodeOffset-6; - } -} - -void PPCRecompilerX64Gen_imlInstruction_r_name(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) -{ - uint32 name = imlInstruction->op_r_name.name; - if( name >= PPCREC_NAME_R0 && name < PPCREC_NAME_R0+32 ) - { - x64Emit_mov_reg64_mem32(x64GenContext, tempToRealRegister(imlInstruction->op_r_name.registerIndex), REG_RSP, offsetof(PPCInterpreter_t, gpr)+sizeof(uint32)*(name-PPCREC_NAME_R0)); - } - else if( name >= PPCREC_NAME_SPR0 && name < PPCREC_NAME_SPR0+999 ) - { - sint32 sprIndex = (name - PPCREC_NAME_SPR0); - if (sprIndex == SPR_LR) - x64Emit_mov_reg64_mem32(x64GenContext, tempToRealRegister(imlInstruction->op_r_name.registerIndex), REG_RSP, offsetof(PPCInterpreter_t, spr.LR)); - else if (sprIndex == SPR_CTR) - x64Emit_mov_reg64_mem32(x64GenContext, tempToRealRegister(imlInstruction->op_r_name.registerIndex), REG_RSP, offsetof(PPCInterpreter_t, spr.CTR)); - else if (sprIndex == SPR_XER) - x64Emit_mov_reg64_mem32(x64GenContext, tempToRealRegister(imlInstruction->op_r_name.registerIndex), REG_RSP, offsetof(PPCInterpreter_t, spr.XER)); - else if (sprIndex >= SPR_UGQR0 && sprIndex <= SPR_UGQR7) - { - sint32 memOffset = offsetof(PPCInterpreter_t, spr.UGQR) + sizeof(PPCInterpreter_t::spr.UGQR[0]) * (sprIndex - SPR_UGQR0); - x64Emit_mov_reg64_mem32(x64GenContext, tempToRealRegister(imlInstruction->op_r_name.registerIndex), REG_RSP, memOffset); - } - else - assert_dbg(); - //x64Emit_mov_reg64_mem32(x64GenContext, tempToRealRegister(imlInstruction->op_r_name.registerIndex), REG_RSP, offsetof(PPCInterpreter_t, spr)+sizeof(uint32)*(name-PPCREC_NAME_SPR0)); - } - else - assert_dbg(); -} - -void PPCRecompilerX64Gen_imlInstruction_name_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, PPCRecImlInstruction_t* imlInstruction) -{ - uint32 name = imlInstruction->op_r_name.name; - if( name >= PPCREC_NAME_R0 && name < PPCREC_NAME_R0+32 ) - { - x64Emit_mov_mem32_reg64(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, gpr)+sizeof(uint32)*(name-PPCREC_NAME_R0), tempToRealRegister(imlInstruction->op_r_name.registerIndex)); - } - else if( name >= PPCREC_NAME_SPR0 && name < PPCREC_NAME_SPR0+999 ) - { - uint32 sprIndex = (name - PPCREC_NAME_SPR0); - if (sprIndex == SPR_LR) - x64Emit_mov_mem32_reg64(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, spr.LR), tempToRealRegister(imlInstruction->op_r_name.registerIndex)); - else if (sprIndex == SPR_CTR) - x64Emit_mov_mem32_reg64(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, spr.CTR), tempToRealRegister(imlInstruction->op_r_name.registerIndex)); - else if (sprIndex == SPR_XER) - x64Emit_mov_mem32_reg64(x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, spr.XER), tempToRealRegister(imlInstruction->op_r_name.registerIndex)); - else if (sprIndex >= SPR_UGQR0 && sprIndex <= SPR_UGQR7) - { - sint32 memOffset = offsetof(PPCInterpreter_t, spr.UGQR) + sizeof(PPCInterpreter_t::spr.UGQR[0]) * (sprIndex - SPR_UGQR0); - x64Emit_mov_mem32_reg64(x64GenContext, REG_RSP, memOffset, tempToRealRegister(imlInstruction->op_r_name.registerIndex)); - } - else - assert_dbg(); - } - else - assert_dbg(); -} - -uint8* codeMemoryBlock = nullptr; -sint32 codeMemoryBlockIndex = 0; -sint32 codeMemoryBlockSize = 0; - -std::mutex mtx_allocExecutableMemory; - -uint8* PPCRecompilerX86_allocateExecutableMemory(sint32 size) -{ - std::lock_guard<std::mutex> lck(mtx_allocExecutableMemory); - if( codeMemoryBlockIndex+size > codeMemoryBlockSize ) - { - // allocate new block - codeMemoryBlockSize = std::max(1024*1024*4, size+1024); // 4MB (or more if the function is larger than 4MB) - codeMemoryBlockIndex = 0; - codeMemoryBlock = (uint8*)MemMapper::AllocateMemory(nullptr, codeMemoryBlockSize, MemMapper::PAGE_PERMISSION::P_RWX); - } - uint8* codeMem = codeMemoryBlock + codeMemoryBlockIndex; - codeMemoryBlockIndex += size; - // pad to 4 byte alignment - while (codeMemoryBlockIndex & 3) - { - codeMemoryBlock[codeMemoryBlockIndex] = 0x90; - codeMemoryBlockIndex++; - } - return codeMem; -} - -void PPCRecompiler_dumpIML(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext); - -bool PPCRecompiler_generateX64Code(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext) -{ - x64GenContext_t x64GenContext = {0}; - x64GenContext.codeBufferSize = 1024; - x64GenContext.codeBuffer = (uint8*)malloc(x64GenContext.codeBufferSize); - x64GenContext.codeBufferIndex = 0; - x64GenContext.activeCRRegister = PPC_REC_INVALID_REGISTER; - - // generate iml instruction code - bool codeGenerationFailed = false; - for(sint32 s=0; s<ppcImlGenContext->segmentListCount; s++) - { - PPCRecImlSegment_t* imlSegment = ppcImlGenContext->segmentList[s]; - ppcImlGenContext->segmentList[s]->x64Offset = x64GenContext.codeBufferIndex; - for(sint32 i=0; i<imlSegment->imlListCount; i++) - { - PPCRecImlInstruction_t* imlInstruction = imlSegment->imlList+i; - - if( imlInstruction->type == PPCREC_IML_TYPE_R_NAME ) - { - PPCRecompilerX64Gen_imlInstruction_r_name(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); - } - else if( imlInstruction->type == PPCREC_IML_TYPE_NAME_R ) - { - PPCRecompilerX64Gen_imlInstruction_name_r(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); - } - else if( imlInstruction->type == PPCREC_IML_TYPE_R_R ) - { - if( PPCRecompilerX64Gen_imlInstruction_r_r(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction) == false ) - { - codeGenerationFailed = true; - } - } - else if (imlInstruction->type == PPCREC_IML_TYPE_R_S32) - { - if (PPCRecompilerX64Gen_imlInstruction_r_s32(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction) == false) - { - codeGenerationFailed = true; - } - } - else if (imlInstruction->type == PPCREC_IML_TYPE_CONDITIONAL_R_S32) - { - if (PPCRecompilerX64Gen_imlInstruction_conditional_r_s32(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction) == false) - { - codeGenerationFailed = true; - } - } - else if( imlInstruction->type == PPCREC_IML_TYPE_R_R_S32 ) - { - if( PPCRecompilerX64Gen_imlInstruction_r_r_s32(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction) == false ) - { - codeGenerationFailed = true; - } - } - else if( imlInstruction->type == PPCREC_IML_TYPE_R_R_R ) - { - if( PPCRecompilerX64Gen_imlInstruction_r_r_r(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction) == false ) - { - codeGenerationFailed = true; - } - } - else if( imlInstruction->type == PPCREC_IML_TYPE_CJUMP ) - { - if( PPCRecompilerX64Gen_imlInstruction_conditionalJump(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlSegment, imlInstruction) == false ) - { - codeGenerationFailed = true; - } - } - else if( imlInstruction->type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK ) - { - PPCRecompilerX64Gen_imlInstruction_conditionalJumpCycleCheck(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); - } - else if( imlInstruction->type == PPCREC_IML_TYPE_MACRO ) - { - if( PPCRecompilerX64Gen_imlInstruction_macro(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction) == false ) - { - codeGenerationFailed = true; - } - } - else if( imlInstruction->type == PPCREC_IML_TYPE_LOAD ) - { - if( PPCRecompilerX64Gen_imlInstruction_load(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, false) == false ) - { - codeGenerationFailed = true; - } - } - else if( imlInstruction->type == PPCREC_IML_TYPE_LOAD_INDEXED ) - { - if( PPCRecompilerX64Gen_imlInstruction_load(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, true) == false ) - { - codeGenerationFailed = true; - } - } - else if( imlInstruction->type == PPCREC_IML_TYPE_STORE ) - { - if( PPCRecompilerX64Gen_imlInstruction_store(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, false) == false ) - { - codeGenerationFailed = true; - } - } - else if( imlInstruction->type == PPCREC_IML_TYPE_STORE_INDEXED ) - { - if( PPCRecompilerX64Gen_imlInstruction_store(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, true) == false ) - { - codeGenerationFailed = true; - } - } - else if (imlInstruction->type == PPCREC_IML_TYPE_MEM2MEM) - { - PPCRecompilerX64Gen_imlInstruction_mem2mem(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); - } - else if( imlInstruction->type == PPCREC_IML_TYPE_CR ) - { - if( PPCRecompilerX64Gen_imlInstruction_cr(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction) == false ) - { - codeGenerationFailed = true; - } - } - else if( imlInstruction->type == PPCREC_IML_TYPE_JUMPMARK ) - { - // no op - } - else if( imlInstruction->type == PPCREC_IML_TYPE_NO_OP ) - { - // no op - } - else if( imlInstruction->type == PPCREC_IML_TYPE_PPC_ENTER ) - { - PPCRecompilerX64Gen_imlInstruction_ppcEnter(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); - } - else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R_NAME ) - { - PPCRecompilerX64Gen_imlInstruction_fpr_r_name(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); - } - else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_NAME_R ) - { - PPCRecompilerX64Gen_imlInstruction_fpr_name_r(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); - } - else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD ) - { - if( PPCRecompilerX64Gen_imlInstruction_fpr_load(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, false) == false ) - { - codeGenerationFailed = true; - } - } - else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED ) - { - if( PPCRecompilerX64Gen_imlInstruction_fpr_load(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, true) == false ) - { - codeGenerationFailed = true; - } - } - else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE ) - { - if( PPCRecompilerX64Gen_imlInstruction_fpr_store(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, false) == false ) - { - codeGenerationFailed = true; - } - } - else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE_INDEXED ) - { - if( PPCRecompilerX64Gen_imlInstruction_fpr_store(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction, true) == false ) - { - codeGenerationFailed = true; - } - } - else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R ) - { - PPCRecompilerX64Gen_imlInstruction_fpr_r_r(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); - } - else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R ) - { - PPCRecompilerX64Gen_imlInstruction_fpr_r_r_r(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); - } - else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R_R ) - { - PPCRecompilerX64Gen_imlInstruction_fpr_r_r_r_r(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); - } - else if( imlInstruction->type == PPCREC_IML_TYPE_FPR_R ) - { - PPCRecompilerX64Gen_imlInstruction_fpr_r(PPCRecFunction, ppcImlGenContext, &x64GenContext, imlInstruction); - } - else - { - debug_printf("PPCRecompiler_generateX64Code(): Unsupported iml type 0x%x\n", imlInstruction->type); - assert_dbg(); - } - } - } - // handle failed code generation - if( codeGenerationFailed ) - { - free(x64GenContext.codeBuffer); - if (x64GenContext.relocateOffsetTable) - free(x64GenContext.relocateOffsetTable); - return false; - } - // allocate executable memory - uint8* executableMemory = PPCRecompilerX86_allocateExecutableMemory(x64GenContext.codeBufferIndex); - size_t baseAddress = (size_t)executableMemory; - // fix relocs - for(sint32 i=0; i<x64GenContext.relocateOffsetTableCount; i++) - { - if( x64GenContext.relocateOffsetTable[i].type == X86_RELOC_MAKE_RELATIVE ) - { - assert_dbg(); // deprecated - } - else if(x64GenContext.relocateOffsetTable[i].type == X64_RELOC_LINK_TO_PPC || x64GenContext.relocateOffsetTable[i].type == X64_RELOC_LINK_TO_SEGMENT) - { - // if link to PPC, search for segment that starts with this offset - uint32 ppcOffset = (uint32)(size_t)x64GenContext.relocateOffsetTable[i].extraInfo; - uint32 x64Offset = 0xFFFFFFFF; - if (x64GenContext.relocateOffsetTable[i].type == X64_RELOC_LINK_TO_PPC) - { - for (sint32 s = 0; s < ppcImlGenContext->segmentListCount; s++) - { - if (ppcImlGenContext->segmentList[s]->isJumpDestination && ppcImlGenContext->segmentList[s]->jumpDestinationPPCAddress == ppcOffset) - { - x64Offset = ppcImlGenContext->segmentList[s]->x64Offset; - break; - } - } - if (x64Offset == 0xFFFFFFFF) - { - debug_printf("Recompiler could not resolve jump (function at 0x%08x)\n", PPCRecFunction->ppcAddress); - // todo: Cleanup - return false; - } - } - else - { - PPCRecImlSegment_t* destSegment = (PPCRecImlSegment_t*)x64GenContext.relocateOffsetTable[i].extraInfo; - x64Offset = destSegment->x64Offset; - } - uint32 relocBase = x64GenContext.relocateOffsetTable[i].offset; - uint8* relocInstruction = x64GenContext.codeBuffer+relocBase; - if( relocInstruction[0] == 0x0F && (relocInstruction[1] >= 0x80 && relocInstruction[1] <= 0x8F) ) - { - // Jcc relativeImm32 - sint32 distanceNearJump = (sint32)((baseAddress + x64Offset) - (baseAddress + relocBase + 2)); - if (distanceNearJump >= -128 && distanceNearJump < 127) // disabled - { - // convert to near Jcc - *(uint8*)(relocInstruction + 0) = (uint8)(relocInstruction[1]-0x80 + 0x70); - // patch offset - *(uint8*)(relocInstruction + 1) = (uint8)distanceNearJump; - // replace unused 4 bytes with NOP instruction - relocInstruction[2] = 0x0F; - relocInstruction[3] = 0x1F; - relocInstruction[4] = 0x40; - relocInstruction[5] = 0x00; - } - else - { - // patch offset - *(uint32*)(relocInstruction + 2) = (uint32)((baseAddress + x64Offset) - (baseAddress + relocBase + 6)); - } - } - else if( relocInstruction[0] == 0xE9 ) - { - // JMP relativeImm32 - *(uint32*)(relocInstruction+1) = (uint32)((baseAddress+x64Offset)-(baseAddress+relocBase+5)); - } - else - assert_dbg(); - } - else - { - assert_dbg(); - } - } - - // copy code to executable memory - memcpy(executableMemory, x64GenContext.codeBuffer, x64GenContext.codeBufferIndex); - free(x64GenContext.codeBuffer); - x64GenContext.codeBuffer = nullptr; - if (x64GenContext.relocateOffsetTable) - free(x64GenContext.relocateOffsetTable); - // set code - PPCRecFunction->x86Code = executableMemory; - PPCRecFunction->x86Size = x64GenContext.codeBufferIndex; - return true; -} - -void PPCRecompilerX64Gen_generateEnterRecompilerCode() -{ - x64GenContext_t x64GenContext = {0}; - x64GenContext.codeBufferSize = 1024; - x64GenContext.codeBuffer = (uint8*)malloc(x64GenContext.codeBufferSize); - x64GenContext.codeBufferIndex = 0; - x64GenContext.activeCRRegister = PPC_REC_INVALID_REGISTER; - - // start of recompiler entry function - x64Gen_push_reg64(&x64GenContext, REG_RAX); - x64Gen_push_reg64(&x64GenContext, REG_RCX); - x64Gen_push_reg64(&x64GenContext, REG_RDX); - x64Gen_push_reg64(&x64GenContext, REG_RBX); - x64Gen_push_reg64(&x64GenContext, REG_RBP); - x64Gen_push_reg64(&x64GenContext, REG_RDI); - x64Gen_push_reg64(&x64GenContext, REG_RSI); - x64Gen_push_reg64(&x64GenContext, REG_R8); - x64Gen_push_reg64(&x64GenContext, REG_R9); - x64Gen_push_reg64(&x64GenContext, REG_R10); - x64Gen_push_reg64(&x64GenContext, REG_R11); - x64Gen_push_reg64(&x64GenContext, REG_R12); - x64Gen_push_reg64(&x64GenContext, REG_R13); - x64Gen_push_reg64(&x64GenContext, REG_R14); - x64Gen_push_reg64(&x64GenContext, REG_R15); - - // 000000007775EF04 | E8 00 00 00 00 call +0x00 - x64Gen_writeU8(&x64GenContext, 0xE8); - x64Gen_writeU8(&x64GenContext, 0x00); - x64Gen_writeU8(&x64GenContext, 0x00); - x64Gen_writeU8(&x64GenContext, 0x00); - x64Gen_writeU8(&x64GenContext, 0x00); - //000000007775EF09 | 48 83 04 24 05 add qword ptr ss:[rsp],5 - x64Gen_writeU8(&x64GenContext, 0x48); - x64Gen_writeU8(&x64GenContext, 0x83); - x64Gen_writeU8(&x64GenContext, 0x04); - x64Gen_writeU8(&x64GenContext, 0x24); - uint32 jmpPatchOffset = x64GenContext.codeBufferIndex; - x64Gen_writeU8(&x64GenContext, 0); // skip the distance until after the JMP - x64Emit_mov_mem64_reg64(&x64GenContext, REG_RDX, offsetof(PPCInterpreter_t, rspTemp), REG_RSP); - - - // MOV RSP, RDX (ppc interpreter instance) - x64Gen_mov_reg64_reg64(&x64GenContext, REG_RSP, REG_RDX); - // MOV R15, ppcRecompilerInstanceData - x64Gen_mov_reg64_imm64(&x64GenContext, REG_R15, (uint64)ppcRecompilerInstanceData); - // MOV R13, memory_base - x64Gen_mov_reg64_imm64(&x64GenContext, REG_R13, (uint64)memory_base); - - //JMP recFunc - x64Gen_jmp_reg64(&x64GenContext, REG_RCX); // call argument 1 - - x64GenContext.codeBuffer[jmpPatchOffset] = (x64GenContext.codeBufferIndex-(jmpPatchOffset-4)); - - //recompilerExit1: - x64Gen_pop_reg64(&x64GenContext, REG_R15); - x64Gen_pop_reg64(&x64GenContext, REG_R14); - x64Gen_pop_reg64(&x64GenContext, REG_R13); - x64Gen_pop_reg64(&x64GenContext, REG_R12); - x64Gen_pop_reg64(&x64GenContext, REG_R11); - x64Gen_pop_reg64(&x64GenContext, REG_R10); - x64Gen_pop_reg64(&x64GenContext, REG_R9); - x64Gen_pop_reg64(&x64GenContext, REG_R8); - x64Gen_pop_reg64(&x64GenContext, REG_RSI); - x64Gen_pop_reg64(&x64GenContext, REG_RDI); - x64Gen_pop_reg64(&x64GenContext, REG_RBP); - x64Gen_pop_reg64(&x64GenContext, REG_RBX); - x64Gen_pop_reg64(&x64GenContext, REG_RDX); - x64Gen_pop_reg64(&x64GenContext, REG_RCX); - x64Gen_pop_reg64(&x64GenContext, REG_RAX); - // RET - x64Gen_ret(&x64GenContext); - - uint8* executableMemory = PPCRecompilerX86_allocateExecutableMemory(x64GenContext.codeBufferIndex); - // copy code to executable memory - memcpy(executableMemory, x64GenContext.codeBuffer, x64GenContext.codeBufferIndex); - free(x64GenContext.codeBuffer); - PPCRecompiler_enterRecompilerCode = (void ATTR_MS_ABI (*)(uint64,uint64))executableMemory; -} - - -void* PPCRecompilerX64Gen_generateLeaveRecompilerCode() -{ - x64GenContext_t x64GenContext = {0}; - x64GenContext.codeBufferSize = 128; - x64GenContext.codeBuffer = (uint8*)malloc(x64GenContext.codeBufferSize); - x64GenContext.codeBufferIndex = 0; - x64GenContext.activeCRRegister = PPC_REC_INVALID_REGISTER; - - // update instruction pointer - // LR is in EDX - x64Emit_mov_mem32_reg32(&x64GenContext, REG_RSP, offsetof(PPCInterpreter_t, instructionPointer), REG_EDX); - - // MOV RSP, [ppcRecompilerX64_rspTemp] - x64Emit_mov_reg64_mem64(&x64GenContext, REG_RSP, REG_RESV_HCPU, offsetof(PPCInterpreter_t, rspTemp)); - - // RET - x64Gen_ret(&x64GenContext); - - uint8* executableMemory = PPCRecompilerX86_allocateExecutableMemory(x64GenContext.codeBufferIndex); - // copy code to executable memory - memcpy(executableMemory, x64GenContext.codeBuffer, x64GenContext.codeBufferIndex); - free(x64GenContext.codeBuffer); - return executableMemory; -} - -void PPCRecompilerX64Gen_generateRecompilerInterfaceFunctions() -{ - PPCRecompilerX64Gen_generateEnterRecompilerCode(); - PPCRecompiler_leaveRecompilerCode_unvisited = (void ATTR_MS_ABI (*)())PPCRecompilerX64Gen_generateLeaveRecompilerCode(); - PPCRecompiler_leaveRecompilerCode_visited = (void ATTR_MS_ABI (*)())PPCRecompilerX64Gen_generateLeaveRecompilerCode(); - cemu_assert_debug(PPCRecompiler_leaveRecompilerCode_unvisited != PPCRecompiler_leaveRecompilerCode_visited); -} \ No newline at end of file diff --git a/src/Cafe/HW/Latte/Core/LatteThread.cpp b/src/Cafe/HW/Latte/Core/LatteThread.cpp index 92a7fdbb..426f8e48 100644 --- a/src/Cafe/HW/Latte/Core/LatteThread.cpp +++ b/src/Cafe/HW/Latte/Core/LatteThread.cpp @@ -235,6 +235,8 @@ void Latte_Start() void Latte_Stop() { std::unique_lock _lock(sLatteThreadStateMutex); + if (!sLatteThreadRunning) + return; sLatteThreadRunning = false; _lock.unlock(); sLatteThread.join(); diff --git a/src/Cemu/Logging/CemuLogging.h b/src/Cemu/Logging/CemuLogging.h index 5b2e5fa4..d729d364 100644 --- a/src/Cemu/Logging/CemuLogging.h +++ b/src/Cemu/Logging/CemuLogging.h @@ -39,7 +39,6 @@ enum class LogType : sint32 NN_SL = 26, TextureReadback = 29, - ProcUi = 39, nlibcurl = 41, @@ -47,6 +46,7 @@ enum class LogType : sint32 NFC = 41, NTAG = 42, + Recompiler = 60, }; template <> diff --git a/src/config/ActiveSettings.cpp b/src/config/ActiveSettings.cpp index f81f8336..e552d164 100644 --- a/src/config/ActiveSettings.cpp +++ b/src/config/ActiveSettings.cpp @@ -165,6 +165,11 @@ bool ActiveSettings::DumpTexturesEnabled() return s_dump_textures; } +bool ActiveSettings::DumpRecompilerFunctionsEnabled() +{ + return s_dump_recompiler_functions; +} + bool ActiveSettings::DumpLibcurlRequestsEnabled() { return s_dump_libcurl_requests; @@ -180,6 +185,11 @@ void ActiveSettings::EnableDumpTextures(bool state) s_dump_textures = state; } +void ActiveSettings::EnableDumpRecompilerFunctions(bool state) +{ + s_dump_recompiler_functions = state; +} + void ActiveSettings::EnableDumpLibcurlRequests(bool state) { s_dump_libcurl_requests = state; diff --git a/src/config/ActiveSettings.h b/src/config/ActiveSettings.h index e672fbee..0d7ecfec 100644 --- a/src/config/ActiveSettings.h +++ b/src/config/ActiveSettings.h @@ -109,9 +109,11 @@ public: // dump options [[nodiscard]] static bool DumpShadersEnabled(); [[nodiscard]] static bool DumpTexturesEnabled(); + [[nodiscard]] static bool DumpRecompilerFunctionsEnabled(); [[nodiscard]] static bool DumpLibcurlRequestsEnabled(); static void EnableDumpShaders(bool state); static void EnableDumpTextures(bool state); + static void EnableDumpRecompilerFunctions(bool state); static void EnableDumpLibcurlRequests(bool state); // hacks @@ -125,6 +127,7 @@ private: // dump options inline static bool s_dump_shaders = false; inline static bool s_dump_textures = false; + inline static bool s_dump_recompiler_functions = false; inline static bool s_dump_libcurl_requests = false; // timer speed diff --git a/src/config/LaunchSettings.cpp b/src/config/LaunchSettings.cpp index 32a069c6..d68eb412 100644 --- a/src/config/LaunchSettings.cpp +++ b/src/config/LaunchSettings.cpp @@ -13,6 +13,7 @@ #include "util/crypto/aes128.h" #include "Cafe/Filesystem/FST/FST.h" +#include "util/helpers/StringHelpers.h" void requireConsole(); @@ -74,7 +75,9 @@ bool LaunchSettings::HandleCommandline(const std::vector<std::wstring>& args) po::options_description hidden{ "Hidden options" }; hidden.add_options() ("nsight", po::value<bool>()->implicit_value(true), "NSight debugging options") - ("legacy", po::value<bool>()->implicit_value(true), "Intel legacy graphic mode"); + ("legacy", po::value<bool>()->implicit_value(true), "Intel legacy graphic mode") + ("ppcrec-lower-addr", po::value<std::string>(), "For debugging: Lower address allowed for PPC recompilation") + ("ppcrec-upper-addr", po::value<std::string>(), "For debugging: Upper address allowed for PPC recompilation"); po::options_description extractor{ "Extractor tool" }; extractor.add_options() @@ -186,6 +189,20 @@ bool LaunchSettings::HandleCommandline(const std::vector<std::wstring>& args) if (vm.count("output")) log_path = vm["output"].as<std::wstring>(); + // recompiler range limit for debugging + if (vm.count("ppcrec-lower-addr")) + { + uint32 addr = (uint32)StringHelpers::ToInt64(vm["ppcrec-lower-addr"].as<std::string>()); + ppcRec_limitLowerAddr = addr; + } + if (vm.count("ppcrec-upper-addr")) + { + uint32 addr = (uint32)StringHelpers::ToInt64(vm["ppcrec-upper-addr"].as<std::string>()); + ppcRec_limitUpperAddr = addr; + } + if(ppcRec_limitLowerAddr != 0 && ppcRec_limitUpperAddr != 0) + cemuLog_log(LogType::Force, "PPCRec range limited to 0x{:08x}-0x{:08x}", ppcRec_limitLowerAddr, ppcRec_limitUpperAddr); + if(!extract_path.empty()) { ExtractorTool(extract_path, output_path, log_path); diff --git a/src/config/LaunchSettings.h b/src/config/LaunchSettings.h index b0f673a1..074fbb91 100644 --- a/src/config/LaunchSettings.h +++ b/src/config/LaunchSettings.h @@ -29,6 +29,9 @@ public: static std::optional<uint32> GetPersistentId() { return s_persistent_id; } + static uint32 GetPPCRecLowerAddr() { return ppcRec_limitLowerAddr; }; + static uint32 GetPPCRecUpperAddr() { return ppcRec_limitUpperAddr; }; + private: inline static std::optional<fs::path> s_load_game_file{}; inline static std::optional<uint64> s_load_title_id{}; @@ -44,6 +47,10 @@ private: inline static std::optional<uint32> s_persistent_id{}; + // for recompiler debugging + inline static uint32 ppcRec_limitLowerAddr{}; + inline static uint32 ppcRec_limitUpperAddr{}; + static bool ExtractorTool(std::wstring_view wud_path, std::string_view output_path, std::wstring_view log_path); }; diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 48bdd7d7..f2a5a2fa 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -144,6 +144,7 @@ enum // debug->dump MAINFRAME_MENU_ID_DEBUG_DUMP_TEXTURES = 21600, MAINFRAME_MENU_ID_DEBUG_DUMP_SHADERS, + MAINFRAME_MENU_ID_DEBUG_DUMP_RECOMPILER_FUNCTIONS, MAINFRAME_MENU_ID_DEBUG_DUMP_RAM, MAINFRAME_MENU_ID_DEBUG_DUMP_FST, MAINFRAME_MENU_ID_DEBUG_DUMP_CURL_REQUESTS, @@ -206,8 +207,9 @@ EVT_MENU_RANGE(MAINFRAME_MENU_ID_NFC_RECENT_0 + 0, MAINFRAME_MENU_ID_NFC_RECENT_ EVT_MENU_RANGE(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + 0, MAINFRAME_MENU_ID_DEBUG_LOGGING0 + 98, MainWindow::OnDebugLoggingToggleFlagGeneric) EVT_MENU(MAINFRAME_MENU_ID_DEBUG_ADVANCED_PPC_INFO, MainWindow::OnPPCInfoToggle) // debug -> dump menu -EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_TEXTURES, MainWindow::OnDebugDumpUsedTextures) -EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_SHADERS, MainWindow::OnDebugDumpUsedShaders) +EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_TEXTURES, MainWindow::OnDebugDumpGeneric) +EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_SHADERS, MainWindow::OnDebugDumpGeneric) +EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_RECOMPILER_FUNCTIONS, MainWindow::OnDebugDumpGeneric) EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_CURL_REQUESTS, MainWindow::OnDebugSetting) // debug -> Other options EVT_MENU(MAINFRAME_MENU_ID_DEBUG_RENDER_UPSIDE_DOWN, MainWindow::OnDebugSetting) @@ -1091,31 +1093,29 @@ void MainWindow::OnPPCInfoToggle(wxCommandEvent& event) g_config.Save(); } -void MainWindow::OnDebugDumpUsedTextures(wxCommandEvent& event) +void MainWindow::OnDebugDumpGeneric(wxCommandEvent& event) { - const bool value = event.IsChecked(); - ActiveSettings::EnableDumpTextures(value); - if (value) + std::string dumpSubpath; + std::function<void(bool)> setDumpState; + switch(event.GetId()) { - try - { - // create directory - const fs::path path(ActiveSettings::GetUserDataPath()); - fs::create_directories(path / "dump" / "textures"); - } - catch (const std::exception& ex) - { - SystemException sys(ex); - cemuLog_log(LogType::Force, "can't create texture dump folder: {}", ex.what()); - ActiveSettings::EnableDumpTextures(false); - } + case MAINFRAME_MENU_ID_DEBUG_DUMP_TEXTURES: + dumpSubpath = "dump/textures"; + setDumpState = ActiveSettings::EnableDumpTextures; + break; + case MAINFRAME_MENU_ID_DEBUG_DUMP_SHADERS: + dumpSubpath = "dump/shaders"; + setDumpState = ActiveSettings::EnableDumpShaders; + break; + case MAINFRAME_MENU_ID_DEBUG_DUMP_RECOMPILER_FUNCTIONS: + dumpSubpath = "dump/recompiler"; + setDumpState = ActiveSettings::EnableDumpRecompilerFunctions; + break; + default: + UNREACHABLE; } -} - -void MainWindow::OnDebugDumpUsedShaders(wxCommandEvent& event) -{ const bool value = event.IsChecked(); - ActiveSettings::EnableDumpShaders(value); + setDumpState(value); if (value) { try @@ -2239,6 +2239,7 @@ void MainWindow::RecreateMenu() wxMenu* debugDumpMenu = new wxMenu; debugDumpMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_DUMP_TEXTURES, _("&Textures"), wxEmptyString)->Check(ActiveSettings::DumpTexturesEnabled()); debugDumpMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_DUMP_SHADERS, _("&Shaders"), wxEmptyString)->Check(ActiveSettings::DumpShadersEnabled()); + debugDumpMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_DUMP_RECOMPILER_FUNCTIONS, _("&Recompiled functions"), wxEmptyString)->Check(ActiveSettings::DumpRecompilerFunctionsEnabled()); debugDumpMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_DUMP_CURL_REQUESTS, _("&nlibcurl HTTP/HTTPS requests"), wxEmptyString); // debug submenu wxMenu* debugMenu = new wxMenu(); diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index beb86f98..ddb9795d 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -107,8 +107,7 @@ public: void OnDebugSetting(wxCommandEvent& event); void OnDebugLoggingToggleFlagGeneric(wxCommandEvent& event); void OnPPCInfoToggle(wxCommandEvent& event); - void OnDebugDumpUsedTextures(wxCommandEvent& event); - void OnDebugDumpUsedShaders(wxCommandEvent& event); + void OnDebugDumpGeneric(wxCommandEvent& event); void OnLoggingWindow(wxCommandEvent& event); void OnGDBStubToggle(wxCommandEvent& event); void OnDebugViewPPCThreads(wxCommandEvent& event); diff --git a/src/util/helpers/StringBuf.h b/src/util/helpers/StringBuf.h index 432fa7a1..6242fa4c 100644 --- a/src/util/helpers/StringBuf.h +++ b/src/util/helpers/StringBuf.h @@ -44,10 +44,9 @@ public: void add(std::string_view appendedStr) { - size_t remainingLen = this->limit - this->length; size_t copyLen = appendedStr.size(); - if (remainingLen < copyLen) - copyLen = remainingLen; + if (this->length + copyLen + 1 >= this->limit) + _reserve(std::max<uint32>(this->length + copyLen + 64, this->limit + this->limit / 2)); char* outputStart = (char*)(this->str + this->length); std::copy(appendedStr.data(), appendedStr.data() + copyLen, outputStart); length += copyLen; @@ -80,6 +79,13 @@ public: } private: + void _reserve(uint32 newLimit) + { + cemu_assert_debug(newLimit > length); + this->str = (uint8*)realloc(this->str, newLimit + 4); + this->limit = newLimit; + } + uint8* str; uint32 length; /* in bytes */ uint32 limit; /* in bytes */ From a5f3558b7934bb9ccaee5aa27b8f67741da9be2c Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Sun, 27 Apr 2025 16:53:59 +0200 Subject: [PATCH 249/299] Revert "fix building with fmt11 and GCC" This reverts commit 372c314f0630a6d0ae1cb303f696071efbc72717. It broke formatting in an attempt to fix GCC builds. Some other change (perhaps dependency updates) has resolved the issue. --- src/Cafe/GameProfile/GameProfile.cpp | 2 +- src/config/CemuConfig.h | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Cafe/GameProfile/GameProfile.cpp b/src/Cafe/GameProfile/GameProfile.cpp index ea303226..ee92107a 100644 --- a/src/Cafe/GameProfile/GameProfile.cpp +++ b/src/Cafe/GameProfile/GameProfile.cpp @@ -140,7 +140,7 @@ bool gameProfile_loadEnumOption(IniParser& iniParser, const char* optionName, T& for(const T& v : T()) { // test integer option - if (boost::iequals(fmt::format("{}", fmt::underlying(v)), *option_value)) + if (boost::iequals(fmt::format("{}", static_cast<typename std::underlying_type<T>::type>(v)), *option_value)) { option = v; return true; diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index 62665f6d..191614a2 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -192,7 +192,7 @@ ENABLE_ENUM_ITERATORS(CrashDump, CrashDump::Disabled, CrashDump::Enabled); #endif template <> -struct fmt::formatter<const PrecompiledShaderOption> : formatter<string_view> { +struct fmt::formatter<PrecompiledShaderOption> : formatter<string_view> { template <typename FormatContext> auto format(const PrecompiledShaderOption c, FormatContext &ctx) const { string_view name; @@ -207,7 +207,7 @@ struct fmt::formatter<const PrecompiledShaderOption> : formatter<string_view> { } }; template <> -struct fmt::formatter<const AccurateShaderMulOption> : formatter<string_view> { +struct fmt::formatter<AccurateShaderMulOption> : formatter<string_view> { template <typename FormatContext> auto format(const AccurateShaderMulOption c, FormatContext &ctx) const { string_view name; @@ -221,7 +221,7 @@ struct fmt::formatter<const AccurateShaderMulOption> : formatter<string_view> { } }; template <> -struct fmt::formatter<const CPUMode> : formatter<string_view> { +struct fmt::formatter<CPUMode> : formatter<string_view> { template <typename FormatContext> auto format(const CPUMode c, FormatContext &ctx) const { string_view name; @@ -238,7 +238,7 @@ struct fmt::formatter<const CPUMode> : formatter<string_view> { } }; template <> -struct fmt::formatter<const CPUModeLegacy> : formatter<string_view> { +struct fmt::formatter<CPUModeLegacy> : formatter<string_view> { template <typename FormatContext> auto format(const CPUModeLegacy c, FormatContext &ctx) const { string_view name; @@ -255,7 +255,7 @@ struct fmt::formatter<const CPUModeLegacy> : formatter<string_view> { } }; template <> -struct fmt::formatter<const CafeConsoleRegion> : formatter<string_view> { +struct fmt::formatter<CafeConsoleRegion> : formatter<string_view> { template <typename FormatContext> auto format(const CafeConsoleRegion v, FormatContext &ctx) const { string_view name; @@ -276,7 +276,7 @@ struct fmt::formatter<const CafeConsoleRegion> : formatter<string_view> { } }; template <> -struct fmt::formatter<const CafeConsoleLanguage> : formatter<string_view> { +struct fmt::formatter<CafeConsoleLanguage> : formatter<string_view> { template <typename FormatContext> auto format(const CafeConsoleLanguage v, FormatContext &ctx) { string_view name; @@ -302,7 +302,7 @@ struct fmt::formatter<const CafeConsoleLanguage> : formatter<string_view> { #if BOOST_OS_WINDOWS template <> -struct fmt::formatter<const CrashDump> : formatter<string_view> { +struct fmt::formatter<CrashDump> : formatter<string_view> { template <typename FormatContext> auto format(const CrashDump v, FormatContext &ctx) { string_view name; @@ -319,7 +319,7 @@ struct fmt::formatter<const CrashDump> : formatter<string_view> { }; #elif BOOST_OS_UNIX template <> -struct fmt::formatter<const CrashDump> : formatter<string_view> { +struct fmt::formatter<CrashDump> : formatter<string_view> { template <typename FormatContext> auto format(const CrashDump v, FormatContext &ctx) { string_view name; From e6a64aadda4d99ebe16f33f55b0a813444827a06 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Sun, 27 Apr 2025 17:03:00 +0200 Subject: [PATCH 250/299] undo revert of style improvement --- src/Cafe/GameProfile/GameProfile.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cafe/GameProfile/GameProfile.cpp b/src/Cafe/GameProfile/GameProfile.cpp index ee92107a..ea303226 100644 --- a/src/Cafe/GameProfile/GameProfile.cpp +++ b/src/Cafe/GameProfile/GameProfile.cpp @@ -140,7 +140,7 @@ bool gameProfile_loadEnumOption(IniParser& iniParser, const char* optionName, T& for(const T& v : T()) { // test integer option - if (boost::iequals(fmt::format("{}", static_cast<typename std::underlying_type<T>::type>(v)), *option_value)) + if (boost::iequals(fmt::format("{}", fmt::underlying(v)), *option_value)) { option = v; return true; From 00099c5ecc3748a39545f08b53e65f9a5b86e146 Mon Sep 17 00:00:00 2001 From: Shikakiben <108907095+Shikakiben@users.noreply.github.com> Date: Sun, 4 May 2025 13:29:27 +0200 Subject: [PATCH 251/299] Set StartupWMClass in .desktop file (#1552) --- dist/linux/info.cemu.Cemu.desktop | 1 + 1 file changed, 1 insertion(+) diff --git a/dist/linux/info.cemu.Cemu.desktop b/dist/linux/info.cemu.Cemu.desktop index 5003d4a6..6eeb0120 100644 --- a/dist/linux/info.cemu.Cemu.desktop +++ b/dist/linux/info.cemu.Cemu.desktop @@ -24,3 +24,4 @@ Comment[it]=Software per emulare giochi e applicazioni per Wii U su PC Categories=Game;Emulator; Keywords=Nintendo; MimeType=application/x-wii-u-rom; +StartupWMClass=Cemu From fa7ae84314244fc83fcb27f5886c57733733afa5 Mon Sep 17 00:00:00 2001 From: neebyA <126654084+neebyA@users.noreply.github.com> Date: Sun, 4 May 2025 04:46:01 -0700 Subject: [PATCH 252/299] macOS: Fix browsing of directory paths with spaces (#1546) --- src/gui/TitleManager.cpp | 2 +- src/gui/components/wxGameList.cpp | 8 ++++---- src/gui/components/wxTitleManagerList.cpp | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/gui/TitleManager.cpp b/src/gui/TitleManager.cpp index 4a4f7f56..1dd41b3b 100644 --- a/src/gui/TitleManager.cpp +++ b/src/gui/TitleManager.cpp @@ -491,7 +491,7 @@ void TitleManager::OnSaveOpenDirectory(wxCommandEvent& event) if (!fs::exists(target) || !fs::is_directory(target)) return; - wxLaunchDefaultBrowser(wxHelper::FromUtf8(fmt::format("file:{}", _pathToUtf8(target)))); + wxLaunchDefaultApplication(wxHelper::FromPath(target)); } void TitleManager::OnSaveDelete(wxCommandEvent& event) diff --git a/src/gui/components/wxGameList.cpp b/src/gui/components/wxGameList.cpp index 6cbb5859..e418ca0a 100644 --- a/src/gui/components/wxGameList.cpp +++ b/src/gui/components/wxGameList.cpp @@ -668,7 +668,7 @@ void wxGameList::OnContextMenuSelected(wxCommandEvent& event) { fs::path path(gameInfo.GetBase().GetPath()); _stripPathFilename(path); - wxLaunchDefaultBrowser(wxHelper::FromUtf8(fmt::format("file:{}", _pathToUtf8(path)))); + wxLaunchDefaultApplication(wxHelper::FromPath(path)); break; } case kWikiPage: @@ -689,21 +689,21 @@ void wxGameList::OnContextMenuSelected(wxCommandEvent& event) case kContextMenuSaveFolder: { - wxLaunchDefaultBrowser(wxHelper::FromUtf8(fmt::format("file:{}", _pathToUtf8(gameInfo.GetSaveFolder())))); + wxLaunchDefaultApplication(wxHelper::FromPath(gameInfo.GetSaveFolder())); break; } case kContextMenuUpdateFolder: { fs::path path(gameInfo.GetUpdate().GetPath()); _stripPathFilename(path); - wxLaunchDefaultBrowser(wxHelper::FromUtf8(fmt::format("file:{}", _pathToUtf8(path)))); + wxLaunchDefaultApplication(wxHelper::FromPath(path)); break; } case kContextMenuDLCFolder: { fs::path path(gameInfo.GetAOC().front().GetPath()); _stripPathFilename(path); - wxLaunchDefaultBrowser(wxHelper::FromUtf8(fmt::format("file:{}", _pathToUtf8(path)))); + wxLaunchDefaultApplication(wxHelper::FromPath(path)); break; } case kContextMenuRemoveCache: diff --git a/src/gui/components/wxTitleManagerList.cpp b/src/gui/components/wxTitleManagerList.cpp index e8efb060..c0bf5778 100644 --- a/src/gui/components/wxTitleManagerList.cpp +++ b/src/gui/components/wxTitleManagerList.cpp @@ -869,7 +869,7 @@ void wxTitleManagerList::OnContextMenuSelected(wxCommandEvent& event) case kContextMenuOpenDirectory: { const auto path = fs::is_directory(entry->path) ? entry->path : entry->path.parent_path(); - wxLaunchDefaultBrowser(wxHelper::FromUtf8(fmt::format("file:{}", _pathToUtf8(path)))); + wxLaunchDefaultApplication(wxHelper::FromPath(path)); } break; case kContextMenuDelete: From d083fc047084764d1d78de8863b17cd962181d65 Mon Sep 17 00:00:00 2001 From: Crementif <26669564+Crementif@users.noreply.github.com> Date: Sun, 4 May 2025 17:19:56 +0200 Subject: [PATCH 253/299] Reorder PPCInterpreter memory layout to keep plugin compatibility Commit b089ae5b32f5f2bc901b9ff2db2424cbf3c1186b changed the PPCInterpreter struct that external plugins rely on to hook Cemu through e.g. the exported "osLib_registerHLEFunction". This commit moves some unused values down so that it keeps the same memory layout as before the PPC recompiler rework. --- src/Cafe/HW/Espresso/PPCState.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Cafe/HW/Espresso/PPCState.h b/src/Cafe/HW/Espresso/PPCState.h index 8f27ee93..179e2687 100644 --- a/src/Cafe/HW/Espresso/PPCState.h +++ b/src/Cafe/HW/Espresso/PPCState.h @@ -51,12 +51,10 @@ struct PPCInterpreter_t uint8 xer_ca; // carry from xer uint8 xer_so; uint8 xer_ov; - uint8 LSQE; - uint8 PSE; // thread remaining cycles sint32 remainingCycles; // if this value goes below zero, the next thread is scheduled sint32 skippedCycles; // number of skipped cycles - struct + struct { uint32 LR; uint32 CTR; @@ -72,7 +70,7 @@ struct PPCInterpreter_t uint32 temporaryGPR[4]; // deprecated, refactor backend dependency on this away uint32 temporaryGPR_reg[4]; // values below this are not used by Cafe OS usermode - struct + struct { uint32 fpecr; // is this the same register as fpscr ? uint32 DEC; @@ -87,7 +85,7 @@ struct PPCInterpreter_t // DMA uint32 dmaU; uint32 dmaL; - // MMU + // MMU uint32 dbatU[8]; uint32 dbatL[8]; uint32 ibatU[8]; @@ -95,6 +93,8 @@ struct PPCInterpreter_t uint32 sr[16]; uint32 sdr1; }sprExtended; + uint8 LSQE; + uint8 PSE; // global CPU values PPCInterpreterGlobal_t* global; // interpreter control From 352a9184947c473295f27edff944e3cbf1f6f321 Mon Sep 17 00:00:00 2001 From: Crementif <26669564+Crementif@users.noreply.github.com> Date: Sun, 4 May 2025 18:04:26 +0200 Subject: [PATCH 254/299] debug: add CLI option to have multi-core interpreter (#1553) This option still allows you to have proper stack traces for crashes or for the PPC debugger, but is a lot faster then the really slow interpreter (especially when loading into large games like BotW where there has to be a lot of asset decompressing). It makes memory access breakpoints much more brittle though, so it's not a perfect option. Normal users should of course stick with using the recompiler. --- src/Cafe/CafeSystem.cpp | 2 +- src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp | 4 ++-- src/config/LaunchSettings.cpp | 6 +++++- src/config/LaunchSettings.h | 2 ++ 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Cafe/CafeSystem.cpp b/src/Cafe/CafeSystem.cpp index 1bf3755e..d20ccd9d 100644 --- a/src/Cafe/CafeSystem.cpp +++ b/src/Cafe/CafeSystem.cpp @@ -844,7 +844,7 @@ namespace CafeSystem module->TitleStart(); cemu_initForGame(); // enter scheduler - if (ActiveSettings::GetCPUMode() == CPUMode::MulticoreRecompiler && !LaunchSettings::ForceInterpreter()) + if ((ActiveSettings::GetCPUMode() == CPUMode::MulticoreRecompiler || LaunchSettings::ForceMultiCoreInterpreter()) && !LaunchSettings::ForceInterpreter()) coreinit::OSSchedulerBegin(3); else coreinit::OSSchedulerBegin(1); diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp index 76264717..be1846de 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp @@ -669,9 +669,9 @@ void PPCRecompiler_init() ppcRecompilerEnabled = false; return; } - if (LaunchSettings::ForceInterpreter()) + if (LaunchSettings::ForceInterpreter() || LaunchSettings::ForceMultiCoreInterpreter()) { - cemuLog_log(LogType::Force, "Recompiler disabled. Command line --force-interpreter was passed"); + cemuLog_log(LogType::Force, "Recompiler disabled. Command line --force-interpreter or force-multicore-interpreter was passed"); return; } if (ppcRecompilerInstanceData) diff --git a/src/config/LaunchSettings.cpp b/src/config/LaunchSettings.cpp index d68eb412..b2b0c08a 100644 --- a/src/config/LaunchSettings.cpp +++ b/src/config/LaunchSettings.cpp @@ -69,7 +69,8 @@ bool LaunchSettings::HandleCommandline(const std::vector<std::wstring>& args) ("account,a", po::value<std::string>(), "Persistent id of account") - ("force-interpreter", po::value<bool>()->implicit_value(true), "Force interpreter CPU emulation, disables recompiler") + ("force-interpreter", po::value<bool>()->implicit_value(true), "Force interpreter CPU emulation, disables recompiler. Useful for debugging purposes where you want to get accurate memory accesses and stack traces.") + ("force-multicore-interpreter", po::value<bool>()->implicit_value(true), "Force multi-core interpreter CPU emulation, disables recompiler. Only useful for getting stack traces, but slightly faster than the single-core interpreter mode.") ("enable-gdbstub", po::value<bool>()->implicit_value(true), "Enable GDB stub to debug executables inside Cemu using an external debugger"); po::options_description hidden{ "Hidden options" }; @@ -176,6 +177,9 @@ bool LaunchSettings::HandleCommandline(const std::vector<std::wstring>& args) if(vm.count("force-interpreter")) s_force_interpreter = vm["force-interpreter"].as<bool>(); + + if(vm.count("force-multicore-interpreter")) + s_force_multicore_interpreter = vm["force-multicore-interpreter"].as<bool>(); if (vm.count("enable-gdbstub")) s_enable_gdbstub = vm["enable-gdbstub"].as<bool>(); diff --git a/src/config/LaunchSettings.h b/src/config/LaunchSettings.h index 074fbb91..d1bed9e1 100644 --- a/src/config/LaunchSettings.h +++ b/src/config/LaunchSettings.h @@ -26,6 +26,7 @@ public: static bool NSightModeEnabled() { return s_nsight_mode; } static bool ForceInterpreter() { return s_force_interpreter; }; + static bool ForceMultiCoreInterpreter() { return s_force_multicore_interpreter; } static std::optional<uint32> GetPersistentId() { return s_persistent_id; } @@ -44,6 +45,7 @@ private: inline static bool s_nsight_mode = false; inline static bool s_force_interpreter = false; + inline static bool s_force_multicore_interpreter = false; inline static std::optional<uint32> s_persistent_id{}; From 33d5c6d490681b49f447c9a3e94c5451b241aba1 Mon Sep 17 00:00:00 2001 From: gamerbross <55158797+gamerbross@users.noreply.github.com> Date: Sun, 4 May 2025 21:44:46 +0200 Subject: [PATCH 255/299] nsyshid: Add Skylander Xbox 360 Portal support (#1550) --- src/Cafe/CMakeLists.txt | 4 + src/Cafe/OS/libs/nsyshid/Backend.h | 2 +- src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp | 8 + src/Cafe/OS/libs/nsyshid/SkylanderXbox360.cpp | 160 ++++++ src/Cafe/OS/libs/nsyshid/SkylanderXbox360.h | 46 ++ src/Cafe/OS/libs/nsyshid/Whitelist.cpp | 2 + src/Cafe/OS/libs/nsyshid/g721/g721.cpp | 543 ++++++++++++++++++ src/Cafe/OS/libs/nsyshid/g721/g721.h | 96 ++++ src/Cafe/OS/libs/nsyshid/nsyshid.cpp | 8 +- 9 files changed, 864 insertions(+), 5 deletions(-) create mode 100644 src/Cafe/OS/libs/nsyshid/SkylanderXbox360.cpp create mode 100644 src/Cafe/OS/libs/nsyshid/SkylanderXbox360.h create mode 100644 src/Cafe/OS/libs/nsyshid/g721/g721.cpp create mode 100644 src/Cafe/OS/libs/nsyshid/g721/g721.h diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index 6f9f5e30..34948f13 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -476,6 +476,10 @@ add_library(CemuCafe OS/libs/nsyshid/Infinity.h OS/libs/nsyshid/Skylander.cpp OS/libs/nsyshid/Skylander.h + OS/libs/nsyshid/SkylanderXbox360.cpp + OS/libs/nsyshid/SkylanderXbox360.h + OS/libs/nsyshid/g721/g721.cpp + OS/libs/nsyshid/g721/g721.h OS/libs/nsyskbd/nsyskbd.cpp OS/libs/nsyskbd/nsyskbd.h OS/libs/nsysnet/nsysnet.cpp diff --git a/src/Cafe/OS/libs/nsyshid/Backend.h b/src/Cafe/OS/libs/nsyshid/Backend.h index 67dad4fe..bfd7a235 100644 --- a/src/Cafe/OS/libs/nsyshid/Backend.h +++ b/src/Cafe/OS/libs/nsyshid/Backend.h @@ -172,7 +172,7 @@ namespace nsyshid std::shared_ptr<Device> FindDevice(std::function<bool(const std::shared_ptr<Device>&)> isWantedDevice); - bool FindDeviceById(uint16 vendorId, uint16 productId); + std::shared_ptr<Device> FindDeviceById(uint16 vendorId, uint16 productId); bool IsDeviceWhitelisted(uint16 vendorId, uint16 productId); diff --git a/src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp b/src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp index 533d349e..a5eb95c1 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp +++ b/src/Cafe/OS/libs/nsyshid/BackendEmulated.cpp @@ -4,6 +4,7 @@ #include "Infinity.h" #include "Skylander.h" #include "config/CemuConfig.h" +#include "SkylanderXbox360.h" namespace nsyshid::backend::emulated { @@ -28,6 +29,13 @@ namespace nsyshid::backend::emulated auto device = std::make_shared<SkylanderPortalDevice>(); AttachDevice(device); } + else if (auto usb_portal = FindDeviceById(0x1430, 0x1F17)) + { + cemuLog_logDebug(LogType::Force, "Attaching Xbox 360 Portal"); + // Add Skylander Xbox 360 Portal + auto device = std::make_shared<SkylanderXbox360PortalLibusb>(usb_portal); + AttachDevice(device); + } if (GetConfig().emulated_usb_devices.emulate_infinity_base && !FindDeviceById(0x0E6F, 0x0129)) { cemuLog_logDebug(LogType::Force, "Attaching Emulated Base"); diff --git a/src/Cafe/OS/libs/nsyshid/SkylanderXbox360.cpp b/src/Cafe/OS/libs/nsyshid/SkylanderXbox360.cpp new file mode 100644 index 00000000..c8755982 --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/SkylanderXbox360.cpp @@ -0,0 +1,160 @@ +#include "SkylanderXbox360.h" + +namespace nsyshid +{ + SkylanderXbox360PortalLibusb::SkylanderXbox360PortalLibusb(std::shared_ptr<Device> usbPortal) + : Device(0x1430, 0x0150, 1, 2, 0) + { + m_IsOpened = false; + m_usbPortal = std::static_pointer_cast<backend::libusb::DeviceLibusb>(usbPortal); + } + + bool SkylanderXbox360PortalLibusb::Open() + { + return m_usbPortal->Open(); + } + + void SkylanderXbox360PortalLibusb::Close() + { + return m_usbPortal->Close(); + } + + bool SkylanderXbox360PortalLibusb::IsOpened() + { + return m_usbPortal->IsOpened(); + } + + Device::ReadResult SkylanderXbox360PortalLibusb::Read(ReadMessage* message) + { + std::vector<uint8> xboxData(std::min<uint32>(32, message->length + sizeof(XBOX_DATA_HEADER)), 0); + memcpy(xboxData.data(), XBOX_DATA_HEADER, sizeof(XBOX_DATA_HEADER)); + memcpy(xboxData.data() + sizeof(XBOX_DATA_HEADER), message->data, message->length - sizeof(XBOX_DATA_HEADER)); + + ReadMessage xboxMessage(xboxData.data(), xboxData.size(), 0); + auto result = m_usbPortal->Read(&xboxMessage); + + memcpy(message->data, xboxData.data() + sizeof(XBOX_DATA_HEADER), message->length); + message->bytesRead = xboxMessage.bytesRead; + + return result; + } + + // Use InterruptTransfer instead of ControlTransfer + bool SkylanderXbox360PortalLibusb::SetReport(ReportMessage* message) + { + if (message->data[0] == 'M' && message->data[1] == 0x01) // Enables Speaker + g72x_init_state(&m_state); + + std::vector<uint8> xboxData(message->length + sizeof(XBOX_DATA_HEADER), 0); + memcpy(xboxData.data(), XBOX_DATA_HEADER, sizeof(XBOX_DATA_HEADER)); + memcpy(xboxData.data() + sizeof(XBOX_DATA_HEADER), message->data, message->length); + + WriteMessage xboxMessage(xboxData.data(), xboxData.size(), 0); + auto result = m_usbPortal->Write(&xboxMessage); + + memcpy(message->data, xboxData.data() + sizeof(XBOX_DATA_HEADER), message->length); + + return result == WriteResult::Success; + } + + Device::WriteResult SkylanderXbox360PortalLibusb::Write(WriteMessage* message) + { + std::vector<uint8> audioData(message->data, message->data + message->length); + + std::vector<uint8_t> xboxAudioData; + for (size_t i = 0; i < audioData.size(); i += 4) + { + int16_t sample1 = (static_cast<int16_t>(audioData[i + 1]) << 8) | audioData[i]; + int16_t sample2 = (static_cast<int16_t>(audioData[i + 3]) << 8) | audioData[i + 2]; + + uint8_t encoded1 = g721_encoder(sample1, &m_state) & 0x0F; + uint8_t encoded2 = g721_encoder(sample2, &m_state) & 0x0F; + + xboxAudioData.push_back((encoded2 << 4) | encoded1); + } + + std::vector<uint8> xboxData(xboxAudioData.size() + sizeof(XBOX_AUDIO_DATA_HEADER), 0); + memcpy(xboxData.data(), XBOX_AUDIO_DATA_HEADER, sizeof(XBOX_AUDIO_DATA_HEADER)); + memcpy(xboxData.data() + sizeof(XBOX_AUDIO_DATA_HEADER), xboxAudioData.data(), xboxAudioData.size()); + + WriteMessage xboxMessage(xboxData.data(), xboxData.size(), 0); + auto result = m_usbPortal->Write(&xboxMessage); + + memcpy(message->data, xboxData.data() + sizeof(XBOX_AUDIO_DATA_HEADER), xboxAudioData.size()); + message->bytesWritten = xboxMessage.bytesWritten - sizeof(XBOX_AUDIO_DATA_HEADER); + return result; + } + + bool SkylanderXbox360PortalLibusb::GetDescriptor(uint8 descType, uint8 descIndex, uint16 lang, uint8* output, uint32 outputMaxLength) + { + uint8 configurationDescriptor[0x29]; + + uint8* currentWritePtr; + + // configuration descriptor + currentWritePtr = configurationDescriptor + 0; + *(uint8*)(currentWritePtr + 0) = 9; // bLength + *(uint8*)(currentWritePtr + 1) = 2; // bDescriptorType + *(uint16be*)(currentWritePtr + 2) = 0x0029; // wTotalLength + *(uint8*)(currentWritePtr + 4) = 1; // bNumInterfaces + *(uint8*)(currentWritePtr + 5) = 1; // bConfigurationValue + *(uint8*)(currentWritePtr + 6) = 0; // iConfiguration + *(uint8*)(currentWritePtr + 7) = 0x80; // bmAttributes + *(uint8*)(currentWritePtr + 8) = 0xFA; // MaxPower + currentWritePtr = currentWritePtr + 9; + // interface descriptor + *(uint8*)(currentWritePtr + 0) = 9; // bLength + *(uint8*)(currentWritePtr + 1) = 0x04; // bDescriptorType + *(uint8*)(currentWritePtr + 2) = 0; // bInterfaceNumber + *(uint8*)(currentWritePtr + 3) = 0; // bAlternateSetting + *(uint8*)(currentWritePtr + 4) = 2; // bNumEndpoints + *(uint8*)(currentWritePtr + 5) = 3; // bInterfaceClass + *(uint8*)(currentWritePtr + 6) = 0; // bInterfaceSubClass + *(uint8*)(currentWritePtr + 7) = 0; // bInterfaceProtocol + *(uint8*)(currentWritePtr + 8) = 0; // iInterface + currentWritePtr = currentWritePtr + 9; + // HID descriptor + *(uint8*)(currentWritePtr + 0) = 9; // bLength + *(uint8*)(currentWritePtr + 1) = 0x21; // bDescriptorType + *(uint16be*)(currentWritePtr + 2) = 0x0111; // bcdHID + *(uint8*)(currentWritePtr + 4) = 0x00; // bCountryCode + *(uint8*)(currentWritePtr + 5) = 0x01; // bNumDescriptors + *(uint8*)(currentWritePtr + 6) = 0x22; // bDescriptorType + *(uint16be*)(currentWritePtr + 7) = 0x001D; // wDescriptorLength + currentWritePtr = currentWritePtr + 9; + // endpoint descriptor 1 + *(uint8*)(currentWritePtr + 0) = 7; // bLength + *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType + *(uint8*)(currentWritePtr + 2) = 0x81; // bEndpointAddress + *(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes + *(uint16be*)(currentWritePtr + 4) = 0x0040; // wMaxPacketSize + *(uint8*)(currentWritePtr + 6) = 0x01; // bInterval + currentWritePtr = currentWritePtr + 7; + // endpoint descriptor 2 + *(uint8*)(currentWritePtr + 0) = 7; // bLength + *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType + *(uint8*)(currentWritePtr + 2) = 0x02; // bEndpointAddress + *(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes + *(uint16be*)(currentWritePtr + 4) = 0x0040; // wMaxPacketSize + *(uint8*)(currentWritePtr + 6) = 0x01; // bInterval + currentWritePtr = currentWritePtr + 7; + + cemu_assert_debug((currentWritePtr - configurationDescriptor) == 0x29); + + memcpy(output, configurationDescriptor, + std::min<uint32>(outputMaxLength, sizeof(configurationDescriptor))); + return true; + } + + bool SkylanderXbox360PortalLibusb::SetIdle(uint8 ifIndex, + uint8 reportId, + uint8 duration) + { + return true; + } + + bool SkylanderXbox360PortalLibusb::SetProtocol(uint8 ifIndex, uint8 protocol) + { + return true; + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nsyshid/SkylanderXbox360.h b/src/Cafe/OS/libs/nsyshid/SkylanderXbox360.h new file mode 100644 index 00000000..901c63f9 --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/SkylanderXbox360.h @@ -0,0 +1,46 @@ +#pragma once + +#include "nsyshid.h" +#include "BackendLibusb.h" +#include "g721/g721.h" + +namespace nsyshid +{ + class SkylanderXbox360PortalLibusb final : public Device { + public: + SkylanderXbox360PortalLibusb(std::shared_ptr<Device> usbPortal); + ~SkylanderXbox360PortalLibusb() = default; + + bool Open() override; + + void Close() override; + + bool IsOpened() override; + + ReadResult Read(ReadMessage* message) override; + + WriteResult Write(WriteMessage* message) override; + + bool GetDescriptor(uint8 descType, + uint8 descIndex, + uint16 lang, + uint8* output, + uint32 outputMaxLength) override; + + bool SetIdle(uint8 ifIndex, + uint8 reportId, + uint8 duration) override; + + bool SetProtocol(uint8 ifIndex, uint8 protocol) override; + + bool SetReport(ReportMessage* message) override; + + private: + std::shared_ptr<backend::libusb::DeviceLibusb> m_usbPortal; + bool m_IsOpened; + struct g72x_state m_state; + }; + + constexpr uint8 XBOX_DATA_HEADER[] = { 0x0B, 0x14 }; + constexpr uint8 XBOX_AUDIO_DATA_HEADER[] = { 0x0B, 0x17 }; +} // namespace nsyshid \ No newline at end of file diff --git a/src/Cafe/OS/libs/nsyshid/Whitelist.cpp b/src/Cafe/OS/libs/nsyshid/Whitelist.cpp index f20e4c45..ae965096 100644 --- a/src/Cafe/OS/libs/nsyshid/Whitelist.cpp +++ b/src/Cafe/OS/libs/nsyshid/Whitelist.cpp @@ -16,6 +16,8 @@ namespace nsyshid m_devices.emplace_back(0x0e6f, 0x0241); // skylanders portal m_devices.emplace_back(0x1430, 0x0150); + // skylanders 360 portal + m_devices.emplace_back(0x1430, 0x1F17); // disney infinity base m_devices.emplace_back(0x0e6f, 0x0129); } diff --git a/src/Cafe/OS/libs/nsyshid/g721/g721.cpp b/src/Cafe/OS/libs/nsyshid/g721/g721.cpp new file mode 100644 index 00000000..995212c2 --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/g721/g721.cpp @@ -0,0 +1,543 @@ +/* + * This source code is a product of Sun Microsystems, Inc. and is provided + * for unrestricted use. Users may copy or modify this source code without + * charge. + * + * SUN SOURCE CODE IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING + * THE WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR + * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. + * + * Sun source code is provided with no support and without any obligation on + * the part of Sun Microsystems, Inc. to assist in its use, correction, + * modification or enhancement. + * + * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE + * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY THIS SOFTWARE + * OR ANY PART THEREOF. + * + * In no event will Sun Microsystems, Inc. be liable for any lost revenue + * or profits or other special, indirect and consequential damages, even if + * Sun has been advised of the possibility of such damages. + * + * Sun Microsystems, Inc. + * 2550 Garcia Avenue + * Mountain View, California 94043 + */ + +/* + * g721.c + * + * Description: + * + * g721_encoder(), g721_decoder() + * + * These routines comprise an implementation of the CCITT G.721 ADPCM + * coding algorithm. Essentially, this implementation is identical to + * the bit level description except for a few deviations which + * take advantage of work station attributes, such as hardware 2's + * complement arithmetic and large memory. Specifically, certain time + * consuming operations such as multiplications are replaced + * with lookup tables and software 2's complement operations are + * replaced with hardware 2's complement. + * + * The deviation from the bit level specification (lookup tables) + * preserves the bit level performance specifications. + * + * As outlined in the G.721 Recommendation, the algorithm is broken + * down into modules. Each section of code below is preceded by + * the name of the module which it is implementing. + * + */ +#include "g721.h" +#include <stdlib.h> + +static short qtab_721[7] = { -124, 80, 178, 246, 300, 349, 400 }; +/* + * Maps G.721 code word to reconstructed scale factor normalized log + * magnitude values. + */ +static short _dqlntab[16] = { -2048, 4, 135, 213, 273, 323, 373, 425, + 425, 373, 323, 273, 213, 135, 4, -2048 }; + +/* Maps G.721 code word to log of scale factor multiplier. */ +static short _witab[16] = { -12, 18, 41, 64, 112, 198, 355, 1122, + 1122, 355, 198, 112, 64, 41, 18, -12 }; +/* + * Maps G.721 code words to a set of values whose long and short + * term averages are computed and then compared to give an indication + * how stationary (steady state) the signal is. + */ +static short _fitab[16] = { 0, 0, 0, 0x200, 0x200, 0x200, 0x600, 0xE00, + 0xE00, 0x600, 0x200, 0x200, 0x200, 0, 0, 0 }; + +/* + * g721_encoder() + * + * Encodes the input value of linear PCM from sl and returns + * the resulting code. + */ +int g721_encoder(int sl, struct g72x_state* state_ptr) +{ + short sezi, se, sez; /* ACCUM */ + short d; /* SUBTA */ + short sr; /* ADDB */ + short y; /* MIX */ + short dqsez; /* ADDC */ + short dq, i; + + sl >>= 2; /* linearize input sample to 14-bit PCM */ + + sezi = predictor_zero(state_ptr); + sez = sezi >> 1; + se = (sezi + predictor_pole(state_ptr)) >> 1; /* estimated signal */ + + d = sl - se; /* estimation difference */ + + /* quantize the prediction difference */ + y = step_size(state_ptr); /* quantizer step size */ + i = quantize(d, y, qtab_721, 7); /* i = ADPCM code */ + + dq = reconstruct(i & 8, _dqlntab[i], y); /* quantized est diff */ + + sr = (dq < 0) ? se - (dq & 0x3FFF) : se + dq; /* reconst. signal */ + + dqsez = sr + sez - se; /* pole prediction diff. */ + + update(4, y, _witab[i] << 5, _fitab[i], dq, sr, dqsez, state_ptr); + + return (i); +} + +/* + * g721_decoder() + * + * Description: + * + * Decodes a 4-bit code of G.721 encoded data of i and + * returns the resulting linear PCM + */ +int g721_decoder(int i, struct g72x_state* state_ptr) +{ + short sezi, sei, sez, se; /* ACCUM */ + short y; /* MIX */ + short sr; /* ADDB */ + short dq; + short dqsez; + + i &= 0x0f; /* mask to get proper bits */ + sezi = predictor_zero(state_ptr); + sez = sezi >> 1; + sei = sezi + predictor_pole(state_ptr); + se = sei >> 1; /* se = estimated signal */ + + y = step_size(state_ptr); /* dynamic quantizer step size */ + + dq = reconstruct(i & 0x08, _dqlntab[i], y); /* quantized diff. */ + + sr = (dq < 0) ? (se - (dq & 0x3FFF)) : se + dq; /* reconst. signal */ + + dqsez = sr - se + sez; /* pole prediction diff. */ + + update(4, y, _witab[i] << 5, _fitab[i], dq, sr, dqsez, state_ptr); + + return (sr << 2); +} + + +static short power2[15] = { 1, 2, 4, 8, 0x10, 0x20, 0x40, 0x80, + 0x100, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x4000 }; + +/* + * quan() + * + * quantizes the input val against the table of size short integers. + * It returns i if table[i - 1] <= val < table[i]. + * + * Using linear search for simple coding. + */ +static int quan(int val, short* table, int size) +{ + int i; + + for (i = 0; i < size; i++) + if (val < *table++) + break; + return (i); +} + +/* + * fmult() + * + * returns the integer product of the 14-bit integer "an" and + * "floating point" representation (4-bit exponent, 6-bit mantessa) "srn". + */ +static int fmult(int an, int srn) +{ + short anmag, anexp, anmant; + short wanexp, wanmant; + short retval; + + anmag = (an > 0) ? an : ((-an) & 0x1FFF); + anexp = quan(anmag, power2, 15) - 6; + anmant = (anmag == 0) ? 32 : (anexp >= 0) ? anmag >> anexp : anmag << -anexp; + wanexp = anexp + ((srn >> 6) & 0xF) - 13; + + wanmant = (anmant * (srn & 077) + 0x30) >> 4; + retval = (wanexp >= 0) ? ((wanmant << wanexp) & 0x7FFF) : (wanmant >> -wanexp); + + return (((an ^ srn) < 0) ? -retval : retval); +} + + + +/* + * update() + * + * updates the state variables for each output code + */ +void update(int code_size, /* distinguish 723_40 with others */ + int y, /* quantizer step size */ + int wi, /* scale factor multiplier */ + int fi, /* for long/short term energies */ + int dq, /* quantized prediction difference */ + int sr, /* reconstructed signal */ + int dqsez, /* difference from 2-pole predictor */ + struct g72x_state* state_ptr) /* coder state pointer */ +{ + int cnt; + short mag, exp; /* Adaptive predictor, FLOAT A */ + short a2p = 0; /* LIMC */ + short a1ul; /* UPA1 */ + short pks1; /* UPA2 */ + short fa1; + char tr; /* tone/transition detector */ + short ylint, thr2, dqthr; + short ylfrac, thr1; + short pk0; + + pk0 = (dqsez < 0) ? 1 : 0; /* needed in updating predictor poles */ + + mag = dq & 0x7FFF; /* prediction difference magnitude */ + /* TRANS */ + ylint = state_ptr->yl >> 15; /* exponent part of yl */ + ylfrac = (state_ptr->yl >> 10) & 0x1F; /* fractional part of yl */ + thr1 = (32 + ylfrac) << ylint; /* threshold */ + thr2 = (ylint > 9) ? 31 << 10 : thr1; /* limit thr2 to 31 << 10 */ + dqthr = (thr2 + (thr2 >> 1)) >> 1; /* dqthr = 0.75 * thr2 */ + if (state_ptr->td == 0) /* signal supposed voice */ + tr = 0; + else if (mag <= dqthr) /* supposed data, but small mag */ + tr = 0; /* treated as voice */ + else /* signal is data (modem) */ + tr = 1; + + /* + * Quantizer scale factor adaptation. + */ + + /* FUNCTW & FILTD & DELAY */ + /* update non-steady state step size multiplier */ + state_ptr->yu = y + ((wi - y) >> 5); + + /* LIMB */ + if (state_ptr->yu < 544) /* 544 <= yu <= 5120 */ + state_ptr->yu = 544; + else if (state_ptr->yu > 5120) + state_ptr->yu = 5120; + + /* FILTE & DELAY */ + /* update steady state step size multiplier */ + state_ptr->yl += state_ptr->yu + ((-state_ptr->yl) >> 6); + + /* + * Adaptive predictor coefficients. + */ + if (tr == 1) { /* reset a's and b's for modem signal */ + state_ptr->a[0] = 0; + state_ptr->a[1] = 0; + state_ptr->b[0] = 0; + state_ptr->b[1] = 0; + state_ptr->b[2] = 0; + state_ptr->b[3] = 0; + state_ptr->b[4] = 0; + state_ptr->b[5] = 0; + } else { /* update a's and b's */ + pks1 = pk0 ^ state_ptr->pk[0]; /* UPA2 */ + + /* update predictor pole a[1] */ + a2p = state_ptr->a[1] - (state_ptr->a[1] >> 7); + if (dqsez != 0) { + fa1 = (pks1) ? state_ptr->a[0] : -state_ptr->a[0]; + if (fa1 < -8191) /* a2p = function of fa1 */ + a2p -= 0x100; + else if (fa1 > 8191) + a2p += 0xFF; + else + a2p += fa1 >> 5; + + if (pk0 ^ state_ptr->pk[1]) + /* LIMC */ + if (a2p <= -12160) + a2p = -12288; + else if (a2p >= 12416) + a2p = 12288; + else + a2p -= 0x80; + else if (a2p <= -12416) + a2p = -12288; + else if (a2p >= 12160) + a2p = 12288; + else + a2p += 0x80; + } + + /* TRIGB & DELAY */ + state_ptr->a[1] = a2p; + + /* UPA1 */ + /* update predictor pole a[0] */ + state_ptr->a[0] -= state_ptr->a[0] >> 8; + if (dqsez != 0) { + if (pks1 == 0) + state_ptr->a[0] += 192; + else + state_ptr->a[0] -= 192; + } + + /* LIMD */ + a1ul = 15360 - a2p; + if (state_ptr->a[0] < -a1ul) + state_ptr->a[0] = -a1ul; + else if (state_ptr->a[0] > a1ul) + state_ptr->a[0] = a1ul; + + /* UPB : update predictor zeros b[6] */ + for (cnt = 0; cnt < 6; cnt++) { + if (code_size == 5) /* for 40Kbps G.723 */ + state_ptr->b[cnt] -= state_ptr->b[cnt] >> 9; + else /* for G.721 and 24Kbps G.723 */ + state_ptr->b[cnt] -= state_ptr->b[cnt] >> 8; + if (dq & 0x7FFF) { /* XOR */ + if ((dq ^ state_ptr->dq[cnt]) >= 0) + state_ptr->b[cnt] += 128; + else + state_ptr->b[cnt] -= 128; + } + } + } + + for (cnt = 5; cnt > 0; cnt--) + state_ptr->dq[cnt] = state_ptr->dq[cnt - 1]; + /* FLOAT A : convert dq[0] to 4-bit exp, 6-bit mantissa f.p. */ + if (mag == 0) { + state_ptr->dq[0] = (dq >= 0) ? 0x20 : 0xFC20; + } else { + exp = quan(mag, power2, 15); + state_ptr->dq[0] = (dq >= 0) ? (exp << 6) + ((mag << 6) >> exp) + : (exp << 6) + ((mag << 6) >> exp) - 0x400; + } + + state_ptr->sr[1] = state_ptr->sr[0]; + /* FLOAT B : convert sr to 4-bit exp., 6-bit mantissa f.p. */ + if (sr == 0) { + state_ptr->sr[0] = 0x20; + } else if (sr > 0) { + exp = quan(sr, power2, 15); + state_ptr->sr[0] = (exp << 6) + ((sr << 6) >> exp); + } else if (sr > -32768) { + mag = -sr; + exp = quan(mag, power2, 15); + state_ptr->sr[0] = (exp << 6) + ((mag << 6) >> exp) - 0x400; + } else + state_ptr->sr[0] = 0xFC20; + + /* DELAY A */ + state_ptr->pk[1] = state_ptr->pk[0]; + state_ptr->pk[0] = pk0; + + /* TONE */ + if (tr == 1) /* this sample has been treated as data */ + state_ptr->td = 0; /* next one will be treated as voice */ + else if (a2p < -11776) /* small sample-to-sample correlation */ + state_ptr->td = 1; /* signal may be data */ + else /* signal is voice */ + state_ptr->td = 0; + + /* + * Adaptation speed control. + */ + state_ptr->dms += (fi - state_ptr->dms) >> 5; /* FILTA */ + state_ptr->dml += (((fi << 2) - state_ptr->dml) >> 7); /* FILTB */ + + if (tr == 1) + state_ptr->ap = 256; + else if (y < 1536) /* SUBTC */ + state_ptr->ap += (0x200 - state_ptr->ap) >> 4; + else if (state_ptr->td == 1) + state_ptr->ap += (0x200 - state_ptr->ap) >> 4; + else if (abs((state_ptr->dms << 2) - state_ptr->dml) >= (state_ptr->dml >> 3)) + state_ptr->ap += (0x200 - state_ptr->ap) >> 4; + else + state_ptr->ap += (-state_ptr->ap) >> 4; +} + + +/* + * g72x_init_state() + * + * This routine initializes and/or resets the g72x_state structure + * pointed to by 'state_ptr'. + * All the initial state values are specified in the CCITT G.721 document. + */ +void g72x_init_state(struct g72x_state* state_ptr) +{ + int cnta; + + state_ptr->yl = 34816; + state_ptr->yu = 544; + state_ptr->dms = 0; + state_ptr->dml = 0; + state_ptr->ap = 0; + for (cnta = 0; cnta < 2; cnta++) { + state_ptr->a[cnta] = 0; + state_ptr->pk[cnta] = 0; + state_ptr->sr[cnta] = 32; + } + for (cnta = 0; cnta < 6; cnta++) { + state_ptr->b[cnta] = 0; + state_ptr->dq[cnta] = 32; + } + state_ptr->td = 0; +} + +/* + * predictor_zero() + * + * computes the estimated signal from 6-zero predictor. + * + */ +int predictor_zero(struct g72x_state* state_ptr) +{ + int i; + int sezi; + + sezi = fmult(state_ptr->b[0] >> 2, state_ptr->dq[0]); + for (i = 1; i < 6; i++) /* ACCUM */ + sezi += fmult(state_ptr->b[i] >> 2, state_ptr->dq[i]); + return (sezi); +} +/* + * predictor_pole() + * + * computes the estimated signal from 2-pole predictor. + * + */ +int predictor_pole(struct g72x_state* state_ptr) +{ + return (fmult(state_ptr->a[1] >> 2, state_ptr->sr[1]) + + fmult(state_ptr->a[0] >> 2, state_ptr->sr[0])); +} +/* + * step_size() + * + * computes the quantization step size of the adaptive quantizer. + * + */ +int step_size(struct g72x_state* state_ptr) +{ + int y; + int dif; + int al; + + if (state_ptr->ap >= 256) + return (state_ptr->yu); + else { + y = state_ptr->yl >> 6; + dif = state_ptr->yu - y; + al = state_ptr->ap >> 2; + if (dif > 0) + y += (dif * al) >> 6; + else if (dif < 0) + y += (dif * al + 0x3F) >> 6; + return (y); + } +} + +/* + * quantize() + * + * Given a raw sample, 'd', of the difference signal and a + * quantization step size scale factor, 'y', this routine returns the + * ADPCM codeword to which that sample gets quantized. The step + * size scale factor division operation is done in the log base 2 domain + * as a subtraction. + */ +int quantize(int d, /* Raw difference signal sample */ + int y, /* Step size multiplier */ + short* table, /* quantization table */ + int size) /* table size of short integers */ +{ + short dqm; /* Magnitude of 'd' */ + short exp; /* Integer part of base 2 log of 'd' */ + short mant; /* Fractional part of base 2 log */ + short dl; /* Log of magnitude of 'd' */ + short dln; /* Step size scale factor normalized log */ + int i; + + /* + * LOG + * + * Compute base 2 log of 'd', and store in 'dl'. + */ + dqm = abs(d); + exp = quan(dqm >> 1, power2, 15); + mant = ((dqm << 7) >> exp) & 0x7F; /* Fractional portion. */ + dl = (exp << 7) + mant; + + /* + * SUBTB + * + * "Divide" by step size multiplier. + */ + dln = dl - (y >> 2); + + /* + * QUAN + * + * Obtain codword i for 'd'. + */ + i = quan(dln, table, size); + if (d < 0) /* take 1's complement of i */ + return ((size << 1) + 1 - i); + else if (i == 0) /* take 1's complement of 0 */ + return ((size << 1) + 1); /* new in 1988 */ + else + return (i); +} +/* + * reconstruct() + * + * Returns reconstructed difference signal 'dq' obtained from + * codeword 'i' and quantization step size scale factor 'y'. + * Multiplication is performed in log base 2 domain as addition. + */ +int reconstruct(int sign, /* 0 for non-negative value */ + int dqln, /* G.72x codeword */ + int y) /* Step size multiplier */ +{ + short dql; /* Log of 'dq' magnitude */ + short dex; /* Integer part of log */ + short dqt; + short dq; /* Reconstructed difference signal sample */ + + dql = dqln + (y >> 2); /* ADDA */ + + if (dql < 0) { + return ((sign) ? -0x8000 : 0); + } else { /* ANTILOG */ + dex = (dql >> 7) & 15; + dqt = 128 + (dql & 127); + dq = (dqt << 7) >> (14 - dex); + return ((sign) ? (dq - 0x8000) : dq); + } +} \ No newline at end of file diff --git a/src/Cafe/OS/libs/nsyshid/g721/g721.h b/src/Cafe/OS/libs/nsyshid/g721/g721.h new file mode 100644 index 00000000..c764e5fd --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/g721/g721.h @@ -0,0 +1,96 @@ +/* + * This source code is a product of Sun Microsystems, Inc. and is provided + * for unrestricted use. Users may copy or modify this source code without + * charge. + * + * SUN SOURCE CODE IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING + * THE WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR + * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. + * + * Sun source code is provided with no support and without any obligation on + * the part of Sun Microsystems, Inc. to assist in its use, correction, + * modification or enhancement. + * + * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE + * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY THIS SOFTWARE + * OR ANY PART THEREOF. + * + * In no event will Sun Microsystems, Inc. be liable for any lost revenue + * or profits or other special, indirect and consequential damages, even if + * Sun has been advised of the possibility of such damages. + * + * Sun Microsystems, Inc. + * 2550 Garcia Avenue + * Mountain View, California 94043 + */ + +/* + * g72x.h + * + * Header file for CCITT conversion routines. + * + */ +#ifndef _G72X_H +#define _G72X_H + +/* + * The following is the definition of the state structure + * used by the G.721/G.723 encoder and decoder to preserve their internal + * state between successive calls. The meanings of the majority + * of the state structure fields are explained in detail in the + * CCITT Recommendation G.721. The field names are essentially identical + * to variable names in the bit level description of the coding algorithm + * included in this Recommendation. + */ +struct g72x_state { + long yl; /* Locked or steady state step size multiplier. */ + short yu; /* Unlocked or non-steady state step size multiplier. */ + short dms; /* Short term energy estimate. */ + short dml; /* Long term energy estimate. */ + short ap; /* Linear weighting coefficient of 'yl' and 'yu'. */ + + short a[2]; /* Coefficients of pole portion of prediction filter. */ + short b[6]; /* Coefficients of zero portion of prediction filter. */ + short pk[2]; /* + * Signs of previous two samples of a partially + * reconstructed signal. + */ + short dq[6]; /* + * Previous 6 samples of the quantized difference + * signal represented in an internal floating point + * format. + */ + short sr[2]; /* + * Previous 2 samples of the quantized difference + * signal represented in an internal floating point + * format. + */ + char td; /* delayed tone detect, new in 1988 version */ +}; + +/* External function definitions. */ + +void g72x_init_state(struct g72x_state*); +int g721_encoder(int sample, struct g72x_state* state_ptr); +int g721_decoder(int code, struct g72x_state* state_ptr); + + +int quantize(int d, int y, short* table, int size); +int reconstruct(int, int, int); +void + + update(int code_size, + int y, + int wi, + int fi, + int dq, + int sr, + int dqsez, + struct g72x_state* state_ptr); + +int predictor_zero(struct g72x_state* state_ptr); + +int predictor_pole(struct g72x_state* state_ptr); +int step_size(struct g72x_state* state_ptr); +#endif /* !_G72X_H */ + diff --git a/src/Cafe/OS/libs/nsyshid/nsyshid.cpp b/src/Cafe/OS/libs/nsyshid/nsyshid.cpp index 2fe6da07..93ee8c3d 100644 --- a/src/Cafe/OS/libs/nsyshid/nsyshid.cpp +++ b/src/Cafe/OS/libs/nsyshid/nsyshid.cpp @@ -256,17 +256,17 @@ namespace nsyshid device->m_productId); } - bool FindDeviceById(uint16 vendorId, uint16 productId) + std::shared_ptr<Device> FindDeviceById(uint16 vendorId, uint16 productId) { std::lock_guard<std::recursive_mutex> lock(hidMutex); for (const auto& device : deviceList) { if (device->m_vendorId == vendorId && device->m_productId == productId) { - return true; + return device; } } - return false; + return nullptr; } void export_HIDAddClient(PPCInterpreter_t* hCPU) @@ -876,7 +876,7 @@ namespace nsyshid return nullptr; } - bool Backend::FindDeviceById(uint16 vendorId, uint16 productId) + std::shared_ptr<Device> Backend::FindDeviceById(uint16 vendorId, uint16 productId) { return nsyshid::FindDeviceById(vendorId, productId); } From de542410c248b589d8310a9443d0045045d7c9ec Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Thu, 8 May 2025 03:48:22 +0200 Subject: [PATCH 256/299] PPCRec: Rework floating point instructions (#1554) --- src/CMakeLists.txt | 1 - src/Cafe/CMakeLists.txt | 1 - src/Cafe/HW/Espresso/EspressoISA.h | 12 + src/Cafe/HW/Espresso/PPCTimer.cpp | 1 - .../Recompiler/BackendX64/BackendX64.cpp | 18 +- .../Recompiler/BackendX64/BackendX64.h | 2 + .../Recompiler/BackendX64/BackendX64FPU.cpp | 728 +----- .../BackendX64/BackendX64GenFPU.cpp | 41 + .../HW/Espresso/Recompiler/IML/IMLDebug.cpp | 8 +- .../Recompiler/IML/IMLInstruction.cpp | 174 +- .../Espresso/Recompiler/IML/IMLInstruction.h | 182 +- .../Espresso/Recompiler/IML/IMLOptimizer.cpp | 171 +- .../Recompiler/IML/IMLRegisterAllocator.cpp | 5 +- .../HW/Espresso/Recompiler/PPCRecompiler.cpp | 5 +- .../HW/Espresso/Recompiler/PPCRecompilerIml.h | 59 +- .../Recompiler/PPCRecompilerImlGen.cpp | 252 +- .../Recompiler/PPCRecompilerImlGenFPU.cpp | 2103 ++++++++--------- src/asm/CMakeLists.txt | 53 - src/asm/stub.cpp | 1 - src/asm/x64util.h | 20 - src/asm/x64util_masm.asm | 233 -- src/asm/x64util_nasm.asm | 237 -- 22 files changed, 1428 insertions(+), 2879 deletions(-) delete mode 100644 src/asm/CMakeLists.txt delete mode 100644 src/asm/stub.cpp delete mode 100644 src/asm/x64util.h delete mode 100644 src/asm/x64util_masm.asm delete mode 100644 src/asm/x64util_nasm.asm diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 79471321..ee7f8610 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -49,7 +49,6 @@ add_subdirectory(audio) add_subdirectory(util) add_subdirectory(imgui) add_subdirectory(resource) -add_subdirectory(asm) add_executable(CemuBin main.cpp diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index 34948f13..f4834260 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -548,7 +548,6 @@ else() endif() target_link_libraries(CemuCafe PRIVATE - CemuAsm CemuAudio CemuCommon CemuComponents diff --git a/src/Cafe/HW/Espresso/EspressoISA.h b/src/Cafe/HW/Espresso/EspressoISA.h index e66e1424..5e09763b 100644 --- a/src/Cafe/HW/Espresso/EspressoISA.h +++ b/src/Cafe/HW/Espresso/EspressoISA.h @@ -10,6 +10,18 @@ namespace Espresso CR_BIT_INDEX_SO = 3, }; + enum class PSQ_LOAD_TYPE + { + TYPE_F32 = 0, + TYPE_UNUSED1 = 1, + TYPE_UNUSED2 = 2, + TYPE_UNUSED3 = 3, + TYPE_U8 = 4, + TYPE_U16 = 5, + TYPE_S8 = 6, + TYPE_S16 = 7, + }; + enum class PrimaryOpcode { // underscore at the end of the name means that this instruction always updates CR0 (as if RC bit is set) diff --git a/src/Cafe/HW/Espresso/PPCTimer.cpp b/src/Cafe/HW/Espresso/PPCTimer.cpp index c27c94ee..257973a6 100644 --- a/src/Cafe/HW/Espresso/PPCTimer.cpp +++ b/src/Cafe/HW/Espresso/PPCTimer.cpp @@ -1,5 +1,4 @@ #include "Cafe/HW/Espresso/Const.h" -#include "asm/x64util.h" #include "config/ActiveSettings.h" #include "util/helpers/fspinlock.h" #include "util/highresolutiontimer/HighResolutionTimer.h" diff --git a/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64.cpp b/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64.cpp index 6a8aac2b..eadb80fb 100644 --- a/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64.cpp @@ -609,7 +609,7 @@ bool PPCRecompilerX64Gen_imlInstruction_r_r(PPCRecFunction_t* PPCRecFunction, pp } else { - debug_printf("PPCRecompilerX64Gen_imlInstruction_r_r(): Unsupported operation 0x%x\n", imlInstruction->operation); + cemuLog_logDebug(LogType::Force, "PPCRecompilerX64Gen_imlInstruction_r_r(): Unsupported operation 0x%x\n", imlInstruction->operation); return false; } return true; @@ -635,7 +635,7 @@ bool PPCRecompilerX64Gen_imlInstruction_r_s32(PPCRecFunction_t* PPCRecFunction, } else { - debug_printf("PPCRecompilerX64Gen_imlInstruction_r_s32(): Unsupported operation 0x%x\n", imlInstruction->operation); + cemuLog_logDebug(LogType::Force, "PPCRecompilerX64Gen_imlInstruction_r_s32(): Unsupported operation 0x%x\n", imlInstruction->operation); return false; } return true; @@ -894,7 +894,7 @@ bool PPCRecompilerX64Gen_imlInstruction_r_r_r(PPCRecFunction_t* PPCRecFunction, } else { - debug_printf("PPCRecompilerX64Gen_imlInstruction_r_r_r(): Unsupported operation 0x%x\n", imlInstruction->operation); + cemuLog_logDebug(LogType::Force, "PPCRecompilerX64Gen_imlInstruction_r_r_r(): Unsupported operation 0x%x\n", imlInstruction->operation); return false; } return true; @@ -1204,9 +1204,11 @@ void PPCRecompilerX64Gen_imlInstruction_r_name(PPCRecFunction_t* PPCRecFunction, else if (imlInstruction->op_r_name.regR.GetBaseFormat() == IMLRegFormat::F64) { auto regR = _regF64(imlInstruction->op_r_name.regR); - if (name >= PPCREC_NAME_FPR0 && name < (PPCREC_NAME_FPR0 + 32)) + if (name >= PPCREC_NAME_FPR_HALF && name < (PPCREC_NAME_FPR_HALF + 64)) { - x64Gen_movupd_xmmReg_memReg128(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, fpr) + sizeof(FPR_t) * (name - PPCREC_NAME_FPR0)); + sint32 regIndex = (name - PPCREC_NAME_FPR_HALF) / 2; + sint32 pairIndex = (name - PPCREC_NAME_FPR_HALF) % 2; + x64Gen_movsd_xmmReg_memReg64(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, fpr) + sizeof(FPR_t) * regIndex + pairIndex * sizeof(double)); } else if (name >= PPCREC_NAME_TEMPORARY_FPR0 || name < (PPCREC_NAME_TEMPORARY_FPR0 + 8)) { @@ -1281,9 +1283,11 @@ void PPCRecompilerX64Gen_imlInstruction_name_r(PPCRecFunction_t* PPCRecFunction, { auto regR = _regF64(imlInstruction->op_r_name.regR); uint32 name = imlInstruction->op_r_name.name; - if (name >= PPCREC_NAME_FPR0 && name < (PPCREC_NAME_FPR0 + 32)) + if (name >= PPCREC_NAME_FPR_HALF && name < (PPCREC_NAME_FPR_HALF + 64)) { - x64Gen_movupd_memReg128_xmmReg(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, fpr) + sizeof(FPR_t) * (name - PPCREC_NAME_FPR0)); + sint32 regIndex = (name - PPCREC_NAME_FPR_HALF) / 2; + sint32 pairIndex = (name - PPCREC_NAME_FPR_HALF) % 2; + x64Gen_movsd_memReg64_xmmReg(x64GenContext, regR, REG_RESV_HCPU, offsetof(PPCInterpreter_t, fpr) + sizeof(FPR_t) * regIndex + (pairIndex ? sizeof(double) : 0)); } else if (name >= PPCREC_NAME_TEMPORARY_FPR0 && name < (PPCREC_NAME_TEMPORARY_FPR0 + 8)) { diff --git a/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64.h b/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64.h index e4d1f5a9..de415ca9 100644 --- a/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64.h +++ b/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64.h @@ -205,6 +205,7 @@ void x64Gen_movddup_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegi void x64Gen_movhlps_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); void x64Gen_movsd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); void x64Gen_movsd_memReg64_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32); +void x64Gen_movsd_xmmReg_memReg64(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32); void x64Gen_movlpd_xmmReg_memReg64(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32); void x64Gen_unpcklpd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); void x64Gen_unpckhpd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); @@ -230,6 +231,7 @@ void x64Gen_andps_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegist void x64Gen_pcmpeqd_xmmReg_mem128Reg64(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, uint32 memReg, uint32 memImmS32); void x64Gen_cvttpd2dq_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); void x64Gen_cvttsd2si_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 registerDest, sint32 xmmRegisterSrc); +void x64Gen_cvtsi2sd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 registerSrc); void x64Gen_cvtsd2ss_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); void x64Gen_cvtpd2ps_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); void x64Gen_cvtss2sd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc); diff --git a/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64FPU.cpp b/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64FPU.cpp index 4d9a538d..dc07f9d0 100644 --- a/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64FPU.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64FPU.cpp @@ -3,8 +3,6 @@ #include "BackendX64.h" #include "Common/cpu_features.h" -#include "asm/x64util.h" // for recompiler_fres / frsqrte - uint32 _regF64(IMLReg physReg); uint32 _regI32(IMLReg r) @@ -34,231 +32,6 @@ static x86Assembler64::GPR8_REX _reg8_from_reg32(x86Assembler64::GPR32 regId) return (x86Assembler64::GPR8_REX)regId; } -void PPCRecompilerX64Gen_imlInstr_gqr_generateScaleCode(ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, sint32 registerXMM, bool isLoad, bool scalePS1, IMLReg registerGQR) -{ - // load GQR - x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, _regI32(registerGQR)); - // extract scale field and multiply by 16 to get array offset - x64Gen_shr_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, (isLoad?16:0)+8-4); - x64Gen_and_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, (0x3F<<4)); - // multiply xmm by scale - x64Gen_add_reg64_reg64(x64GenContext, REG_RESV_TEMP, REG_RESV_RECDATA); - if (isLoad) - { - if(scalePS1) - x64Gen_mulpd_xmmReg_memReg128(x64GenContext, registerXMM, REG_RESV_TEMP, offsetof(PPCRecompilerInstanceData_t, _psq_ld_scale_ps0_ps1)); - else - x64Gen_mulpd_xmmReg_memReg128(x64GenContext, registerXMM, REG_RESV_TEMP, offsetof(PPCRecompilerInstanceData_t, _psq_ld_scale_ps0_1)); - } - else - { - if (scalePS1) - x64Gen_mulpd_xmmReg_memReg128(x64GenContext, registerXMM, REG_RESV_TEMP, offsetof(PPCRecompilerInstanceData_t, _psq_st_scale_ps0_ps1)); - else - x64Gen_mulpd_xmmReg_memReg128(x64GenContext, registerXMM, REG_RESV_TEMP, offsetof(PPCRecompilerInstanceData_t, _psq_st_scale_ps0_1)); - } -} - -// generate code for PSQ load for a particular type -// if scaleGQR is -1 then a scale of 1.0 is assumed (no scale) -void PPCRecompilerX64Gen_imlInstr_psq_load(ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, uint8 mode, sint32 registerXMM, sint32 memReg, sint32 memRegEx, sint32 memImmS32, bool indexed, IMLReg registerGQR = IMLREG_INVALID) -{ - if (mode == PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0_PS1) - { - if (indexed) - { - assert_dbg(); - } - // optimized code for ps float load - x64Emit_mov_reg64_mem64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, memReg, memImmS32); - x64GenContext->emitter->BSWAP_q(REG_RESV_TEMP); - x64Gen_rol_reg64_imm8(x64GenContext, REG_RESV_TEMP, 32); // swap upper and lower DWORD - x64Gen_movq_xmmReg_reg64(x64GenContext, registerXMM, REG_RESV_TEMP); - x64Gen_cvtps2pd_xmmReg_xmmReg(x64GenContext, registerXMM, registerXMM); - // note: floats are not scaled - } - else if (mode == PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0) - { - if (indexed) - { - x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, memRegEx); - x64Gen_add_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, memReg); - if (g_CPUFeatures.x86.movbe) - { - x64Gen_movBEZeroExtend_reg64_mem32Reg64PlusReg64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, REG_RESV_TEMP, memImmS32); - } - else - { - x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, REG_RESV_TEMP, memImmS32); - x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); - } - } - else - { - if (g_CPUFeatures.x86.movbe) - { - x64Gen_movBEZeroExtend_reg64_mem32Reg64PlusReg64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, memReg, memImmS32); - } - else - { - x64Emit_mov_reg32_mem32(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, memReg, memImmS32); - x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); - } - } - if (g_CPUFeatures.x86.avx) - { - x64Gen_movd_xmmReg_reg64Low32(x64GenContext, REG_RESV_FPR_TEMP, REG_RESV_TEMP); - } - else - { - x64Emit_mov_mem32_reg64(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR), REG_RESV_TEMP); - x64Gen_movddup_xmmReg_memReg64(x64GenContext, REG_RESV_FPR_TEMP, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryFPR)); - } - x64Gen_cvtss2sd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, REG_RESV_FPR_TEMP); - // load constant 1.0 into lower half and upper half of temp register - x64Gen_movddup_xmmReg_memReg64(x64GenContext, registerXMM, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_constDouble1_1)); - // overwrite lower half with single from memory - x64Gen_movsd_xmmReg_xmmReg(x64GenContext, registerXMM, REG_RESV_FPR_TEMP); - // note: floats are not scaled - } - else - { - sint32 readSize; - bool isSigned = false; - if (mode == PPCREC_FPR_LD_MODE_PSQ_S16_PS0 || - mode == PPCREC_FPR_LD_MODE_PSQ_S16_PS0_PS1) - { - readSize = 16; - isSigned = true; - } - else if (mode == PPCREC_FPR_LD_MODE_PSQ_U16_PS0 || - mode == PPCREC_FPR_LD_MODE_PSQ_U16_PS0_PS1) - { - readSize = 16; - isSigned = false; - } - else if (mode == PPCREC_FPR_LD_MODE_PSQ_S8_PS0 || - mode == PPCREC_FPR_LD_MODE_PSQ_S8_PS0_PS1) - { - readSize = 8; - isSigned = true; - } - else if (mode == PPCREC_FPR_LD_MODE_PSQ_U8_PS0 || - mode == PPCREC_FPR_LD_MODE_PSQ_U8_PS0_PS1) - { - readSize = 8; - isSigned = false; - } - else - assert_dbg(); - - bool loadPS1 = (mode == PPCREC_FPR_LD_MODE_PSQ_S16_PS0_PS1 || - mode == PPCREC_FPR_LD_MODE_PSQ_U16_PS0_PS1 || - mode == PPCREC_FPR_LD_MODE_PSQ_U8_PS0_PS1 || - mode == PPCREC_FPR_LD_MODE_PSQ_S8_PS0_PS1); - for (sint32 wordIndex = 0; wordIndex < 2; wordIndex++) - { - if (indexed) - { - assert_dbg(); - } - // read from memory - if (wordIndex == 1 && loadPS1 == false) - { - // store constant 1 - x64Gen_mov_mem32Reg64_imm32(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryGPR) + sizeof(uint32) * 1, 1); - } - else - { - uint32 memOffset = memImmS32 + wordIndex * (readSize / 8); - if (readSize == 16) - { - // half word - x64Gen_movZeroExtend_reg64Low16_mem16Reg64PlusReg64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, memReg, memOffset); - x64Gen_rol_reg64Low16_imm8(x64GenContext, REG_RESV_TEMP, 8); // endian swap - if (isSigned) - x64Gen_movSignExtend_reg64Low32_reg64Low16(x64GenContext, REG_RESV_TEMP, REG_RESV_TEMP); - else - x64Gen_movZeroExtend_reg64Low32_reg64Low16(x64GenContext, REG_RESV_TEMP, REG_RESV_TEMP); - } - else if (readSize == 8) - { - // byte - x64Emit_mov_reg64b_mem8(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, memReg, memOffset); - if (isSigned) - x64Gen_movSignExtend_reg64Low32_reg64Low8(x64GenContext, REG_RESV_TEMP, REG_RESV_TEMP); - else - x64Gen_movZeroExtend_reg64Low32_reg64Low8(x64GenContext, REG_RESV_TEMP, REG_RESV_TEMP); - } - // store - x64Emit_mov_mem32_reg32(x64GenContext, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryGPR) + sizeof(uint32) * wordIndex, REG_RESV_TEMP); - } - } - // convert the two integers to doubles - x64Gen_cvtpi2pd_xmmReg_mem64Reg64(x64GenContext, registerXMM, REG_RESV_HCPU, offsetof(PPCInterpreter_t, temporaryGPR)); - // scale - if (registerGQR.IsValid()) - PPCRecompilerX64Gen_imlInstr_gqr_generateScaleCode(ppcImlGenContext, x64GenContext, registerXMM, true, loadPS1, registerGQR); - } -} - -void PPCRecompilerX64Gen_imlInstr_psq_load_generic(ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, uint8 mode, sint32 registerXMM, sint32 memReg, sint32 memRegEx, sint32 memImmS32, bool indexed, IMLReg registerGQR) -{ - bool loadPS1 = (mode == PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1); - // load GQR - x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, _regI32(registerGQR)); - // extract load type field - x64Gen_shr_reg64Low32_imm8(x64GenContext, REG_RESV_TEMP, 16); - x64Gen_and_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 7); - // jump cases - x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 4); // type 4 -> u8 - sint32 jumpOffset_caseU8 = x64GenContext->emitter->GetWriteIndex(); - x64Gen_jmpc_far(x64GenContext, X86_CONDITION_EQUAL, 0); - x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 5); // type 5 -> u16 - sint32 jumpOffset_caseU16 = x64GenContext->emitter->GetWriteIndex(); - x64Gen_jmpc_far(x64GenContext, X86_CONDITION_EQUAL, 0); - x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 6); // type 4 -> s8 - sint32 jumpOffset_caseS8 = x64GenContext->emitter->GetWriteIndex(); - x64Gen_jmpc_far(x64GenContext, X86_CONDITION_EQUAL, 0); - x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 7); // type 5 -> s16 - sint32 jumpOffset_caseS16 = x64GenContext->emitter->GetWriteIndex(); - x64Gen_jmpc_far(x64GenContext, X86_CONDITION_EQUAL, 0); - // default case -> float - - // generate cases - uint32 jumpOffset_endOfFloat; - uint32 jumpOffset_endOfU8; - uint32 jumpOffset_endOfU16; - uint32 jumpOffset_endOfS8; - - PPCRecompilerX64Gen_imlInstr_psq_load(ppcImlGenContext, x64GenContext, loadPS1 ? PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0_PS1 : PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0, registerXMM, memReg, memRegEx, memImmS32, indexed, registerGQR); - jumpOffset_endOfFloat = x64GenContext->emitter->GetWriteIndex(); - x64Gen_jmp_imm32(x64GenContext, 0); - - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_caseU16, x64GenContext->emitter->GetWriteIndex()); - PPCRecompilerX64Gen_imlInstr_psq_load(ppcImlGenContext, x64GenContext, loadPS1 ? PPCREC_FPR_LD_MODE_PSQ_U16_PS0_PS1 : PPCREC_FPR_LD_MODE_PSQ_U16_PS0, registerXMM, memReg, memRegEx, memImmS32, indexed, registerGQR); - jumpOffset_endOfU8 = x64GenContext->emitter->GetWriteIndex(); - x64Gen_jmp_imm32(x64GenContext, 0); - - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_caseS16, x64GenContext->emitter->GetWriteIndex()); - PPCRecompilerX64Gen_imlInstr_psq_load(ppcImlGenContext, x64GenContext, loadPS1 ? PPCREC_FPR_LD_MODE_PSQ_S16_PS0_PS1 : PPCREC_FPR_LD_MODE_PSQ_S16_PS0, registerXMM, memReg, memRegEx, memImmS32, indexed, registerGQR); - jumpOffset_endOfU16 = x64GenContext->emitter->GetWriteIndex(); - x64Gen_jmp_imm32(x64GenContext, 0); - - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_caseU8, x64GenContext->emitter->GetWriteIndex()); - PPCRecompilerX64Gen_imlInstr_psq_load(ppcImlGenContext, x64GenContext, loadPS1 ? PPCREC_FPR_LD_MODE_PSQ_U8_PS0_PS1 : PPCREC_FPR_LD_MODE_PSQ_U8_PS0, registerXMM, memReg, memRegEx, memImmS32, indexed, registerGQR); - jumpOffset_endOfS8 = x64GenContext->emitter->GetWriteIndex(); - x64Gen_jmp_imm32(x64GenContext, 0); - - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_caseS8, x64GenContext->emitter->GetWriteIndex()); - PPCRecompilerX64Gen_imlInstr_psq_load(ppcImlGenContext, x64GenContext, loadPS1 ? PPCREC_FPR_LD_MODE_PSQ_S8_PS0_PS1 : PPCREC_FPR_LD_MODE_PSQ_S8_PS0, registerXMM, memReg, memRegEx, memImmS32, indexed, registerGQR); - - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_endOfFloat, x64GenContext->emitter->GetWriteIndex()); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_endOfU8, x64GenContext->emitter->GetWriteIndex()); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_endOfU16, x64GenContext->emitter->GetWriteIndex()); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_endOfS8, x64GenContext->emitter->GetWriteIndex()); -} - // load from memory bool PPCRecompilerX64Gen_imlInstruction_fpr_load(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction, bool indexed) { @@ -269,7 +42,7 @@ bool PPCRecompilerX64Gen_imlInstruction_fpr_load(PPCRecFunction_t* PPCRecFunctio realRegisterMem2 = _regI32(imlInstruction->op_storeLoad.registerMem2); uint8 mode = imlInstruction->op_storeLoad.mode; - if( mode == PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1 ) + if( mode == PPCREC_FPR_LD_MODE_SINGLE ) { // load byte swapped single into temporary FPR if( indexed ) @@ -299,10 +72,9 @@ bool PPCRecompilerX64Gen_imlInstruction_fpr_load(PPCRecFunction_t* PPCRecFunctio else { x64Gen_cvtss2sd_xmmReg_xmmReg(x64GenContext, realRegisterXMM, realRegisterXMM); - x64Gen_movddup_xmmReg_xmmReg(x64GenContext, realRegisterXMM, realRegisterXMM); - } + } } - else if( mode == PPCREC_FPR_LD_MODE_DOUBLE_INTO_PS0 ) + else if( mode == PPCREC_FPR_LD_MODE_DOUBLE ) { if( g_CPUFeatures.x86.avx ) { @@ -361,25 +133,6 @@ bool PPCRecompilerX64Gen_imlInstruction_fpr_load(PPCRecFunction_t* PPCRecFunctio } } } - else if (mode == PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0_PS1 || - mode == PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0 || - mode == PPCREC_FPR_LD_MODE_PSQ_S16_PS0 || - mode == PPCREC_FPR_LD_MODE_PSQ_S16_PS0_PS1 || - mode == PPCREC_FPR_LD_MODE_PSQ_S16_PS0 || - mode == PPCREC_FPR_LD_MODE_PSQ_U16_PS0 || - mode == PPCREC_FPR_LD_MODE_PSQ_U16_PS0_PS1 || - mode == PPCREC_FPR_LD_MODE_PSQ_S8_PS0 || - mode == PPCREC_FPR_LD_MODE_PSQ_S8_PS0_PS1 || - mode == PPCREC_FPR_LD_MODE_PSQ_S8_PS0 || - mode == PPCREC_FPR_LD_MODE_PSQ_U8_PS0_PS1 ) - { - PPCRecompilerX64Gen_imlInstr_psq_load(ppcImlGenContext, x64GenContext, mode, realRegisterXMM, realRegisterMem, realRegisterMem2, imlInstruction->op_storeLoad.immS32, indexed); - } - else if (mode == PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1 || - mode == PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0) - { - PPCRecompilerX64Gen_imlInstr_psq_load_generic(ppcImlGenContext, x64GenContext, mode, realRegisterXMM, realRegisterMem, realRegisterMem2, imlInstruction->op_storeLoad.immS32, indexed, imlInstruction->op_storeLoad.registerGQR); - } else { return false; @@ -387,188 +140,6 @@ bool PPCRecompilerX64Gen_imlInstruction_fpr_load(PPCRecFunction_t* PPCRecFunctio return true; } -void PPCRecompilerX64Gen_imlInstr_psq_store(ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, uint8 mode, sint32 registerXMM, sint32 memReg, sint32 memRegEx, sint32 memImmS32, bool indexed, IMLReg registerGQR = IMLREG_INVALID) -{ - bool storePS1 = (mode == PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0_PS1 || - mode == PPCREC_FPR_ST_MODE_PSQ_S8_PS0_PS1 || - mode == PPCREC_FPR_ST_MODE_PSQ_U8_PS0_PS1 || - mode == PPCREC_FPR_ST_MODE_PSQ_U16_PS0_PS1 || - mode == PPCREC_FPR_ST_MODE_PSQ_S16_PS0_PS1); - bool isFloat = mode == PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0 || mode == PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0_PS1; - if (registerGQR.IsValid()) - { - // move to temporary xmm and update registerXMM - x64Gen_movaps_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, registerXMM); - registerXMM = REG_RESV_FPR_TEMP; - // apply scale - if(isFloat == false) - PPCRecompilerX64Gen_imlInstr_gqr_generateScaleCode(ppcImlGenContext, x64GenContext, registerXMM, false, storePS1, registerGQR); - } - if (mode == PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0) - { - x64Gen_cvtsd2ss_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, registerXMM); - x64Gen_movd_reg64Low32_xmmReg(x64GenContext, REG_RESV_TEMP, REG_RESV_FPR_TEMP); - if (g_CPUFeatures.x86.movbe == false) - x64Gen_bswap_reg64Lower32bit(x64GenContext, REG_RESV_TEMP); - if (indexed) - { - cemu_assert_debug(memReg != memRegEx); - x64Gen_add_reg64Low32_reg64Low32(x64GenContext, memReg, memRegEx); - } - if (g_CPUFeatures.x86.movbe) - x64Gen_movBETruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, memReg, memImmS32, REG_RESV_TEMP); - else - x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, memReg, memImmS32, REG_RESV_TEMP); - if (indexed) - { - x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, memReg, memRegEx); - } - return; - } - else if (mode == PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0_PS1) - { - if (indexed) - assert_dbg(); // todo - x64Gen_cvtpd2ps_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, registerXMM); - x64Gen_movq_reg64_xmmReg(x64GenContext, REG_RESV_TEMP, REG_RESV_FPR_TEMP); - x64Gen_rol_reg64_imm8(x64GenContext, REG_RESV_TEMP, 32); // swap upper and lower DWORD - x64GenContext->emitter->BSWAP_q(REG_RESV_TEMP); - x64Gen_mov_mem64Reg64PlusReg64_reg64(x64GenContext, REG_RESV_TEMP, REG_RESV_MEMBASE, memReg, memImmS32); - return; - } - // store as integer - // get limit from mode - sint32 clampMin, clampMax; - sint32 bitWriteSize; - if (mode == PPCREC_FPR_ST_MODE_PSQ_S8_PS0 || - mode == PPCREC_FPR_ST_MODE_PSQ_S8_PS0_PS1 ) - { - clampMin = -128; - clampMax = 127; - bitWriteSize = 8; - } - else if (mode == PPCREC_FPR_ST_MODE_PSQ_U8_PS0 || - mode == PPCREC_FPR_ST_MODE_PSQ_U8_PS0_PS1 ) - { - clampMin = 0; - clampMax = 255; - bitWriteSize = 8; - } - else if (mode == PPCREC_FPR_ST_MODE_PSQ_U16_PS0 || - mode == PPCREC_FPR_ST_MODE_PSQ_U16_PS0_PS1 ) - { - clampMin = 0; - clampMax = 0xFFFF; - bitWriteSize = 16; - } - else if (mode == PPCREC_FPR_ST_MODE_PSQ_S16_PS0 || - mode == PPCREC_FPR_ST_MODE_PSQ_S16_PS0_PS1 ) - { - clampMin = -32768; - clampMax = 32767; - bitWriteSize = 16; - } - else - { - cemu_assert(false); - } - for (sint32 valueIndex = 0; valueIndex < (storePS1?2:1); valueIndex++) - { - // todo - multiply by GQR scale - if (valueIndex == 0) - { - // convert low half (PS0) to integer - x64Gen_cvttsd2si_reg64Low_xmmReg(x64GenContext, REG_RESV_TEMP, registerXMM); - } - else - { - // load top half (PS1) into bottom half of temporary register - x64Gen_movhlps_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, registerXMM); - // convert low half to integer - x64Gen_cvttsd2si_reg64Low_xmmReg(x64GenContext, REG_RESV_TEMP, REG_RESV_FPR_TEMP); - } - // max(i, -clampMin) - x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, clampMin); - sint32 jumpInstructionOffset1 = x64GenContext->emitter->GetWriteIndex(); - x64Gen_jmpc_near(x64GenContext, X86_CONDITION_SIGNED_GREATER_EQUAL, 0); - x64Gen_mov_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, clampMin); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset1, x64GenContext->emitter->GetWriteIndex()); - // min(i, clampMax) - x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, clampMax); - sint32 jumpInstructionOffset2 = x64GenContext->emitter->GetWriteIndex(); - x64Gen_jmpc_near(x64GenContext, X86_CONDITION_SIGNED_LESS_EQUAL, 0); - x64Gen_mov_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, clampMax); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset2, x64GenContext->emitter->GetWriteIndex()); - // endian swap - if( bitWriteSize == 16) - x64Gen_rol_reg64Low16_imm8(x64GenContext, REG_RESV_TEMP, 8); - // write to memory - if (indexed) - assert_dbg(); // unsupported - sint32 memOffset = memImmS32 + valueIndex * (bitWriteSize/8); - if (bitWriteSize == 8) - x64Gen_movTruncate_mem8Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, memReg, memOffset, REG_RESV_TEMP); - else if (bitWriteSize == 16) - x64Gen_movTruncate_mem16Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, memReg, memOffset, REG_RESV_TEMP); - } -} - -void PPCRecompilerX64Gen_imlInstr_psq_store_generic(ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, uint8 mode, sint32 registerXMM, sint32 memReg, sint32 memRegEx, sint32 memImmS32, bool indexed, IMLReg registerGQR) -{ - bool storePS1 = (mode == PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1); - // load GQR - x64Gen_mov_reg64_reg64(x64GenContext, REG_RESV_TEMP, _regI32(registerGQR)); - // extract store type field - x64Gen_and_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 7); - // jump cases - x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 4); // type 4 -> u8 - sint32 jumpOffset_caseU8 = x64GenContext->emitter->GetWriteIndex(); - x64Gen_jmpc_far(x64GenContext, X86_CONDITION_EQUAL, 0); - x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 5); // type 5 -> u16 - sint32 jumpOffset_caseU16 = x64GenContext->emitter->GetWriteIndex(); - x64Gen_jmpc_far(x64GenContext, X86_CONDITION_EQUAL, 0); - x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 6); // type 4 -> s8 - sint32 jumpOffset_caseS8 = x64GenContext->emitter->GetWriteIndex(); - x64Gen_jmpc_far(x64GenContext, X86_CONDITION_EQUAL, 0); - x64Gen_cmp_reg64Low32_imm32(x64GenContext, REG_RESV_TEMP, 7); // type 5 -> s16 - sint32 jumpOffset_caseS16 = x64GenContext->emitter->GetWriteIndex(); - x64Gen_jmpc_far(x64GenContext, X86_CONDITION_EQUAL, 0); - // default case -> float - - // generate cases - uint32 jumpOffset_endOfFloat; - uint32 jumpOffset_endOfU8; - uint32 jumpOffset_endOfU16; - uint32 jumpOffset_endOfS8; - - PPCRecompilerX64Gen_imlInstr_psq_store(ppcImlGenContext, x64GenContext, storePS1 ? PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0_PS1 : PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0, registerXMM, memReg, memRegEx, memImmS32, indexed, registerGQR); - jumpOffset_endOfFloat = x64GenContext->emitter->GetWriteIndex(); - x64Gen_jmp_imm32(x64GenContext, 0); - - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_caseU16, x64GenContext->emitter->GetWriteIndex()); - PPCRecompilerX64Gen_imlInstr_psq_store(ppcImlGenContext, x64GenContext, storePS1 ? PPCREC_FPR_ST_MODE_PSQ_U16_PS0_PS1 : PPCREC_FPR_ST_MODE_PSQ_U16_PS0, registerXMM, memReg, memRegEx, memImmS32, indexed, registerGQR); - jumpOffset_endOfU8 = x64GenContext->emitter->GetWriteIndex(); - x64Gen_jmp_imm32(x64GenContext, 0); - - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_caseS16, x64GenContext->emitter->GetWriteIndex()); - PPCRecompilerX64Gen_imlInstr_psq_store(ppcImlGenContext, x64GenContext, storePS1 ? PPCREC_FPR_ST_MODE_PSQ_S16_PS0_PS1 : PPCREC_FPR_ST_MODE_PSQ_S16_PS0, registerXMM, memReg, memRegEx, memImmS32, indexed, registerGQR); - jumpOffset_endOfU16 = x64GenContext->emitter->GetWriteIndex(); - x64Gen_jmp_imm32(x64GenContext, 0); - - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_caseU8, x64GenContext->emitter->GetWriteIndex()); - PPCRecompilerX64Gen_imlInstr_psq_store(ppcImlGenContext, x64GenContext, storePS1 ? PPCREC_FPR_ST_MODE_PSQ_U8_PS0_PS1 : PPCREC_FPR_ST_MODE_PSQ_U8_PS0, registerXMM, memReg, memRegEx, memImmS32, indexed, registerGQR); - jumpOffset_endOfS8 = x64GenContext->emitter->GetWriteIndex(); - x64Gen_jmp_imm32(x64GenContext, 0); - - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_caseS8, x64GenContext->emitter->GetWriteIndex()); - PPCRecompilerX64Gen_imlInstr_psq_store(ppcImlGenContext, x64GenContext, storePS1 ? PPCREC_FPR_ST_MODE_PSQ_S8_PS0_PS1 : PPCREC_FPR_ST_MODE_PSQ_S8_PS0, registerXMM, memReg, memRegEx, memImmS32, indexed, registerGQR); - - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_endOfFloat, x64GenContext->emitter->GetWriteIndex()); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_endOfU8, x64GenContext->emitter->GetWriteIndex()); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_endOfU16, x64GenContext->emitter->GetWriteIndex()); - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpOffset_endOfS8, x64GenContext->emitter->GetWriteIndex()); -} - // store to memory bool PPCRecompilerX64Gen_imlInstruction_fpr_store(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction, bool indexed) { @@ -578,7 +149,7 @@ bool PPCRecompilerX64Gen_imlInstruction_fpr_store(PPCRecFunction_t* PPCRecFuncti if( indexed ) realRegisterMem2 = _regI32(imlInstruction->op_storeLoad.registerMem2); uint8 mode = imlInstruction->op_storeLoad.mode; - if( mode == PPCREC_FPR_ST_MODE_SINGLE_FROM_PS0 ) + if( mode == PPCREC_FPR_ST_MODE_SINGLE ) { if (imlInstruction->op_storeLoad.flags2.notExpanded) { @@ -607,7 +178,7 @@ bool PPCRecompilerX64Gen_imlInstruction_fpr_store(PPCRecFunction_t* PPCRecFuncti x64Gen_sub_reg64Low32_reg64Low32(x64GenContext, realRegisterMem, realRegisterMem2); } } - else if( mode == PPCREC_FPR_ST_MODE_DOUBLE_FROM_PS0 ) + else if( mode == PPCREC_FPR_ST_MODE_DOUBLE ) { if( indexed ) { @@ -645,192 +216,61 @@ bool PPCRecompilerX64Gen_imlInstruction_fpr_store(PPCRecFunction_t* PPCRecFuncti x64Gen_movTruncate_mem32Reg64PlusReg64_reg64(x64GenContext, REG_RESV_MEMBASE, realRegisterMem, imlInstruction->op_storeLoad.immS32, REG_RESV_TEMP); } } - else if(mode == PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0_PS1 || - mode == PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0 || - mode == PPCREC_FPR_ST_MODE_PSQ_S8_PS0 || - mode == PPCREC_FPR_ST_MODE_PSQ_S8_PS0_PS1 || - mode == PPCREC_FPR_ST_MODE_PSQ_U8_PS0 || - mode == PPCREC_FPR_ST_MODE_PSQ_U8_PS0_PS1 || - mode == PPCREC_FPR_ST_MODE_PSQ_S16_PS0 || - mode == PPCREC_FPR_ST_MODE_PSQ_S16_PS0_PS1 || - mode == PPCREC_FPR_ST_MODE_PSQ_U16_PS0 || - mode == PPCREC_FPR_ST_MODE_PSQ_U16_PS0_PS1 ) - { - cemu_assert_debug(imlInstruction->op_storeLoad.flags2.notExpanded == false); - PPCRecompilerX64Gen_imlInstr_psq_store(ppcImlGenContext, x64GenContext, mode, realRegisterXMM, realRegisterMem, realRegisterMem2, imlInstruction->op_storeLoad.immS32, indexed); - } - else if (mode == PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1 || - mode == PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0) - { - PPCRecompilerX64Gen_imlInstr_psq_store_generic(ppcImlGenContext, x64GenContext, mode, realRegisterXMM, realRegisterMem, realRegisterMem2, imlInstruction->op_storeLoad.immS32, indexed, imlInstruction->op_storeLoad.registerGQR); - } else { - if( indexed ) - assert_dbg(); // todo debug_printf("PPCRecompilerX64Gen_imlInstruction_fpr_store(): Unsupported mode %d\n", mode); return false; } return true; } -void _swapPS0PS1(x64GenContext_t* x64GenContext, sint32 xmmReg) -{ - x64Gen_shufpd_xmmReg_xmmReg_imm8(x64GenContext, xmmReg, xmmReg, 1); -} - // FPR op FPR void PPCRecompilerX64Gen_imlInstruction_fpr_r_r(PPCRecFunction_t* PPCRecFunction, ppcImlGenContext_t* ppcImlGenContext, x64GenContext_t* x64GenContext, IMLInstruction* imlInstruction) { + if( imlInstruction->operation == PPCREC_IML_OP_FPR_FLOAT_TO_INT ) + { + uint32 regGpr = _regI32(imlInstruction->op_fpr_r_r.regR); + uint32 regFpr = _regF64(imlInstruction->op_fpr_r_r.regA); + x64Gen_cvttsd2si_reg64Low_xmmReg(x64GenContext, regGpr, regFpr); + return; + } + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_INT_TO_FLOAT ) + { + uint32 regFpr = _regF64(imlInstruction->op_fpr_r_r.regR); + uint32 regGpr = _regI32(imlInstruction->op_fpr_r_r.regA); + x64Gen_cvtsi2sd_xmmReg_xmmReg(x64GenContext, regFpr, regGpr); + return; + } + uint32 regR = _regF64(imlInstruction->op_fpr_r_r.regR); uint32 regA = _regF64(imlInstruction->op_fpr_r_r.regA); - - if( imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP ) - { - x64Gen_movddup_xmmReg_xmmReg(x64GenContext, regR, regA); - } - else if( imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM_AND_TOP ) - { - // VPUNPCKHQDQ - if (regR == regA) - { - // unpack top to bottom and top - x64Gen_unpckhpd_xmmReg_xmmReg(x64GenContext, regR, regA); - } - //else if ( hasAVXSupport ) - //{ - // // unpack top to bottom and top with non-destructive destination - // // update: On Ivy Bridge this causes weird stalls? - // x64Gen_avx_VUNPCKHPD_xmm_xmm_xmm(x64GenContext, registerResult, registerOperand, registerOperand); - //} - else - { - // move top to bottom - x64Gen_movhlps_xmmReg_xmmReg(x64GenContext, regR, regA); - // duplicate bottom - x64Gen_movddup_xmmReg_xmmReg(x64GenContext, regR, regR); - } - - } - else if( imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM ) + if( imlInstruction->operation == PPCREC_IML_OP_FPR_ASSIGN ) { x64Gen_movsd_xmmReg_xmmReg(x64GenContext, regR, regA); } - else if( imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_TOP ) - { - x64Gen_unpcklpd_xmmReg_xmmReg(x64GenContext, regR, regA); - } - else if( imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_BOTTOM_AND_TOP_SWAPPED ) - { - if( regR != regA ) - x64Gen_movaps_xmmReg_xmmReg(x64GenContext, regR, regA); - _swapPS0PS1(x64GenContext, regR); - } - else if( imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_TOP_TO_TOP ) - { - x64Gen_shufpd_xmmReg_xmmReg_imm8(x64GenContext, regR, regA, 2); - } - else if( imlInstruction->operation == PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM ) - { - // use unpckhpd here? - x64Gen_shufpd_xmmReg_xmmReg_imm8(x64GenContext, regR, regA, 3); - _swapPS0PS1(x64GenContext, regR); - } - else if( imlInstruction->operation == PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM ) + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_MULTIPLY ) { x64Gen_mulsd_xmmReg_xmmReg(x64GenContext, regR, regA); } - else if( imlInstruction->operation == PPCREC_IML_OP_FPR_MULTIPLY_PAIR ) - { - x64Gen_mulpd_xmmReg_xmmReg(x64GenContext, regR, regA); - } - else if( imlInstruction->operation == PPCREC_IML_OP_FPR_DIVIDE_BOTTOM ) + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_DIVIDE ) { x64Gen_divsd_xmmReg_xmmReg(x64GenContext, regR, regA); } - else if (imlInstruction->operation == PPCREC_IML_OP_FPR_DIVIDE_PAIR) - { - x64Gen_divpd_xmmReg_xmmReg(x64GenContext, regR, regA); - } - else if( imlInstruction->operation == PPCREC_IML_OP_FPR_ADD_BOTTOM ) + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_ADD ) { x64Gen_addsd_xmmReg_xmmReg(x64GenContext, regR, regA); } - else if( imlInstruction->operation == PPCREC_IML_OP_FPR_ADD_PAIR ) - { - x64Gen_addpd_xmmReg_xmmReg(x64GenContext, regR, regA); - } - else if( imlInstruction->operation == PPCREC_IML_OP_FPR_SUB_PAIR ) - { - x64Gen_subpd_xmmReg_xmmReg(x64GenContext, regR, regA); - } - else if( imlInstruction->operation == PPCREC_IML_OP_FPR_SUB_BOTTOM ) + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_SUB ) { x64Gen_subsd_xmmReg_xmmReg(x64GenContext, regR, regA); } - else if( imlInstruction->operation == PPCREC_IML_OP_ASSIGN ) - { - x64Gen_movaps_xmmReg_xmmReg(x64GenContext, regR, regA); - } - else if( imlInstruction->operation == PPCREC_IML_OP_FPR_BOTTOM_FCTIWZ ) + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_FCTIWZ ) { x64Gen_cvttsd2si_xmmReg_xmmReg(x64GenContext, REG_RESV_TEMP, regA); x64Gen_mov_reg64Low32_reg64Low32(x64GenContext, REG_RESV_TEMP, REG_RESV_TEMP); // move to FPR register x64Gen_movq_xmmReg_reg64(x64GenContext, regR, REG_RESV_TEMP); } - else if (imlInstruction->operation == PPCREC_IML_OP_FPR_BOTTOM_RECIPROCAL_SQRT) - { - // move register to XMM15 - x64Gen_movsd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, regA); - - // call assembly routine to calculate accurate FRSQRTE result in XMM15 - x64Gen_mov_reg64_imm64(x64GenContext, REG_RESV_TEMP, (uint64)recompiler_frsqrte); - x64Gen_call_reg64(x64GenContext, REG_RESV_TEMP); - - // copy result to bottom of result register - x64Gen_movsd_xmmReg_xmmReg(x64GenContext, regR, REG_RESV_FPR_TEMP); - } - else if( imlInstruction->operation == PPCREC_IML_OP_FPR_NEGATE_PAIR ) - { - // copy register - if( regR != regA ) - { - x64Gen_movaps_xmmReg_xmmReg(x64GenContext, regR, regA); - } - // toggle sign bits - x64Gen_xorps_xmmReg_mem128Reg64(x64GenContext, regR, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_xorNegateMaskPair)); - } - else if( imlInstruction->operation == PPCREC_IML_OP_FPR_ABS_PAIR ) - { - // copy register - if( regR != regA ) - { - x64Gen_movaps_xmmReg_xmmReg(x64GenContext, regR, regA); - } - // set sign bit to 0 - x64Gen_andps_xmmReg_mem128Reg64(x64GenContext, regR, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_andAbsMaskPair)); - } - else if( imlInstruction->operation == PPCREC_IML_OP_FPR_FRES_PAIR || imlInstruction->operation == PPCREC_IML_OP_FPR_FRSQRTE_PAIR) - { - // calculate bottom half of result - x64Gen_movsd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, regA); - if(imlInstruction->operation == PPCREC_IML_OP_FPR_FRES_PAIR) - x64Gen_mov_reg64_imm64(x64GenContext, REG_RESV_TEMP, (uint64)recompiler_fres); - else - x64Gen_mov_reg64_imm64(x64GenContext, REG_RESV_TEMP, (uint64)recompiler_frsqrte); - x64Gen_call_reg64(x64GenContext, REG_RESV_TEMP); // calculate fres result in xmm15 - x64Gen_movsd_xmmReg_xmmReg(x64GenContext, regR, REG_RESV_FPR_TEMP); - - // calculate top half of result - // todo - this top to bottom copy can be optimized? - x64Gen_shufpd_xmmReg_xmmReg_imm8(x64GenContext, REG_RESV_FPR_TEMP, regA, 3); - x64Gen_shufpd_xmmReg_xmmReg_imm8(x64GenContext, REG_RESV_FPR_TEMP, REG_RESV_FPR_TEMP, 1); // swap top and bottom - - x64Gen_call_reg64(x64GenContext, REG_RESV_TEMP); // calculate fres result in xmm15 - - x64Gen_unpcklpd_xmmReg_xmmReg(x64GenContext, regR, REG_RESV_FPR_TEMP); // copy bottom to top - } else { assert_dbg(); @@ -846,7 +286,7 @@ void PPCRecompilerX64Gen_imlInstruction_fpr_r_r_r(PPCRecFunction_t* PPCRecFuncti uint32 regA = _regF64(imlInstruction->op_fpr_r_r_r.regA); uint32 regB = _regF64(imlInstruction->op_fpr_r_r_r.regB); - if (imlInstruction->operation == PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM) + if (imlInstruction->operation == PPCREC_IML_OP_FPR_MULTIPLY) { if (regR == regA) { @@ -862,7 +302,7 @@ void PPCRecompilerX64Gen_imlInstruction_fpr_r_r_r(PPCRecFunction_t* PPCRecFuncti x64Gen_mulsd_xmmReg_xmmReg(x64GenContext, regR, regB); } } - else if (imlInstruction->operation == PPCREC_IML_OP_FPR_ADD_BOTTOM) + else if (imlInstruction->operation == PPCREC_IML_OP_FPR_ADD) { // todo: Use AVX 3-operand VADDSD if available if (regR == regA) @@ -879,30 +319,7 @@ void PPCRecompilerX64Gen_imlInstruction_fpr_r_r_r(PPCRecFunction_t* PPCRecFuncti x64Gen_addsd_xmmReg_xmmReg(x64GenContext, regR, regB); } } - else if (imlInstruction->operation == PPCREC_IML_OP_FPR_SUB_PAIR) - { - // registerResult = registerOperandA - registerOperandB - if( regR == regA ) - { - x64Gen_subpd_xmmReg_xmmReg(x64GenContext, regR, regB); - } - else if (g_CPUFeatures.x86.avx) - { - x64Gen_avx_VSUBPD_xmm_xmm_xmm(x64GenContext, regR, regA, regB); - } - else if( regR == regB ) - { - x64Gen_movaps_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, regA); - x64Gen_subpd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, regB); - x64Gen_movaps_xmmReg_xmmReg(x64GenContext, regR, REG_RESV_FPR_TEMP); - } - else - { - x64Gen_movaps_xmmReg_xmmReg(x64GenContext, regR, regA); - x64Gen_subpd_xmmReg_xmmReg(x64GenContext, regR, regB); - } - } - else if( imlInstruction->operation == PPCREC_IML_OP_FPR_SUB_BOTTOM ) + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_SUB ) { if( regR == regA ) { @@ -934,39 +351,7 @@ void PPCRecompilerX64Gen_imlInstruction_fpr_r_r_r_r(PPCRecFunction_t* PPCRecFunc uint32 regB = _regF64(imlInstruction->op_fpr_r_r_r_r.regB); uint32 regC = _regF64(imlInstruction->op_fpr_r_r_r_r.regC); - if( imlInstruction->operation == PPCREC_IML_OP_FPR_SUM0 ) - { - // todo: Investigate if there are other optimizations possible if the operand registers overlap - // generic case - // 1) move frA bottom to frTemp bottom and top - x64Gen_movddup_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, regA); - // 2) add frB (both halfs, lower half is overwritten in the next step) - x64Gen_addpd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, regB); - // 3) Interleave top of frTemp and frC - x64Gen_unpckhpd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, regC); - // todo: We can optimize the REG_RESV_FPR_TEMP -> resultReg copy operation away when the result register does not overlap with any of the operand registers - x64Gen_movaps_xmmReg_xmmReg(x64GenContext, regR, REG_RESV_FPR_TEMP); - } - else if( imlInstruction->operation == PPCREC_IML_OP_FPR_SUM1 ) - { - // todo: Investigate if there are other optimizations possible if the operand registers overlap - // 1) move frA bottom to frTemp bottom and top - x64Gen_movddup_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, regA); - // 2) add frB (both halfs, lower half is overwritten in the next step) - x64Gen_addpd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, regB); - // 3) Copy bottom from frC - x64Gen_movsd_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, regC); - //// 4) Swap bottom and top half - //x64Gen_shufpd_xmmReg_xmmReg_imm8(x64GenContext, REG_RESV_FPR_TEMP, REG_RESV_FPR_TEMP, 1); - // todo: We can optimize the REG_RESV_FPR_TEMP -> resultReg copy operation away when the result register does not overlap with any of the operand registers - x64Gen_movaps_xmmReg_xmmReg(x64GenContext, regR, REG_RESV_FPR_TEMP); - - //float s0 = (float)hCPU->fpr[frC].fp0; - //float s1 = (float)(hCPU->fpr[frA].fp0 + hCPU->fpr[frB].fp1); - //hCPU->fpr[frD].fp0 = s0; - //hCPU->fpr[frD].fp1 = s1; - } - else if( imlInstruction->operation == PPCREC_IML_OP_FPR_SELECT_BOTTOM ) + if( imlInstruction->operation == PPCREC_IML_OP_FPR_SELECT ) { x64Gen_comisd_xmmReg_mem64Reg64(x64GenContext, regA, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_constDouble0_0)); sint32 jumpInstructionOffset1 = x64GenContext->emitter->GetWriteIndex(); @@ -981,38 +366,6 @@ void PPCRecompilerX64Gen_imlInstruction_fpr_r_r_r_r(PPCRecFunction_t* PPCRecFunc // end PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset2, x64GenContext->emitter->GetWriteIndex()); } - else if( imlInstruction->operation == PPCREC_IML_OP_FPR_SELECT_PAIR ) - { - // select bottom - x64Gen_comisd_xmmReg_mem64Reg64(x64GenContext, regA, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_constDouble0_0)); - sint32 jumpInstructionOffset1_bottom = x64GenContext->emitter->GetWriteIndex(); - x64Gen_jmpc_near(x64GenContext, X86_CONDITION_UNSIGNED_BELOW, 0); - // select C bottom - x64Gen_movsd_xmmReg_xmmReg(x64GenContext, regR, regC); - sint32 jumpInstructionOffset2_bottom = x64GenContext->emitter->GetWriteIndex(); - x64Gen_jmpc_near(x64GenContext, X86_CONDITION_NONE, 0); - // select B bottom - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset1_bottom, x64GenContext->emitter->GetWriteIndex()); - x64Gen_movsd_xmmReg_xmmReg(x64GenContext, regR, regB); - // end - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset2_bottom, x64GenContext->emitter->GetWriteIndex()); - // select top - x64Gen_movhlps_xmmReg_xmmReg(x64GenContext, REG_RESV_FPR_TEMP, regA); // copy top to bottom (todo: May cause stall?) - x64Gen_comisd_xmmReg_mem64Reg64(x64GenContext, REG_RESV_FPR_TEMP, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_constDouble0_0)); - sint32 jumpInstructionOffset1_top = x64GenContext->emitter->GetWriteIndex(); - x64Gen_jmpc_near(x64GenContext, X86_CONDITION_UNSIGNED_BELOW, 0); - // select C top - //x64Gen_movsd_xmmReg_xmmReg(x64GenContext, registerResult, registerOperandC); - x64Gen_shufpd_xmmReg_xmmReg_imm8(x64GenContext, regR, regC, 2); - sint32 jumpInstructionOffset2_top = x64GenContext->emitter->GetWriteIndex(); - x64Gen_jmpc_near(x64GenContext, X86_CONDITION_NONE, 0); - // select B top - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset1_top, x64GenContext->emitter->GetWriteIndex()); - //x64Gen_movsd_xmmReg_xmmReg(x64GenContext, registerResult, registerOperandB); - x64Gen_shufpd_xmmReg_xmmReg_imm8(x64GenContext, regR, regB, 2); - // end - PPCRecompilerX64Gen_redirectRelativeJump(x64GenContext, jumpInstructionOffset2_top, x64GenContext->emitter->GetWriteIndex()); - } else assert_dbg(); } @@ -1021,15 +374,19 @@ void PPCRecompilerX64Gen_imlInstruction_fpr_r(PPCRecFunction_t* PPCRecFunction, { uint32 regR = _regF64(imlInstruction->op_fpr_r.regR); - if( imlInstruction->operation == PPCREC_IML_OP_FPR_NEGATE_BOTTOM ) + if( imlInstruction->operation == PPCREC_IML_OP_FPR_NEGATE ) { x64Gen_xorps_xmmReg_mem128Reg64(x64GenContext, regR, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_xorNegateMaskBottom)); } - else if( imlInstruction->operation == PPCREC_IML_OP_FPR_ABS_BOTTOM ) + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_LOAD_ONE ) + { + x64Gen_movsd_xmmReg_memReg64(x64GenContext, regR, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_constDouble1_1)); + } + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_ABS ) { x64Gen_andps_xmmReg_mem128Reg64(x64GenContext, regR, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_andAbsMaskBottom)); } - else if( imlInstruction->operation == PPCREC_IML_OP_FPR_NEGATIVE_ABS_BOTTOM ) + else if( imlInstruction->operation == PPCREC_IML_OP_FPR_NEGATIVE_ABS ) { x64Gen_orps_xmmReg_mem128Reg64(x64GenContext, regR, REG_RESV_RECDATA, offsetof(PPCRecompilerInstanceData_t, _x64XMM_xorNegateMaskBottom)); } @@ -1040,19 +397,10 @@ void PPCRecompilerX64Gen_imlInstruction_fpr_r(PPCRecFunction_t* PPCRecFunction, // convert back to 64bit double x64Gen_cvtss2sd_xmmReg_xmmReg(x64GenContext, regR, regR); } - else if( imlInstruction->operation == PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_PAIR ) - { - // convert to 32bit singles - x64Gen_cvtpd2ps_xmmReg_xmmReg(x64GenContext, regR, regR); - // convert back to 64bit doubles - x64Gen_cvtps2pd_xmmReg_xmmReg(x64GenContext, regR, regR); - } - else if (imlInstruction->operation == PPCREC_IML_OP_FPR_EXPAND_BOTTOM32_TO_BOTTOM64_AND_TOP64) + else if (imlInstruction->operation == PPCREC_IML_OP_FPR_EXPAND_F32_TO_F64) { // convert bottom to 64bit double x64Gen_cvtss2sd_xmmReg_xmmReg(x64GenContext, regR, regR); - // copy to top half - x64Gen_movddup_xmmReg_xmmReg(x64GenContext, regR, regR); } else { diff --git a/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64GenFPU.cpp b/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64GenFPU.cpp index 882820e2..4bbcc025 100644 --- a/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64GenFPU.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64GenFPU.cpp @@ -213,6 +213,37 @@ void x64Gen_movsd_memReg64_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegi } } +void x64Gen_movsd_xmmReg_memReg64(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32) +{ + // SSE2 + if( memRegister == X86_REG_RSP ) + { + // MOVSD <xmm>, [RSP+<imm>] + x64Gen_writeU8(x64GenContext, 0xF2); + x64Gen_genSSEVEXPrefix2(x64GenContext, 0, xmmRegister, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x10); + x64Gen_writeU8(x64GenContext, 0x84+(xmmRegister&7)*8); + x64Gen_writeU8(x64GenContext, 0x24); + x64Gen_writeU32(x64GenContext, memImmU32); + } + else if( memRegister == 15 ) + { + // MOVSD <xmm>, [R15+<imm>] + x64Gen_writeU8(x64GenContext, 0x36); + x64Gen_writeU8(x64GenContext, 0xF2); + x64Gen_genSSEVEXPrefix2(x64GenContext, memRegister, xmmRegister, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x10); + x64Gen_writeU8(x64GenContext, 0x87+(xmmRegister&7)*8); + x64Gen_writeU32(x64GenContext, memImmU32); + } + else + { + assert_dbg(); + } +} + void x64Gen_movlpd_xmmReg_memReg64(x64GenContext_t* x64GenContext, sint32 xmmRegister, sint32 memRegister, uint32 memImmU32) { // SSE3 @@ -561,6 +592,16 @@ void x64Gen_cvttsd2si_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 regis x64Gen_writeU8(x64GenContext, 0xC0+(registerDest&7)*8+(xmmRegisterSrc&7)); } +void x64Gen_cvtsi2sd_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 registerSrc) +{ + // SSE2 + x64Gen_writeU8(x64GenContext, 0xF2); + x64Gen_genSSEVEXPrefix2(x64GenContext, registerSrc, xmmRegisterDest, false); + x64Gen_writeU8(x64GenContext, 0x0F); + x64Gen_writeU8(x64GenContext, 0x2A); + x64Gen_writeU8(x64GenContext, 0xC0+(xmmRegisterDest&7)*8+(registerSrc&7)); +} + void x64Gen_cvtsd2ss_xmmReg_xmmReg(x64GenContext_t* x64GenContext, sint32 xmmRegisterDest, sint32 xmmRegisterSrc) { // SSE2 diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IMLDebug.cpp b/src/Cafe/HW/Espresso/Recompiler/IML/IMLDebug.cpp index 07fd4002..f736c2a7 100644 --- a/src/Cafe/HW/Espresso/Recompiler/IML/IMLDebug.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IMLDebug.cpp @@ -189,9 +189,13 @@ void IMLDebug_DisassembleInstruction(const IMLInstruction& inst, std::string& di { strOutput.addFmt("r{}", inst.op_r_name.name - PPCREC_NAME_R0); } - else if (inst.op_r_name.name >= PPCREC_NAME_FPR0 && inst.op_r_name.name < (PPCREC_NAME_FPR0 + 999)) + if (inst.op_r_name.name >= PPCREC_NAME_FPR_HALF && inst.op_r_name.name < (PPCREC_NAME_FPR_HALF + 32*2)) { - strOutput.addFmt("f{}", inst.op_r_name.name - PPCREC_NAME_FPR0); + strOutput.addFmt("f{}", inst.op_r_name.name - ((PPCREC_NAME_FPR_HALF - inst.op_r_name.name)/2)); + if ((inst.op_r_name.name-PPCREC_NAME_FPR_HALF)&1) + strOutput.add(".ps1"); + else + strOutput.add(".ps0"); } else if (inst.op_r_name.name >= PPCREC_NAME_SPR0 && inst.op_r_name.name < (PPCREC_NAME_SPR0 + 999)) { diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.cpp b/src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.cpp index cb481043..60b7c6ca 100644 --- a/src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.cpp @@ -226,35 +226,6 @@ void IMLInstruction::CheckRegisterUsage(IMLUsedRegisters* registersUsed) const // address is in gpr register if (op_storeLoad.registerMem.IsValid()) registersUsed->readGPR1 = op_storeLoad.registerMem; - // determine partially written result - switch (op_storeLoad.mode) - { - case PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0: - case PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1: - cemu_assert_debug(op_storeLoad.registerGQR.IsValid()); - registersUsed->readGPR2 = op_storeLoad.registerGQR; - break; - case PPCREC_FPR_LD_MODE_DOUBLE_INTO_PS0: - // PS1 remains the same - cemu_assert_debug(op_storeLoad.registerGQR.IsInvalid()); - registersUsed->readGPR2 = op_storeLoad.registerData; - break; - case PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1: - case PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0_PS1: - case PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0: - case PPCREC_FPR_LD_MODE_PSQ_S16_PS0: - case PPCREC_FPR_LD_MODE_PSQ_S16_PS0_PS1: - case PPCREC_FPR_LD_MODE_PSQ_U16_PS0_PS1: - case PPCREC_FPR_LD_MODE_PSQ_U16_PS0: - case PPCREC_FPR_LD_MODE_PSQ_S8_PS0_PS1: - case PPCREC_FPR_LD_MODE_PSQ_U8_PS0_PS1: - case PPCREC_FPR_LD_MODE_PSQ_U8_PS0: - case PPCREC_FPR_LD_MODE_PSQ_S8_PS0: - cemu_assert_debug(op_storeLoad.registerGQR.IsInvalid()); - break; - default: - cemu_assert_unimplemented(); - } } else if (type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED) { @@ -265,34 +236,6 @@ void IMLInstruction::CheckRegisterUsage(IMLUsedRegisters* registersUsed) const registersUsed->readGPR1 = op_storeLoad.registerMem; if (op_storeLoad.registerMem2.IsValid()) registersUsed->readGPR2 = op_storeLoad.registerMem2; - // determine partially written result - switch (op_storeLoad.mode) - { - case PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0: - case PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1: - cemu_assert_debug(op_storeLoad.registerGQR.IsValid()); - registersUsed->readGPR3 = op_storeLoad.registerGQR; - break; - case PPCREC_FPR_LD_MODE_DOUBLE_INTO_PS0: - // PS1 remains the same - cemu_assert_debug(op_storeLoad.registerGQR.IsInvalid()); - registersUsed->readGPR3 = op_storeLoad.registerData; - break; - case PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1: - case PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0_PS1: - case PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0: - case PPCREC_FPR_LD_MODE_PSQ_S16_PS0: - case PPCREC_FPR_LD_MODE_PSQ_S16_PS0_PS1: - case PPCREC_FPR_LD_MODE_PSQ_U16_PS0_PS1: - case PPCREC_FPR_LD_MODE_PSQ_U16_PS0: - case PPCREC_FPR_LD_MODE_PSQ_S8_PS0_PS1: - case PPCREC_FPR_LD_MODE_PSQ_U8_PS0_PS1: - case PPCREC_FPR_LD_MODE_PSQ_U8_PS0: - cemu_assert_debug(op_storeLoad.registerGQR.IsInvalid()); - break; - default: - cemu_assert_unimplemented(); - } } else if (type == PPCREC_IML_TYPE_FPR_STORE) { @@ -300,18 +243,6 @@ void IMLInstruction::CheckRegisterUsage(IMLUsedRegisters* registersUsed) const registersUsed->readGPR1 = op_storeLoad.registerData; if (op_storeLoad.registerMem.IsValid()) registersUsed->readGPR2 = op_storeLoad.registerMem; - // PSQ generic stores also access GQR - switch (op_storeLoad.mode) - { - case PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0: - case PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1: - cemu_assert_debug(op_storeLoad.registerGQR.IsValid()); - registersUsed->readGPR3 = op_storeLoad.registerGQR; - break; - default: - cemu_assert_debug(op_storeLoad.registerGQR.IsInvalid()); - break; - } } else if (type == PPCREC_IML_TYPE_FPR_STORE_INDEXED) { @@ -322,72 +253,34 @@ void IMLInstruction::CheckRegisterUsage(IMLUsedRegisters* registersUsed) const registersUsed->readGPR2 = op_storeLoad.registerMem; if (op_storeLoad.registerMem2.IsValid()) registersUsed->readGPR3 = op_storeLoad.registerMem2; - // PSQ generic stores also access GQR - switch (op_storeLoad.mode) - { - case PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0: - case PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1: - cemu_assert_debug(op_storeLoad.registerGQR.IsValid()); - registersUsed->readGPR4 = op_storeLoad.registerGQR; - break; - default: - cemu_assert_debug(op_storeLoad.registerGQR.IsInvalid()); - break; - } } else if (type == PPCREC_IML_TYPE_FPR_R_R) { // fpr operation - if (operation == PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP || - operation == PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM_AND_TOP || - operation == PPCREC_IML_OP_FPR_COPY_BOTTOM_AND_TOP_SWAPPED || - operation == PPCREC_IML_OP_ASSIGN || - operation == PPCREC_IML_OP_FPR_NEGATE_PAIR || - operation == PPCREC_IML_OP_FPR_ABS_PAIR || - operation == PPCREC_IML_OP_FPR_FRES_PAIR || - operation == PPCREC_IML_OP_FPR_FRSQRTE_PAIR) - { - // operand read, result written - registersUsed->readGPR1 = op_fpr_r_r.regA; - registersUsed->writtenGPR1 = op_fpr_r_r.regR; - } - else if ( - operation == PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM || - operation == PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_TOP || - operation == PPCREC_IML_OP_FPR_COPY_TOP_TO_TOP || - operation == PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM || - operation == PPCREC_IML_OP_FPR_EXPAND_BOTTOM32_TO_BOTTOM64_AND_TOP64 || - operation == PPCREC_IML_OP_FPR_BOTTOM_FCTIWZ || - operation == PPCREC_IML_OP_FPR_BOTTOM_RECIPROCAL_SQRT + if ( + operation == PPCREC_IML_OP_FPR_ASSIGN || + operation == PPCREC_IML_OP_FPR_EXPAND_F32_TO_F64 || + operation == PPCREC_IML_OP_FPR_FCTIWZ ) { - // operand read, result read and (partially) written registersUsed->readGPR1 = op_fpr_r_r.regA; - registersUsed->readGPR2 = op_fpr_r_r.regR; registersUsed->writtenGPR1 = op_fpr_r_r.regR; } - else if (operation == PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM || - operation == PPCREC_IML_OP_FPR_MULTIPLY_PAIR || - operation == PPCREC_IML_OP_FPR_DIVIDE_BOTTOM || - operation == PPCREC_IML_OP_FPR_DIVIDE_PAIR || - operation == PPCREC_IML_OP_FPR_ADD_BOTTOM || - operation == PPCREC_IML_OP_FPR_ADD_PAIR || - operation == PPCREC_IML_OP_FPR_SUB_PAIR || - operation == PPCREC_IML_OP_FPR_SUB_BOTTOM) + else if (operation == PPCREC_IML_OP_FPR_MULTIPLY || + operation == PPCREC_IML_OP_FPR_DIVIDE || + operation == PPCREC_IML_OP_FPR_ADD || + operation == PPCREC_IML_OP_FPR_SUB) { - // operand read, result read and written registersUsed->readGPR1 = op_fpr_r_r.regA; registersUsed->readGPR2 = op_fpr_r_r.regR; registersUsed->writtenGPR1 = op_fpr_r_r.regR; } - else if (operation == PPCREC_IML_OP_FPR_FCMPU_BOTTOM || - operation == PPCREC_IML_OP_FPR_FCMPU_TOP || - operation == PPCREC_IML_OP_FPR_FCMPO_BOTTOM) + else if (operation == PPCREC_IML_OP_FPR_FLOAT_TO_INT || + operation == PPCREC_IML_OP_FPR_INT_TO_FLOAT) { - // operand read, result read + registersUsed->writtenGPR1 = op_fpr_r_r.regR; registersUsed->readGPR1 = op_fpr_r_r.regA; - registersUsed->readGPR2 = op_fpr_r_r.regR; } else cemu_assert_unimplemented(); @@ -398,19 +291,6 @@ void IMLInstruction::CheckRegisterUsage(IMLUsedRegisters* registersUsed) const registersUsed->readGPR1 = op_fpr_r_r_r.regA; registersUsed->readGPR2 = op_fpr_r_r_r.regB; registersUsed->writtenGPR1 = op_fpr_r_r_r.regR; - // handle partially written result - switch (operation) - { - case PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM: - case PPCREC_IML_OP_FPR_ADD_BOTTOM: - case PPCREC_IML_OP_FPR_SUB_BOTTOM: - registersUsed->readGPR3 = op_fpr_r_r_r.regR; - break; - case PPCREC_IML_OP_FPR_SUB_PAIR: - break; - default: - cemu_assert_unimplemented(); - } } else if (type == PPCREC_IML_TYPE_FPR_R_R_R_R) { @@ -419,33 +299,23 @@ void IMLInstruction::CheckRegisterUsage(IMLUsedRegisters* registersUsed) const registersUsed->readGPR2 = op_fpr_r_r_r_r.regB; registersUsed->readGPR3 = op_fpr_r_r_r_r.regC; registersUsed->writtenGPR1 = op_fpr_r_r_r_r.regR; - // handle partially written result - switch (operation) - { - case PPCREC_IML_OP_FPR_SELECT_BOTTOM: - registersUsed->readGPR4 = op_fpr_r_r_r_r.regR; - break; - case PPCREC_IML_OP_FPR_SUM0: - case PPCREC_IML_OP_FPR_SUM1: - case PPCREC_IML_OP_FPR_SELECT_PAIR: - break; - default: - cemu_assert_unimplemented(); - } } else if (type == PPCREC_IML_TYPE_FPR_R) { // fpr operation - if (operation == PPCREC_IML_OP_FPR_NEGATE_BOTTOM || - operation == PPCREC_IML_OP_FPR_ABS_BOTTOM || - operation == PPCREC_IML_OP_FPR_NEGATIVE_ABS_BOTTOM || - operation == PPCREC_IML_OP_FPR_EXPAND_BOTTOM32_TO_BOTTOM64_AND_TOP64 || - operation == PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_BOTTOM || - operation == PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_PAIR) + if (operation == PPCREC_IML_OP_FPR_NEGATE || + operation == PPCREC_IML_OP_FPR_ABS || + operation == PPCREC_IML_OP_FPR_NEGATIVE_ABS || + operation == PPCREC_IML_OP_FPR_EXPAND_F32_TO_F64 || + operation == PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_BOTTOM) { registersUsed->readGPR1 = op_fpr_r.regR; registersUsed->writtenGPR1 = op_fpr_r.regR; } + else if (operation == PPCREC_IML_OP_FPR_LOAD_ONE) + { + registersUsed->writtenGPR1 = op_fpr_r.regR; + } else cemu_assert_unimplemented(); } @@ -608,27 +478,23 @@ void IMLInstruction::RewriteGPR(const std::unordered_map<IMLRegID, IMLRegID>& tr { op_storeLoad.registerData = replaceRegisterIdMultiple(op_storeLoad.registerData, translationTable); op_storeLoad.registerMem = replaceRegisterIdMultiple(op_storeLoad.registerMem, translationTable); - op_storeLoad.registerGQR = replaceRegisterIdMultiple(op_storeLoad.registerGQR, translationTable); } else if (type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED) { op_storeLoad.registerData = replaceRegisterIdMultiple(op_storeLoad.registerData, translationTable); op_storeLoad.registerMem = replaceRegisterIdMultiple(op_storeLoad.registerMem, translationTable); op_storeLoad.registerMem2 = replaceRegisterIdMultiple(op_storeLoad.registerMem2, translationTable); - op_storeLoad.registerGQR = replaceRegisterIdMultiple(op_storeLoad.registerGQR, translationTable); } else if (type == PPCREC_IML_TYPE_FPR_STORE) { op_storeLoad.registerData = replaceRegisterIdMultiple(op_storeLoad.registerData, translationTable); op_storeLoad.registerMem = replaceRegisterIdMultiple(op_storeLoad.registerMem, translationTable); - op_storeLoad.registerGQR = replaceRegisterIdMultiple(op_storeLoad.registerGQR, translationTable); } else if (type == PPCREC_IML_TYPE_FPR_STORE_INDEXED) { op_storeLoad.registerData = replaceRegisterIdMultiple(op_storeLoad.registerData, translationTable); op_storeLoad.registerMem = replaceRegisterIdMultiple(op_storeLoad.registerMem, translationTable); op_storeLoad.registerMem2 = replaceRegisterIdMultiple(op_storeLoad.registerMem2, translationTable); - op_storeLoad.registerGQR = replaceRegisterIdMultiple(op_storeLoad.registerGQR, translationTable); } else if (type == PPCREC_IML_TYPE_FPR_R) { diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.h b/src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.h index 3ba0a1af..3b3898e9 100644 --- a/src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.h +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.h @@ -126,46 +126,22 @@ enum PPCREC_IML_OP_SRW, // SRW (shift based on register by up to 63 bits) PPCREC_IML_OP_CNTLZW, // FPU - PPCREC_IML_OP_FPR_ADD_BOTTOM, - PPCREC_IML_OP_FPR_ADD_PAIR, - PPCREC_IML_OP_FPR_SUB_PAIR, - PPCREC_IML_OP_FPR_SUB_BOTTOM, - PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM, - PPCREC_IML_OP_FPR_MULTIPLY_PAIR, - PPCREC_IML_OP_FPR_DIVIDE_BOTTOM, - PPCREC_IML_OP_FPR_DIVIDE_PAIR, - PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, - PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM_AND_TOP, - PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, - PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_TOP, // leave bottom of destination untouched - PPCREC_IML_OP_FPR_COPY_TOP_TO_TOP, // leave bottom of destination untouched - PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM, // leave top of destination untouched - PPCREC_IML_OP_FPR_COPY_BOTTOM_AND_TOP_SWAPPED, - PPCREC_IML_OP_FPR_EXPAND_BOTTOM32_TO_BOTTOM64_AND_TOP64, // expand bottom f32 to f64 in bottom and top half - PPCREC_IML_OP_FPR_FCMPO_BOTTOM, // deprecated - PPCREC_IML_OP_FPR_FCMPU_BOTTOM, // deprecated - PPCREC_IML_OP_FPR_FCMPU_TOP, // deprecated - PPCREC_IML_OP_FPR_NEGATE_BOTTOM, - PPCREC_IML_OP_FPR_NEGATE_PAIR, - PPCREC_IML_OP_FPR_ABS_BOTTOM, // abs(fp0) - PPCREC_IML_OP_FPR_ABS_PAIR, - PPCREC_IML_OP_FPR_FRES_PAIR, // 1.0/fp approx (Espresso accuracy) - PPCREC_IML_OP_FPR_FRSQRTE_PAIR, // 1.0/sqrt(fp) approx (Espresso accuracy) - PPCREC_IML_OP_FPR_NEGATIVE_ABS_BOTTOM, // -abs(fp0) + PPCREC_IML_OP_FPR_ASSIGN, + PPCREC_IML_OP_FPR_LOAD_ONE, // load constant 1.0 into register + PPCREC_IML_OP_FPR_ADD, + PPCREC_IML_OP_FPR_SUB, + PPCREC_IML_OP_FPR_MULTIPLY, + PPCREC_IML_OP_FPR_DIVIDE, + PPCREC_IML_OP_FPR_EXPAND_F32_TO_F64, // expand f32 to f64 in-place + PPCREC_IML_OP_FPR_NEGATE, + PPCREC_IML_OP_FPR_ABS, // abs(fpr) + PPCREC_IML_OP_FPR_NEGATIVE_ABS, // -abs(fpr) PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_BOTTOM, // round 64bit double to 64bit double with 32bit float precision (in bottom half of xmm register) - PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_PAIR, // round two 64bit doubles to 64bit double with 32bit float precision - PPCREC_IML_OP_FPR_BOTTOM_RECIPROCAL_SQRT, - PPCREC_IML_OP_FPR_BOTTOM_FCTIWZ, - PPCREC_IML_OP_FPR_SELECT_BOTTOM, // selectively copy bottom value from operand B or C based on value in operand A - PPCREC_IML_OP_FPR_SELECT_PAIR, // selectively copy top/bottom from operand B or C based on value in top/bottom of operand A - // PS - PPCREC_IML_OP_FPR_SUM0, - PPCREC_IML_OP_FPR_SUM1, - - - // R_R_R only - - // R_R_S32 only + PPCREC_IML_OP_FPR_FCTIWZ, + PPCREC_IML_OP_FPR_SELECT, // selectively copy bottom value from operand B or C based on value in operand A + // Conversion (FPR_R_R) + PPCREC_IML_OP_FPR_INT_TO_FLOAT, // convert integer value in gpr to floating point value in fpr + PPCREC_IML_OP_FPR_FLOAT_TO_INT, // convert floating point value in fpr to integer value in gpr // R_R_R + R_R_S32 PPCREC_IML_OP_ADD, // also R_R_R_CARRY @@ -275,7 +251,7 @@ enum // IMLName PPCREC_NAME_TEMPORARY = 1000, PPCREC_NAME_R0 = 2000, PPCREC_NAME_SPR0 = 3000, - PPCREC_NAME_FPR0 = 4000, + PPCREC_NAME_FPR_HALF = 4800, // Counts PS0 and PS1 separately. E.g. fp3.ps1 is at offset 3 * 2 + 1 PPCREC_NAME_TEMPORARY_FPR0 = 5000, // 0 to 7 PPCREC_NAME_XER_CA = 6000, // carry bit from XER PPCREC_NAME_XER_OV = 6001, // overflow bit from XER @@ -291,39 +267,14 @@ enum // IMLName enum { // fpr load - PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0, - PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1, - PPCREC_FPR_LD_MODE_DOUBLE_INTO_PS0, - PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0, - PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1, - PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0, - PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0_PS1, - PPCREC_FPR_LD_MODE_PSQ_S16_PS0, - PPCREC_FPR_LD_MODE_PSQ_S16_PS0_PS1, - PPCREC_FPR_LD_MODE_PSQ_U16_PS0, - PPCREC_FPR_LD_MODE_PSQ_U16_PS0_PS1, - PPCREC_FPR_LD_MODE_PSQ_S8_PS0, - PPCREC_FPR_LD_MODE_PSQ_S8_PS0_PS1, - PPCREC_FPR_LD_MODE_PSQ_U8_PS0, - PPCREC_FPR_LD_MODE_PSQ_U8_PS0_PS1, + PPCREC_FPR_LD_MODE_SINGLE, + PPCREC_FPR_LD_MODE_DOUBLE, + // fpr store - PPCREC_FPR_ST_MODE_SINGLE_FROM_PS0, // store 1 single precision float from ps0 - PPCREC_FPR_ST_MODE_DOUBLE_FROM_PS0, // store 1 double precision float from ps0 + PPCREC_FPR_ST_MODE_SINGLE, + PPCREC_FPR_ST_MODE_DOUBLE, PPCREC_FPR_ST_MODE_UI32_FROM_PS0, // store raw low-32bit of PS0 - - PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1, - PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0, - PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0_PS1, - PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0, - PPCREC_FPR_ST_MODE_PSQ_S8_PS0, - PPCREC_FPR_ST_MODE_PSQ_S8_PS0_PS1, - PPCREC_FPR_ST_MODE_PSQ_U8_PS0, - PPCREC_FPR_ST_MODE_PSQ_U8_PS0_PS1, - PPCREC_FPR_ST_MODE_PSQ_U16_PS0, - PPCREC_FPR_ST_MODE_PSQ_U16_PS0_PS1, - PPCREC_FPR_ST_MODE_PSQ_S16_PS0, - PPCREC_FPR_ST_MODE_PSQ_S16_PS0_PS1, }; struct IMLUsedRegisters @@ -463,7 +414,6 @@ struct IMLInstruction IMLReg registerData; IMLReg registerMem; IMLReg registerMem2; - IMLReg registerGQR; uint8 copyWidth; struct { @@ -471,7 +421,7 @@ struct IMLInstruction bool signExtend : 1; bool notExpanded : 1; // for floats }flags2; - uint8 mode; // transfer mode (copy width, ps0/ps1 behavior) + uint8 mode; // transfer mode sint32 immS32; }op_storeLoad; struct @@ -752,6 +702,56 @@ struct IMLInstruction this->op_call_imm.regReturn = regReturn; } + // FPR + + // load from memory + void make_fpr_r_memory(IMLReg registerDestination, IMLReg registerMemory, sint32 immS32, uint32 mode, bool switchEndian) + { + this->type = PPCREC_IML_TYPE_FPR_LOAD; + this->operation = 0; + this->op_storeLoad.registerData = registerDestination; + this->op_storeLoad.registerMem = registerMemory; + this->op_storeLoad.immS32 = immS32; + this->op_storeLoad.mode = mode; + this->op_storeLoad.flags2.swapEndian = switchEndian; + } + + void make_fpr_r_memory_indexed(IMLReg registerDestination, IMLReg registerMemory1, IMLReg registerMemory2, uint32 mode, bool switchEndian) + { + this->type = PPCREC_IML_TYPE_FPR_LOAD_INDEXED; + this->operation = 0; + this->op_storeLoad.registerData = registerDestination; + this->op_storeLoad.registerMem = registerMemory1; + this->op_storeLoad.registerMem2 = registerMemory2; + this->op_storeLoad.immS32 = 0; + this->op_storeLoad.mode = mode; + this->op_storeLoad.flags2.swapEndian = switchEndian; + } + + // store to memory + void make_fpr_memory_r(IMLReg registerSource, IMLReg registerMemory, sint32 immS32, uint32 mode, bool switchEndian) + { + this->type = PPCREC_IML_TYPE_FPR_STORE; + this->operation = 0; + this->op_storeLoad.registerData = registerSource; + this->op_storeLoad.registerMem = registerMemory; + this->op_storeLoad.immS32 = immS32; + this->op_storeLoad.mode = mode; + this->op_storeLoad.flags2.swapEndian = switchEndian; + } + + void make_fpr_memory_r_indexed(IMLReg registerSource, IMLReg registerMemory1, IMLReg registerMemory2, sint32 immS32, uint32 mode, bool switchEndian) + { + this->type = PPCREC_IML_TYPE_FPR_STORE_INDEXED; + this->operation = 0; + this->op_storeLoad.registerData = registerSource; + this->op_storeLoad.registerMem = registerMemory1; + this->op_storeLoad.registerMem2 = registerMemory2; + this->op_storeLoad.immS32 = immS32; + this->op_storeLoad.mode = mode; + this->op_storeLoad.flags2.swapEndian = switchEndian; + } + void make_fpr_compare(IMLReg regA, IMLReg regB, IMLReg regR, IMLCondition cond) { this->type = PPCREC_IML_TYPE_FPR_COMPARE; @@ -762,6 +762,44 @@ struct IMLInstruction this->op_fpr_compare.cond = cond; } + void make_fpr_r(sint32 operation, IMLReg registerResult) + { + // OP (fpr) + this->type = PPCREC_IML_TYPE_FPR_R; + this->operation = operation; + this->op_fpr_r.regR = registerResult; + } + + void make_fpr_r_r(sint32 operation, IMLReg registerResult, IMLReg registerOperand, sint32 crRegister=PPC_REC_INVALID_REGISTER) + { + // fpr OP fpr + this->type = PPCREC_IML_TYPE_FPR_R_R; + this->operation = operation; + this->op_fpr_r_r.regR = registerResult; + this->op_fpr_r_r.regA = registerOperand; + } + + void make_fpr_r_r_r(sint32 operation, IMLReg registerResult, IMLReg registerOperand1, IMLReg registerOperand2, sint32 crRegister=PPC_REC_INVALID_REGISTER) + { + // fpr = OP (fpr,fpr) + this->type = PPCREC_IML_TYPE_FPR_R_R_R; + this->operation = operation; + this->op_fpr_r_r_r.regR = registerResult; + this->op_fpr_r_r_r.regA = registerOperand1; + this->op_fpr_r_r_r.regB = registerOperand2; + } + + void make_fpr_r_r_r_r(sint32 operation, IMLReg registerResult, IMLReg registerOperandA, IMLReg registerOperandB, IMLReg registerOperandC, sint32 crRegister=PPC_REC_INVALID_REGISTER) + { + // fpr = OP (fpr,fpr,fpr) + this->type = PPCREC_IML_TYPE_FPR_R_R_R_R; + this->operation = operation; + this->op_fpr_r_r_r_r.regR = registerResult; + this->op_fpr_r_r_r_r.regA = registerOperandA; + this->op_fpr_r_r_r_r.regB = registerOperandB; + this->op_fpr_r_r_r_r.regC = registerOperandC; + } + /* X86 specific */ void make_x86_eflags_jcc(IMLCondition cond, bool invertedCondition) { diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IMLOptimizer.cpp b/src/Cafe/HW/Espresso/Recompiler/IML/IMLOptimizer.cpp index f2cf173a..d0348e5a 100644 --- a/src/Cafe/HW/Espresso/Recompiler/IML/IMLOptimizer.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IMLOptimizer.cpp @@ -34,8 +34,8 @@ void PPCRecompiler_optimizeDirectFloatCopiesScanForward(ppcImlGenContext_t* ppcI if (imlInstruction->IsSuffixInstruction()) break; // check if FPR is stored - if ((imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE && imlInstruction->op_storeLoad.mode == PPCREC_FPR_ST_MODE_SINGLE_FROM_PS0) || - (imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE_INDEXED && imlInstruction->op_storeLoad.mode == PPCREC_FPR_ST_MODE_SINGLE_FROM_PS0)) + if ((imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE && imlInstruction->op_storeLoad.mode == PPCREC_FPR_ST_MODE_SINGLE) || + (imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE_INDEXED && imlInstruction->op_storeLoad.mode == PPCREC_FPR_ST_MODE_SINGLE)) { if (imlInstruction->op_storeLoad.registerData.GetRegID() == fprIndex) { @@ -73,7 +73,7 @@ void PPCRecompiler_optimizeDirectFloatCopiesScanForward(ppcImlGenContext_t* ppcI { // insert expand instruction after store IMLInstruction* newExpand = PPCRecompiler_insertInstruction(imlSegment, lastStore); - PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext, newExpand, PPCREC_IML_OP_FPR_EXPAND_BOTTOM32_TO_BOTTOM64_AND_TOP64, _FPRRegFromID(fprIndex)); + newExpand->make_fpr_r(PPCREC_IML_OP_FPR_EXPAND_F32_TO_F64, _FPRRegFromID(fprIndex)); } } @@ -90,21 +90,23 @@ void PPCRecompiler_optimizeDirectFloatCopiesScanForward(ppcImlGenContext_t* ppcI */ void IMLOptimizer_OptimizeDirectFloatCopies(ppcImlGenContext_t* ppcImlGenContext) { - for (IMLSegment* segIt : ppcImlGenContext->segmentList2) - { - for (sint32 i = 0; i < segIt->imlList.size(); i++) - { - IMLInstruction* imlInstruction = segIt->imlList.data() + i; - if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD && imlInstruction->op_storeLoad.mode == PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1) - { - PPCRecompiler_optimizeDirectFloatCopiesScanForward(ppcImlGenContext, segIt, i, imlInstruction->op_storeLoad.registerData); - } - else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED && imlInstruction->op_storeLoad.mode == PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1) - { - PPCRecompiler_optimizeDirectFloatCopiesScanForward(ppcImlGenContext, segIt, i, imlInstruction->op_storeLoad.registerData); - } - } - } + cemuLog_logDebugOnce(LogType::Force, "IMLOptimizer_OptimizeDirectFloatCopies(): Currently disabled\n"); + return; + // for (IMLSegment* segIt : ppcImlGenContext->segmentList2) + // { + // for (sint32 i = 0; i < segIt->imlList.size(); i++) + // { + // IMLInstruction* imlInstruction = segIt->imlList.data() + i; + // if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD && imlInstruction->op_storeLoad.mode == PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1) + // { + // PPCRecompiler_optimizeDirectFloatCopiesScanForward(ppcImlGenContext, segIt, i, imlInstruction->op_storeLoad.registerData); + // } + // else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED && imlInstruction->op_storeLoad.mode == PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1) + // { + // PPCRecompiler_optimizeDirectFloatCopiesScanForward(ppcImlGenContext, segIt, i, imlInstruction->op_storeLoad.registerData); + // } + // } + // } } void PPCRecompiler_optimizeDirectIntegerCopiesScanForward(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* imlSegment, sint32 imlIndexLoad, IMLReg gprReg) @@ -207,133 +209,22 @@ sint32 _getGQRIndexFromRegister(ppcImlGenContext_t* ppcImlGenContext, IMLReg gqr bool PPCRecompiler_isUGQRValueKnown(ppcImlGenContext_t* ppcImlGenContext, sint32 gqrIndex, uint32& gqrValue) { - // UGQR 2 to 7 are initialized by the OS and we assume that games won't ever permanently touch those - // todo - hack - replace with more accurate solution - if (gqrIndex == 2) - gqrValue = 0x00040004; - else if (gqrIndex == 3) - gqrValue = 0x00050005; - else if (gqrIndex == 4) - gqrValue = 0x00060006; - else if (gqrIndex == 5) - gqrValue = 0x00070007; + // the default configuration is: + // UGQR0 = 0x00000000 + // UGQR2 = 0x00040004 + // UGQR3 = 0x00050005 + // UGQR4 = 0x00060006 + // UGQR5 = 0x00070007 + // but games are free to modify UGQR2 to UGQR7 it seems. + // no game modifies UGQR0 so it's safe enough to optimize for the default value + // Ideally we would do some kind of runtime tracking and second recompilation to create fast paths for PSQ_L/PSQ_ST but thats todo + if (gqrIndex == 0) + gqrValue = 0x00000000; else return false; return true; } -/* - * If value of GQR can be predicted for a given PSQ load or store instruction then replace it with an optimized version - */ -void PPCRecompiler_optimizePSQLoadAndStore(ppcImlGenContext_t* ppcImlGenContext) -{ - for (IMLSegment* segIt : ppcImlGenContext->segmentList2) - { - for(IMLInstruction& instIt : segIt->imlList) - { - if (instIt.type == PPCREC_IML_TYPE_FPR_LOAD || instIt.type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED) - { - if(instIt.op_storeLoad.mode != PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0 && - instIt.op_storeLoad.mode != PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1 ) - continue; - // get GQR value - cemu_assert_debug(instIt.op_storeLoad.registerGQR.IsValid()); - sint32 gqrIndex = _getGQRIndexFromRegister(ppcImlGenContext, instIt.op_storeLoad.registerGQR); - cemu_assert(gqrIndex >= 0); - if (ppcImlGenContext->tracking.modifiesGQR[gqrIndex]) - continue; - uint32 gqrValue; - if (!PPCRecompiler_isUGQRValueKnown(ppcImlGenContext, gqrIndex, gqrValue)) - continue; - - uint32 formatType = (gqrValue >> 16) & 7; - uint32 scale = (gqrValue >> 24) & 0x3F; - if (scale != 0) - continue; // only generic handler supports scale - if (instIt.op_storeLoad.mode == PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0) - { - if (formatType == 0) - instIt.op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0; - else if (formatType == 4) - instIt.op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_U8_PS0; - else if (formatType == 5) - instIt.op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_U16_PS0; - else if (formatType == 6) - instIt.op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_S8_PS0; - else if (formatType == 7) - instIt.op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_S16_PS0; - if (instIt.op_storeLoad.mode != PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0) - instIt.op_storeLoad.registerGQR = IMLREG_INVALID; - } - else if (instIt.op_storeLoad.mode == PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1) - { - if (formatType == 0) - instIt.op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_FLOAT_PS0_PS1; - else if (formatType == 4) - instIt.op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_U8_PS0_PS1; - else if (formatType == 5) - instIt.op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_U16_PS0_PS1; - else if (formatType == 6) - instIt.op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_S8_PS0_PS1; - else if (formatType == 7) - instIt.op_storeLoad.mode = PPCREC_FPR_LD_MODE_PSQ_S16_PS0_PS1; - if (instIt.op_storeLoad.mode != PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1) - instIt.op_storeLoad.registerGQR = IMLREG_INVALID; - } - } - else if (instIt.type == PPCREC_IML_TYPE_FPR_STORE || instIt.type == PPCREC_IML_TYPE_FPR_STORE_INDEXED) - { - if(instIt.op_storeLoad.mode != PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0 && - instIt.op_storeLoad.mode != PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1) - continue; - // get GQR value - cemu_assert_debug(instIt.op_storeLoad.registerGQR.IsValid()); - sint32 gqrIndex = _getGQRIndexFromRegister(ppcImlGenContext, instIt.op_storeLoad.registerGQR); - cemu_assert(gqrIndex >= 0 && gqrIndex < 8); - if (ppcImlGenContext->tracking.modifiesGQR[gqrIndex]) - continue; - uint32 gqrValue; - if(!PPCRecompiler_isUGQRValueKnown(ppcImlGenContext, gqrIndex, gqrValue)) - continue; - uint32 formatType = (gqrValue >> 16) & 7; - uint32 scale = (gqrValue >> 24) & 0x3F; - if (scale != 0) - continue; // only generic handler supports scale - if (instIt.op_storeLoad.mode == PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0) - { - if (formatType == 0) - instIt.op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0; - else if (formatType == 4) - instIt.op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_U8_PS0; - else if (formatType == 5) - instIt.op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_U16_PS0; - else if (formatType == 6) - instIt.op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_S8_PS0; - else if (formatType == 7) - instIt.op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_S16_PS0; - if (instIt.op_storeLoad.mode != PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0) - instIt.op_storeLoad.registerGQR = IMLREG_INVALID; - } - else if (instIt.op_storeLoad.mode == PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1) - { - if (formatType == 0) - instIt.op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_FLOAT_PS0_PS1; - else if (formatType == 4) - instIt.op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_U8_PS0_PS1; - else if (formatType == 5) - instIt.op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_U16_PS0_PS1; - else if (formatType == 6) - instIt.op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_S8_PS0_PS1; - else if (formatType == 7) - instIt.op_storeLoad.mode = PPCREC_FPR_ST_MODE_PSQ_S16_PS0_PS1; - if (instIt.op_storeLoad.mode != PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1) - instIt.op_storeLoad.registerGQR = IMLREG_INVALID; - } - } - } - } -} - // analyses register dependencies across the entire function // per segment this will generate information about which registers need to be preserved and which ones don't (e.g. are overwritten) class IMLOptimizerRegIOAnalysis diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocator.cpp b/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocator.cpp index d411be14..5de1408b 100644 --- a/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocator.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocator.cpp @@ -2093,7 +2093,10 @@ void IMLRA_GenerateSegmentMoveInstructions2(IMLRegisterAllocatorContext& ctx, IM cemu_assert_debug(hadSuffixInstruction == imlSegment->HasSuffixInstruction()); if (imlSegment->HasSuffixInstruction()) { - cemu_assert_debug(!currentRange); // currentRange should be NULL? + if (currentRange) + { + cemuLog_logDebug(LogType::Force, "[DEBUG] GenerateSegmentMoveInstructions() hit suffix path with non-null currentRange. Segment: {:08x}", imlSegment->ppcAddress); + } for (auto& remainingRange : activeRanges) { cemu_assert_debug(!remainingRange->hasStore); diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp index be1846de..0dbc073b 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp @@ -311,10 +311,7 @@ bool PPCRecompiler_ApplyIMLPasses(ppcImlGenContext_t& ppcImlGenContext) // this simplifies logic during register allocation PPCRecompilerIML_isolateEnterableSegments(&ppcImlGenContext); - // if GQRs can be predicted, optimize PSQ load/stores - PPCRecompiler_optimizePSQLoadAndStore(&ppcImlGenContext); - - // merge certain float load+store patterns (must happen before FPR register remapping) + // merge certain float load+store patterns IMLOptimizer_OptimizeDirectFloatCopies(&ppcImlGenContext); // delay byte swapping for certain load+store patterns IMLOptimizer_OptimizeDirectIntegerCopies(&ppcImlGenContext); diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerIml.h b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerIml.h index 5d30267d..bfb2aed5 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerIml.h +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerIml.h @@ -14,34 +14,20 @@ void PPCRecompilerIml_insertSegments(ppcImlGenContext_t* ppcImlGenContext, sint3 void PPCRecompilerIml_setSegmentPoint(IMLSegmentPoint* segmentPoint, IMLSegment* imlSegment, sint32 index); void PPCRecompilerIml_removeSegmentPoint(IMLSegmentPoint* segmentPoint); -// GPR register management -IMLReg PPCRecompilerImlGen_loadRegister(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName); +// Register management +IMLReg PPCRecompilerImlGen_LookupReg(ppcImlGenContext_t* ppcImlGenContext, IMLName mappedName, IMLRegFormat regFormat); -// FPR register management -IMLReg PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName, bool loadNew = false); -IMLReg PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName); +IMLReg PPCRecompilerImlGen_loadRegister(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName); // IML instruction generation void PPCRecompilerImlGen_generateNewInstruction_conditional_r_s32(ppcImlGenContext_t* ppcImlGenContext, IMLInstruction* imlInstruction, uint32 operation, IMLReg registerIndex, sint32 immS32, uint32 crRegisterIndex, uint32 crBitIndex, bool bitMustBeSet); -void PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext_t* ppcImlGenContext, IMLInstruction* imlInstruction, sint32 operation, IMLReg registerResult); // IML generation - FPU -bool PPCRecompilerImlGen_LFS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); -bool PPCRecompilerImlGen_LFSU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); -bool PPCRecompilerImlGen_LFSX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); -bool PPCRecompilerImlGen_LFSUX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); -bool PPCRecompilerImlGen_LFD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); -bool PPCRecompilerImlGen_LFDU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); -bool PPCRecompilerImlGen_LFDX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); -bool PPCRecompilerImlGen_LFDUX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); -bool PPCRecompilerImlGen_STFS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); -bool PPCRecompilerImlGen_STFSU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); -bool PPCRecompilerImlGen_STFSX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); -bool PPCRecompilerImlGen_STFSUX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_LFS_LFSU_LFD_LFDU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool withUpdate, bool isDouble); +bool PPCRecompilerImlGen_LFSX_LFSUX_LFDX_LFDUX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool withUpdate, bool isDouble); +bool PPCRecompilerImlGen_STFS_STFSU_STFD_STFDU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool withUpdate, bool isDouble); +bool PPCRecompilerImlGen_STFSX_STFSUX_STFDX_STFDUX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool hasUpdate, bool isDouble); bool PPCRecompilerImlGen_STFIWX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); -bool PPCRecompilerImlGen_STFD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); -bool PPCRecompilerImlGen_STFDU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); -bool PPCRecompilerImlGen_STFDX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_FADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_FSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_FMUL(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); @@ -67,22 +53,17 @@ bool PPCRecompilerImlGen_FNEG(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod bool PPCRecompilerImlGen_FSEL(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_FRSQRTE(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_FCTIWZ(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); -bool PPCRecompilerImlGen_PSQ_L(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); -bool PPCRecompilerImlGen_PSQ_LU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); -bool PPCRecompilerImlGen_PSQ_ST(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); -bool PPCRecompilerImlGen_PSQ_STU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); -bool PPCRecompilerImlGen_PS_MULS0(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); -bool PPCRecompilerImlGen_PS_MULS1(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); -bool PPCRecompilerImlGen_PS_MADDS0(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); -bool PPCRecompilerImlGen_PS_MADDS1(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_PSQ_L(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool withUpdate); +bool PPCRecompilerImlGen_PSQ_ST(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool withUpdate); +bool PPCRecompilerImlGen_PS_MULSX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool isVariant1); +bool PPCRecompilerImlGen_PS_MADDSX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool isVariant1); bool PPCRecompilerImlGen_PS_ADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_PS_SUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_PS_MUL(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_PS_DIV(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_PS_MADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_PS_NMADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); -bool PPCRecompilerImlGen_PS_MSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); -bool PPCRecompilerImlGen_PS_NMSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); +bool PPCRecompilerImlGen_PS_MSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool withNegative); bool PPCRecompilerImlGen_PS_SUM0(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_PS_SUM1(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); bool PPCRecompilerImlGen_PS_NEG(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode); @@ -102,3 +83,19 @@ bool PPCRecompilerImlGen_PS_CMPU1(ppcImlGenContext_t* ppcImlGenContext, uint32 o // IML general void PPCRecompilerIML_isolateEnterableSegments(ppcImlGenContext_t* ppcImlGenContext); + +void PPCIMLGen_CreateSegmentBranchedPath(ppcImlGenContext_t& ppcImlGenContext, PPCBasicBlockInfo& basicBlockInfo, const std::function<void(ppcImlGenContext_t&)>& genSegmentBranchTaken, const std::function<void(ppcImlGenContext_t&)>& genSegmentBranchNotTaken); +void PPCIMLGen_CreateSegmentBranchedPath(ppcImlGenContext_t& ppcImlGenContext, PPCBasicBlockInfo& basicBlockInfo, const std::function<void(ppcImlGenContext_t&)>& genSegmentBranchNotTaken); // no else segment +void PPCIMLGen_CreateSegmentBranchedPathMultiple(ppcImlGenContext_t& ppcImlGenContext, PPCBasicBlockInfo& basicBlockInfo, IMLSegment** segmentsOut, IMLReg compareReg, sint32* compareValues, sint32 count, sint32 defaultCaseIndex); + +class IMLRedirectInstOutput +{ +public: + IMLRedirectInstOutput(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* outputSegment); + ~IMLRedirectInstOutput(); + + +private: + ppcImlGenContext_t* m_context; + IMLSegment* m_prevSegment; +}; \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGen.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGen.cpp index a705baf8..e76a53fa 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGen.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGen.cpp @@ -87,8 +87,7 @@ void PPCRecompilerImlGen_generateNewInstruction_memory_r_indexed(ppcImlGenContex } // create and fill two segments (branch taken and branch not taken) as a follow up to the current segment and then merge flow afterwards -template<typename F1n, typename F2n> -void PPCIMLGen_CreateSegmentBranchedPath(ppcImlGenContext_t& ppcImlGenContext, PPCBasicBlockInfo& basicBlockInfo, F1n genSegmentBranchTaken, F2n genSegmentBranchNotTaken) +void PPCIMLGen_CreateSegmentBranchedPath(ppcImlGenContext_t& ppcImlGenContext, PPCBasicBlockInfo& basicBlockInfo, const std::function<void(ppcImlGenContext_t&)>& genSegmentBranchTaken, const std::function<void(ppcImlGenContext_t&)>& genSegmentBranchNotTaken) { IMLSegment* currentWriteSegment = basicBlockInfo.GetSegmentForInstructionAppend(); @@ -118,6 +117,122 @@ void PPCIMLGen_CreateSegmentBranchedPath(ppcImlGenContext_t& ppcImlGenContext, P basicBlockInfo.appendSegment = segMerge; } +void PPCIMLGen_CreateSegmentBranchedPath(ppcImlGenContext_t& ppcImlGenContext, PPCBasicBlockInfo& basicBlockInfo, const std::function<void(ppcImlGenContext_t&)>& genSegmentBranchNotTaken) +{ + IMLSegment* currentWriteSegment = basicBlockInfo.GetSegmentForInstructionAppend(); + + std::span<IMLSegment*> segments = ppcImlGenContext.InsertSegments(ppcImlGenContext.GetSegmentIndex(currentWriteSegment) + 1, 2); + IMLSegment* segBranchNotTaken = segments[0]; + IMLSegment* segMerge = segments[1]; + + // link the segments + segMerge->SetLinkBranchTaken(currentWriteSegment->GetBranchTaken()); + segMerge->SetLinkBranchNotTaken(currentWriteSegment->GetBranchNotTaken()); + currentWriteSegment->SetLinkBranchTaken(segMerge); + currentWriteSegment->SetLinkBranchNotTaken(segBranchNotTaken); + segBranchNotTaken->SetLinkBranchNotTaken(segMerge); + // generate code for branch not taken segment + ppcImlGenContext.currentOutputSegment = segBranchNotTaken; + genSegmentBranchNotTaken(ppcImlGenContext); + cemu_assert_debug(ppcImlGenContext.currentOutputSegment == segBranchNotTaken); + // make merge segment the new write segment + ppcImlGenContext.currentOutputSegment = segMerge; + basicBlockInfo.appendSegment = segMerge; +} + +IMLReg _GetRegTemporaryS8(ppcImlGenContext_t* ppcImlGenContext, uint32 index); + +IMLRedirectInstOutput::IMLRedirectInstOutput(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* outputSegment) : m_context(ppcImlGenContext) +{ + m_prevSegment = ppcImlGenContext->currentOutputSegment; + cemu_assert_debug(ppcImlGenContext->currentOutputSegment == ppcImlGenContext->currentBasicBlock->appendSegment); + if (outputSegment == ppcImlGenContext->currentOutputSegment) + { + m_prevSegment = nullptr; + return; + } + m_context->currentBasicBlock->appendSegment = outputSegment; + m_context->currentOutputSegment = outputSegment; +} + +IMLRedirectInstOutput::~IMLRedirectInstOutput() +{ + if (m_prevSegment) + { + m_context->currentBasicBlock->appendSegment = m_prevSegment; + m_context->currentOutputSegment = m_prevSegment; + } +} + +// compare values and branch to segment with same index in segmentsOut. The last segment doesn't actually have any comparison and just is the default case. Thus compareValues is one shorter than count +void PPCIMLGen_CreateSegmentBranchedPathMultiple(ppcImlGenContext_t& ppcImlGenContext, PPCBasicBlockInfo& basicBlockInfo, IMLSegment** segmentsOut, IMLReg compareReg, sint32* compareValues, sint32 count, sint32 defaultCaseIndex) +{ + IMLSegment* currentWriteSegment = basicBlockInfo.GetSegmentForInstructionAppend(); + cemu_assert_debug(!currentWriteSegment->HasSuffixInstruction()); // must not already have a suffix instruction + + const sint32 numBranchSegments = count + 1; + const sint32 numCaseSegments = count; + + std::span<IMLSegment*> segments = ppcImlGenContext.InsertSegments(ppcImlGenContext.GetSegmentIndex(currentWriteSegment) + 1, numBranchSegments - 1 + numCaseSegments + 1); + IMLSegment** extraBranchSegments = segments.data(); + IMLSegment** caseSegments = segments.data() + numBranchSegments - 1; + IMLSegment* mergeSegment = segments[numBranchSegments - 1 + numCaseSegments]; + + // move links to the merge segment + mergeSegment->SetLinkBranchTaken(currentWriteSegment->GetBranchTaken()); + mergeSegment->SetLinkBranchNotTaken(currentWriteSegment->GetBranchNotTaken()); + currentWriteSegment->SetLinkBranchTaken(nullptr); + currentWriteSegment->SetLinkBranchNotTaken(nullptr); + + for (sint32 i=0; i<numCaseSegments; i++) + segmentsOut[i] = caseSegments[i]; + + IMLReg tmpBoolReg = _GetRegTemporaryS8(&ppcImlGenContext, 2); + + // the first branch segment is the original current write segment + auto GetBranchSegment = [&](sint32 index) { + if (index == 0) + return currentWriteSegment; + else + return extraBranchSegments[index - 1]; + }; + // link branch segments (taken: Link to case segment. NotTaken: Link to next branch segment. For the last one use a non-conditional jump) + for (sint32 i=0; i<numBranchSegments; i++) + { + IMLSegment* seg = GetBranchSegment(i); + if (i < numBranchSegments - 1) + { + cemu_assert_debug(i < numCaseSegments); + seg->SetLinkBranchTaken(caseSegments[i]); + seg->SetLinkBranchNotTaken(GetBranchSegment(i + 1)); + seg->AppendInstruction()->make_compare_s32(compareReg, compareValues[i], tmpBoolReg, IMLCondition::EQ); + seg->AppendInstruction()->make_conditional_jump(tmpBoolReg, true); + } + else + { + cemu_assert_debug(defaultCaseIndex < numCaseSegments); + seg->SetLinkBranchTaken(caseSegments[defaultCaseIndex]); + seg->AppendInstruction()->make_jump(); + } + } + // link case segments + for (sint32 i=0; i<numCaseSegments; i++) + { + IMLSegment* seg = caseSegments[i]; + if (i < numCaseSegments - 1) + { + seg->SetLinkBranchTaken(mergeSegment); + // -> Jumps are added after the instructions + } + else + { + seg->SetLinkBranchTaken(mergeSegment); + } + } + ppcImlGenContext.currentOutputSegment = mergeSegment; + basicBlockInfo.appendSegment = mergeSegment; +} + IMLReg PPCRecompilerImlGen_LookupReg(ppcImlGenContext_t* ppcImlGenContext, IMLName mappedName, IMLRegFormat regFormat) { auto it = ppcImlGenContext->mappedRegs.find(mappedName); @@ -212,32 +327,14 @@ IMLReg _GetRegTemporary(ppcImlGenContext_t* ppcImlGenContext, uint32 index) return PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + index); } -// get throw-away register. Only valid for the scope of a single translated instruction -// be careful to not collide with manually loaded temporary register +// get throw-away register +// be careful to not collide with other temporary register IMLReg _GetRegTemporaryS8(ppcImlGenContext_t* ppcImlGenContext, uint32 index) { cemu_assert_debug(index < 4); return PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + index); } -/* - * Loads a PPC fpr into any of the available IML FPU registers - * If loadNew is false, it will check first if the fpr is already loaded into any IML register - */ -IMLReg PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName, bool loadNew) -{ - return PPCRecompilerImlGen_LookupReg(ppcImlGenContext, mappedName, IMLRegFormat::F64); -} - -/* - * Checks if a PPC fpr register is already loaded into any IML register - * If not, it will create a new undefined temporary IML FPU register and map the name (effectively overwriting the old ppc register) - */ -IMLReg PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext_t* ppcImlGenContext, uint32 mappedName) -{ - return PPCRecompilerImlGen_LookupReg(ppcImlGenContext, mappedName, IMLRegFormat::F64); -} - bool PPCRecompiler_canInlineFunction(MPTR functionPtr, sint32* functionInstructionCount) { for (sint32 i = 0; i < 6; i++) @@ -1050,15 +1147,15 @@ bool PPCRecompilerImlGen_SRAW(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod // load masked shift factor into temporary register ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_AND, regTmpShiftAmount, regB, 0x3F); - ppcImlGenContext->emitInst().make_compare_s32(regTmpShiftAmount, 32, regTmpCondBool, IMLCondition::UNSIGNED_GT); + ppcImlGenContext->emitInst().make_compare_s32(regTmpShiftAmount, 31, regTmpCondBool, IMLCondition::UNSIGNED_GT); ppcImlGenContext->emitInst().make_conditional_jump(regTmpCondBool, true); PPCIMLGen_CreateSegmentBranchedPath(*ppcImlGenContext, *ppcImlGenContext->currentBasicBlock, [&](ppcImlGenContext_t& genCtx) { - /* branch taken */ - genCtx.emitInst().make_r_r_r(PPCREC_IML_OP_RIGHT_SHIFT_S, regA, regS, regTmpShiftAmount); - genCtx.emitInst().make_compare_s32(regA, 0, regCarry, IMLCondition::NEQ); // if the sign bit is still set it also means it was shifted out and we can set carry + /* branch taken, shift size 32 or above */ + genCtx.emitInst().make_r_r_s32(PPCREC_IML_OP_RIGHT_SHIFT_S, regA, regS, 31); // shift the sign bit into all the bits + genCtx.emitInst().make_compare_s32(regA, 0, regCarry, IMLCondition::NEQ); }, [&](ppcImlGenContext_t& genCtx) { @@ -1073,6 +1170,8 @@ bool PPCRecompilerImlGen_SRAW(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod genCtx.emitInst().make_r_r_r(PPCREC_IML_OP_RIGHT_SHIFT_S, regA, regS, regTmpShiftAmount); } ); + if (opcode & PPC_OPC_RC) + PPCImlGen_UpdateCR0(ppcImlGenContext, regA); return true; } @@ -1909,23 +2008,23 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; - case 12: // multiply scalar - if (PPCRecompilerImlGen_PS_MULS0(ppcImlGenContext, opcode) == false) + case 12: // PS_MULS0 + if (PPCRecompilerImlGen_PS_MULSX(ppcImlGenContext, opcode, false) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; - case 13: // multiply scalar - if (PPCRecompilerImlGen_PS_MULS1(ppcImlGenContext, opcode) == false) + case 13: // PS_MULS1 + if (PPCRecompilerImlGen_PS_MULSX(ppcImlGenContext, opcode, true) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; - case 14: // multiply add scalar - if (PPCRecompilerImlGen_PS_MADDS0(ppcImlGenContext, opcode) == false) + case 14: // PS_MADDS0 + if (PPCRecompilerImlGen_PS_MADDSX(ppcImlGenContext, opcode, false) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; - case 15: // multiply add scalar - if (PPCRecompilerImlGen_PS_MADDS1(ppcImlGenContext, opcode) == false) + case 15: // PS_MADDS1 + if (PPCRecompilerImlGen_PS_MADDSX(ppcImlGenContext, opcode, true) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; @@ -1992,22 +2091,22 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; - case 28: // multiply sub paired - if (PPCRecompilerImlGen_PS_MSUB(ppcImlGenContext, opcode) == false) + case 28: // PS_MSUB + if (PPCRecompilerImlGen_PS_MSUB(ppcImlGenContext, opcode, false) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; - case 29: // multiply add paired + case 29: // PS_MADD if (PPCRecompilerImlGen_PS_MADD(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; - case 30: // negative multiply sub paired - if (PPCRecompilerImlGen_PS_NMSUB(ppcImlGenContext, opcode) == false) + case 30: // PS_NMSUB + if (PPCRecompilerImlGen_PS_MSUB(ppcImlGenContext, opcode, true) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; - case 31: // negative multiply add paired + case 31: // PS_NMADD if (PPCRecompilerImlGen_PS_NMADD(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; @@ -2339,8 +2438,8 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) case 534: // LWBRX PPCRecompilerImlGen_LOAD_INDEXED(ppcImlGenContext, opcode, 32, false, false, false); break; - case 535: - if (PPCRecompilerImlGen_LFSX(ppcImlGenContext, opcode) == false) + case 535: // LFSX + if (PPCRecompilerImlGen_LFSX_LFSUX_LFDX_LFDUX(ppcImlGenContext, opcode, false, false) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; @@ -2348,8 +2447,8 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) if (PPCRecompilerImlGen_SRW(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; - case 567: - if (PPCRecompilerImlGen_LFSUX(ppcImlGenContext, opcode) == false) + case 567: // LFSUX + if (PPCRecompilerImlGen_LFSX_LFSUX_LFDX_LFDUX(ppcImlGenContext, opcode, true, false) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; @@ -2360,13 +2459,13 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) case 598: PPCRecompilerImlGen_SYNC(ppcImlGenContext, opcode); break; - case 599: - if (PPCRecompilerImlGen_LFDX(ppcImlGenContext, opcode) == false) + case 599: // LFDX + if (PPCRecompilerImlGen_LFSX_LFSUX_LFDX_LFDUX(ppcImlGenContext, opcode, false, true) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; - case 631: - if (PPCRecompilerImlGen_LFDUX(ppcImlGenContext, opcode) == false) + case 631: // LFDUX + if (PPCRecompilerImlGen_LFSX_LFSUX_LFDX_LFDUX(ppcImlGenContext, opcode, true, true) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; @@ -2374,20 +2473,24 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) if (!PPCRecompilerImlGen_STORE_INDEXED(ppcImlGenContext, opcode, 32, false, false)) unsupportedInstructionFound = true; break; - case 663: - if (PPCRecompilerImlGen_STFSX(ppcImlGenContext, opcode) == false) + case 663: // STFSX + if (PPCRecompilerImlGen_STFSX_STFSUX_STFDX_STFDUX(ppcImlGenContext, opcode, false, false) == false) unsupportedInstructionFound = true; break; - case 695: - if (PPCRecompilerImlGen_STFSUX(ppcImlGenContext, opcode) == false) + case 695: // STFSUX + if (PPCRecompilerImlGen_STFSX_STFSUX_STFDX_STFDUX(ppcImlGenContext, opcode, true, false) == false) unsupportedInstructionFound = true; break; case 725: if (PPCRecompilerImlGen_STSWI(ppcImlGenContext, opcode) == false) unsupportedInstructionFound = true; break; - case 727: - if (PPCRecompilerImlGen_STFDX(ppcImlGenContext, opcode) == false) + case 727: // STFDX + if (PPCRecompilerImlGen_STFSX_STFSUX_STFDX_STFDUX(ppcImlGenContext, opcode, false, true) == false) + unsupportedInstructionFound = true; + break; + case 759: // STFDUX + if (PPCRecompilerImlGen_STFSX_STFSUX_STFDX_STFDUX(ppcImlGenContext, opcode, true, true) == false) unsupportedInstructionFound = true; break; case 790: // LHBRX @@ -2488,53 +2591,53 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) case 47: PPCRecompilerImlGen_STMW(ppcImlGenContext, opcode); break; - case 48: - if (PPCRecompilerImlGen_LFS(ppcImlGenContext, opcode) == false) + case 48: // LFS + if (PPCRecompilerImlGen_LFS_LFSU_LFD_LFDU(ppcImlGenContext, opcode, false, false) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; - case 49: - if (PPCRecompilerImlGen_LFSU(ppcImlGenContext, opcode) == false) + case 49: // LFSU + if (PPCRecompilerImlGen_LFS_LFSU_LFD_LFDU(ppcImlGenContext, opcode, true, false) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; - case 50: - if (PPCRecompilerImlGen_LFD(ppcImlGenContext, opcode) == false) + case 50: // LFD + if (PPCRecompilerImlGen_LFS_LFSU_LFD_LFDU(ppcImlGenContext, opcode, false, true) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; - case 51: - if (PPCRecompilerImlGen_LFDU(ppcImlGenContext, opcode) == false) + case 51: // LFDU + if (PPCRecompilerImlGen_LFS_LFSU_LFD_LFDU(ppcImlGenContext, opcode, true, true) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; - case 52: - if (PPCRecompilerImlGen_STFS(ppcImlGenContext, opcode) == false) + case 52: // STFS + if (PPCRecompilerImlGen_STFS_STFSU_STFD_STFDU(ppcImlGenContext, opcode, false, false) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; - case 53: - if (PPCRecompilerImlGen_STFSU(ppcImlGenContext, opcode) == false) + case 53: // STFSU + if (PPCRecompilerImlGen_STFS_STFSU_STFD_STFDU(ppcImlGenContext, opcode, true, false) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; - case 54: - if (PPCRecompilerImlGen_STFD(ppcImlGenContext, opcode) == false) + case 54: // STFD + if (PPCRecompilerImlGen_STFS_STFSU_STFD_STFDU(ppcImlGenContext, opcode, false, true) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; - case 55: - if (PPCRecompilerImlGen_STFDU(ppcImlGenContext, opcode) == false) + case 55: // STFDU + if (PPCRecompilerImlGen_STFS_STFSU_STFD_STFDU(ppcImlGenContext, opcode, true, true) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 56: - if (PPCRecompilerImlGen_PSQ_L(ppcImlGenContext, opcode) == false) + if (PPCRecompilerImlGen_PSQ_L(ppcImlGenContext, opcode, false) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 57: - if (PPCRecompilerImlGen_PSQ_LU(ppcImlGenContext, opcode) == false) + if (PPCRecompilerImlGen_PSQ_L(ppcImlGenContext, opcode, true) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; @@ -2587,12 +2690,12 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) } break; case 60: - if (PPCRecompilerImlGen_PSQ_ST(ppcImlGenContext, opcode) == false) + if (PPCRecompilerImlGen_PSQ_ST(ppcImlGenContext, opcode, false) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; case 61: - if (PPCRecompilerImlGen_PSQ_STU(ppcImlGenContext, opcode) == false) + if (PPCRecompilerImlGen_PSQ_ST(ppcImlGenContext, opcode, true) == false) unsupportedInstructionFound = true; ppcImlGenContext->hasFPUInstruction = true; break; @@ -2702,7 +2805,6 @@ bool PPCRecompiler_decodePPCInstruction(ppcImlGenContext_t* ppcImlGenContext) } // returns false if code flow is not interrupted -// continueDefaultPath: Controls if bool PPCRecompiler_CheckIfInstructionEndsSegment(PPCFunctionBoundaryTracker& boundaryTracker, uint32 instructionAddress, uint32 opcode, bool& makeNextInstEnterable, bool& continueDefaultPath, bool& hasBranchTarget, uint32& branchTarget) { hasBranchTarget = false; diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGenFPU.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGenFPU.cpp index 96a7b560..7eb8a4b6 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGenFPU.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGenFPU.cpp @@ -3,176 +3,82 @@ #include "PPCRecompiler.h" #include "PPCRecompilerIml.h" #include "Cafe/GameProfile/GameProfile.h" +#include "IML/IML.h" ATTR_MS_ABI double frsqrte_espresso(double input); ATTR_MS_ABI double fres_espresso(double input); IMLReg _GetRegCR(ppcImlGenContext_t* ppcImlGenContext, uint8 crReg, uint8 crBit); -void PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext_t* ppcImlGenContext, IMLReg registerDestination, IMLReg registerMemory, sint32 immS32, uint32 mode, bool switchEndian, IMLReg registerGQR = IMLREG_INVALID) +#define DefinePS0(name, regIndex) IMLReg name = _GetFPRRegPS0(ppcImlGenContext, regIndex); +#define DefinePS1(name, regIndex) IMLReg name = _GetFPRRegPS1(ppcImlGenContext, regIndex); +#define DefinePSX(name, regIndex, isPS1) IMLReg name = isPS1 ? _GetFPRRegPS1(ppcImlGenContext, regIndex) : _GetFPRRegPS0(ppcImlGenContext, regIndex); +#define DefineTempFPR(name, index) IMLReg name = _GetFPRTemp(ppcImlGenContext, index); + +IMLReg _GetFPRRegPS0(ppcImlGenContext_t* ppcImlGenContext, uint32 regIndex) { - // load from memory - IMLInstruction* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); - imlInstruction->type = PPCREC_IML_TYPE_FPR_LOAD; - imlInstruction->operation = 0; - imlInstruction->op_storeLoad.registerData = registerDestination; - imlInstruction->op_storeLoad.registerMem = registerMemory; - imlInstruction->op_storeLoad.registerGQR = registerGQR; - imlInstruction->op_storeLoad.immS32 = immS32; - imlInstruction->op_storeLoad.mode = mode; - imlInstruction->op_storeLoad.flags2.swapEndian = switchEndian; + cemu_assert_debug(regIndex < 32); + return PPCRecompilerImlGen_LookupReg(ppcImlGenContext, PPCREC_NAME_FPR_HALF + regIndex * 2 + 0, IMLRegFormat::F64); } -void PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory_indexed(ppcImlGenContext_t* ppcImlGenContext, IMLReg registerDestination, IMLReg registerMemory1, IMLReg registerMemory2, uint32 mode, bool switchEndian, IMLReg registerGQR = IMLREG_INVALID) +IMLReg _GetFPRRegPS1(ppcImlGenContext_t* ppcImlGenContext, uint32 regIndex) { - // load from memory - IMLInstruction* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); - imlInstruction->type = PPCREC_IML_TYPE_FPR_LOAD_INDEXED; - imlInstruction->operation = 0; - imlInstruction->op_storeLoad.registerData = registerDestination; - imlInstruction->op_storeLoad.registerMem = registerMemory1; - imlInstruction->op_storeLoad.registerMem2 = registerMemory2; - imlInstruction->op_storeLoad.registerGQR = registerGQR; - imlInstruction->op_storeLoad.immS32 = 0; - imlInstruction->op_storeLoad.mode = mode; - imlInstruction->op_storeLoad.flags2.swapEndian = switchEndian; + cemu_assert_debug(regIndex < 32); + return PPCRecompilerImlGen_LookupReg(ppcImlGenContext, PPCREC_NAME_FPR_HALF + regIndex * 2 + 1, IMLRegFormat::F64); } -void PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r(ppcImlGenContext_t* ppcImlGenContext, IMLReg registerSource, IMLReg registerMemory, sint32 immS32, uint32 mode, bool switchEndian, IMLReg registerGQR = IMLREG_INVALID) +IMLReg _GetFPRTemp(ppcImlGenContext_t* ppcImlGenContext, uint32 index) { - // store to memory - IMLInstruction* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); - imlInstruction->type = PPCREC_IML_TYPE_FPR_STORE; - imlInstruction->operation = 0; - imlInstruction->op_storeLoad.registerData = registerSource; - imlInstruction->op_storeLoad.registerMem = registerMemory; - imlInstruction->op_storeLoad.registerGQR = registerGQR; - imlInstruction->op_storeLoad.immS32 = immS32; - imlInstruction->op_storeLoad.mode = mode; - imlInstruction->op_storeLoad.flags2.swapEndian = switchEndian; + cemu_assert_debug(index < 4); + return PPCRecompilerImlGen_LookupReg(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0 + index, IMLRegFormat::F64); } -void PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r_indexed(ppcImlGenContext_t* ppcImlGenContext, IMLReg registerSource, IMLReg registerMemory1, IMLReg registerMemory2, sint32 immS32, uint32 mode, bool switchEndian, IMLReg registerGQR = IMLREG_INVALID) +IMLReg _GetFPRReg(ppcImlGenContext_t* ppcImlGenContext, uint32 regIndex, bool selectPS1) { - // store to memory - IMLInstruction* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); - imlInstruction->type = PPCREC_IML_TYPE_FPR_STORE_INDEXED; - imlInstruction->operation = 0; - imlInstruction->op_storeLoad.registerData = registerSource; - imlInstruction->op_storeLoad.registerMem = registerMemory1; - imlInstruction->op_storeLoad.registerMem2 = registerMemory2; - imlInstruction->op_storeLoad.registerGQR = registerGQR; - imlInstruction->op_storeLoad.immS32 = immS32; - imlInstruction->op_storeLoad.mode = mode; - imlInstruction->op_storeLoad.flags2.swapEndian = switchEndian; + cemu_assert_debug(regIndex < 32); + return PPCRecompilerImlGen_LookupReg(ppcImlGenContext, PPCREC_NAME_FPR_HALF + regIndex * 2 + (selectPS1 ? 1 : 0), IMLRegFormat::F64); } -void PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext_t* ppcImlGenContext, sint32 operation, IMLReg registerResult, IMLReg registerOperand, sint32 crRegister=PPC_REC_INVALID_REGISTER) +void PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext_t* ppcImlGenContext, IMLReg fprRegister, bool flushDenormals=false) { - // fpr OP fpr - IMLInstruction* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); - imlInstruction->type = PPCREC_IML_TYPE_FPR_R_R; - imlInstruction->operation = operation; - imlInstruction->op_fpr_r_r.regR = registerResult; - imlInstruction->op_fpr_r_r.regA = registerOperand; -} - -void PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r(ppcImlGenContext_t* ppcImlGenContext, sint32 operation, IMLReg registerResult, IMLReg registerOperand1, IMLReg registerOperand2, sint32 crRegister=PPC_REC_INVALID_REGISTER) -{ - // fpr = OP (fpr,fpr) - IMLInstruction* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); - imlInstruction->type = PPCREC_IML_TYPE_FPR_R_R_R; - imlInstruction->operation = operation; - imlInstruction->op_fpr_r_r_r.regR = registerResult; - imlInstruction->op_fpr_r_r_r.regA = registerOperand1; - imlInstruction->op_fpr_r_r_r.regB = registerOperand2; -} - -void PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r_r(ppcImlGenContext_t* ppcImlGenContext, sint32 operation, IMLReg registerResult, IMLReg registerOperandA, IMLReg registerOperandB, IMLReg registerOperandC, sint32 crRegister=PPC_REC_INVALID_REGISTER) -{ - // fpr = OP (fpr,fpr,fpr) - IMLInstruction* imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); - imlInstruction->type = PPCREC_IML_TYPE_FPR_R_R_R_R; - imlInstruction->operation = operation; - imlInstruction->op_fpr_r_r_r_r.regR = registerResult; - imlInstruction->op_fpr_r_r_r_r.regA = registerOperandA; - imlInstruction->op_fpr_r_r_r_r.regB = registerOperandB; - imlInstruction->op_fpr_r_r_r_r.regC = registerOperandC; -} - -void PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext_t* ppcImlGenContext, IMLInstruction* imlInstruction, sint32 operation, IMLReg registerResult) -{ - // OP (fpr) - if(imlInstruction == NULL) - imlInstruction = PPCRecompilerImlGen_generateNewEmptyInstruction(ppcImlGenContext); - imlInstruction->type = PPCREC_IML_TYPE_FPR_R; - imlInstruction->operation = operation; - imlInstruction->op_fpr_r.regR = registerResult; -} - -/* - * Rounds the bottom double to single precision (if single precision accuracy is emulated) - */ -void PPRecompilerImmGen_optionalRoundBottomFPRToSinglePrecision(ppcImlGenContext_t* ppcImlGenContext, IMLReg fprRegister, bool flushDenormals=false) -{ - PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext, NULL, PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_BOTTOM, fprRegister); + ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_BOTTOM, fprRegister); if( flushDenormals ) assert_dbg(); } -/* - * Rounds pair of doubles to single precision (if single precision accuracy is emulated) - */ -void PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext_t* ppcImlGenContext, IMLReg fprRegister, bool flushDenormals=false) -{ - PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext, NULL, PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_PAIR, fprRegister); - if( flushDenormals ) - assert_dbg(); -} - -bool PPCRecompilerImlGen_LFS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +bool PPCRecompilerImlGen_LFS_LFSU_LFD_LFDU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool withUpdate, bool isDouble) { sint32 rA, frD; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, imm); - // get memory gpr register index IMLReg gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - // get fpr register index - IMLReg fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - if( ppcImlGenContext->LSQE ) + if (withUpdate) { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext, fprRegister, gprRegister, imm, PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1, true); + // add imm to memory register + cemu_assert_debug(rA != 0); + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_ADD, gprRegister, gprRegister, (sint32)imm); + imm = 0; // set imm to 0 so we dont add it twice + } + DefinePS0(fpPs0, frD); + if (isDouble) + { + // LFD/LFDU + ppcImlGenContext->emitInst().make_fpr_r_memory(fpPs0, gprRegister, imm, PPCREC_FPR_LD_MODE_DOUBLE, true); } else { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext, fprRegister, gprRegister, imm, PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0, true); + // LFS/LFSU + ppcImlGenContext->emitInst().make_fpr_r_memory(fpPs0, gprRegister, imm, PPCREC_FPR_LD_MODE_SINGLE, true); + if( ppcImlGenContext->LSQE ) + { + DefinePS1(fpPs1, frD); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fpPs1, fpPs0); + } } return true; } -bool PPCRecompilerImlGen_LFSU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rA, frD; - uint32 imm; - PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, imm); - // get memory gpr register index - IMLReg gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - // add imm to memory register - ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_ADD, gprRegister, gprRegister, (sint32)imm); - // get fpr register index - IMLReg fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - if( ppcImlGenContext->LSQE ) - { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext, fprRegister, gprRegister, 0, PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1, true); - } - else - { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext, fprRegister, gprRegister, 0, PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0, true); - } - return true; -} - -bool PPCRecompilerImlGen_LFSX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +bool PPCRecompilerImlGen_LFSX_LFSUX_LFDX_LFDUX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool withUpdate, bool isDouble) { sint32 rA, frD, rB; PPC_OPC_TEMPL_X(opcode, frD, rA, rB); @@ -184,148 +90,51 @@ bool PPCRecompilerImlGen_LFSX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod // get memory gpr registers IMLReg gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); IMLReg gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); - // get fpr register index - IMLReg fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - if( ppcImlGenContext->LSQE ) + if (withUpdate) + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_ADD, gprRegister1, gprRegister1, gprRegister2); + DefinePS0(fpPs0, frD); + if (isDouble) { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory_indexed(ppcImlGenContext, fprRegister, gprRegister1, gprRegister2, PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1, true); + if (withUpdate) + ppcImlGenContext->emitInst().make_fpr_r_memory(fpPs0, gprRegister1, 0, PPCREC_FPR_LD_MODE_DOUBLE, true); + else + ppcImlGenContext->emitInst().make_fpr_r_memory_indexed(fpPs0, gprRegister1, gprRegister2, PPCREC_FPR_LD_MODE_DOUBLE, true); } else { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory_indexed(ppcImlGenContext, fprRegister, gprRegister1, gprRegister2, PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0, true); + if (withUpdate) + ppcImlGenContext->emitInst().make_fpr_r_memory( fpPs0, gprRegister1, 0, PPCREC_FPR_LD_MODE_SINGLE, true); + else + ppcImlGenContext->emitInst().make_fpr_r_memory_indexed( fpPs0, gprRegister1, gprRegister2, PPCREC_FPR_LD_MODE_SINGLE, true); + if( ppcImlGenContext->LSQE ) + { + DefinePS1(fpPs1, frD); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fpPs1, fpPs0); + } } return true; } -bool PPCRecompilerImlGen_LFSUX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +bool PPCRecompilerImlGen_STFS_STFSU_STFD_STFDU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool withUpdate, bool isDouble) { - sint32 rA, frD, rB; - PPC_OPC_TEMPL_X(opcode, frD, rA, rB); - if( rA == 0 ) + sint32 rA, frD; + uint32 imm; + PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, imm); + IMLReg gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + DefinePS0(fpPs0, frD); + if (withUpdate) { - debugBreakpoint(); - return false; - } - // get memory gpr registers - IMLReg gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - IMLReg gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); - // add rB to rA (if rA != 0) - ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_ADD, gprRegister1, gprRegister1, gprRegister2); - // get fpr register index - IMLReg fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - if( ppcImlGenContext->LSQE ) - { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext, fprRegister, gprRegister1, 0, PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1, true); + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_ADD, gprRegister, gprRegister, (sint32)imm); + imm = 0; } + if (isDouble) + ppcImlGenContext->emitInst().make_fpr_memory_r(fpPs0, gprRegister, imm, PPCREC_FPR_ST_MODE_DOUBLE, true); else - { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext, fprRegister, gprRegister1, 0, PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0, true); - } + ppcImlGenContext->emitInst().make_fpr_memory_r(fpPs0, gprRegister, imm, PPCREC_FPR_ST_MODE_SINGLE, true); return true; } -bool PPCRecompilerImlGen_LFD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rA, frD; - uint32 imm; - PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, imm); - if( rA == 0 ) - { - assert_dbg(); - } - // get memory gpr register index - IMLReg gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - // get fpr register index - IMLReg fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext, fprRegister, gprRegister, imm, PPCREC_FPR_LD_MODE_DOUBLE_INTO_PS0, true); - return true; -} - -bool PPCRecompilerImlGen_LFDU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rA, frD; - uint32 imm; - PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, imm); - if( rA == 0 ) - { - assert_dbg(); - } - // get memory gpr register index - IMLReg gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - // add imm to memory register - ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_ADD, gprRegister, gprRegister, (sint32)imm); - // get fpr register index - IMLReg fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - // emit load iml - PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext, fprRegister, gprRegister, 0, PPCREC_FPR_LD_MODE_DOUBLE_INTO_PS0, true); - return true; -} - -bool PPCRecompilerImlGen_LFDX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rA, frD, rB; - PPC_OPC_TEMPL_X(opcode, frD, rA, rB); - if( rA == 0 ) - { - debugBreakpoint(); - return false; - } - // get memory gpr registers - IMLReg gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - IMLReg gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); - // get fpr register index - IMLReg fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory_indexed(ppcImlGenContext, fprRegister, gprRegister1, gprRegister2, PPCREC_FPR_LD_MODE_DOUBLE_INTO_PS0, true); - return true; -} - -bool PPCRecompilerImlGen_LFDUX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rA, frD, rB; - PPC_OPC_TEMPL_X(opcode, frD, rA, rB); - if( rA == 0 ) - { - debugBreakpoint(); - return false; - } - IMLReg gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - IMLReg gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); - // add rB to rA (if rA != 0) - ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_ADD, gprRegister1, gprRegister1, gprRegister2); - IMLReg fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext, fprRegister, gprRegister1, 0, PPCREC_FPR_LD_MODE_DOUBLE_INTO_PS0, true); - return true; -} - -bool PPCRecompilerImlGen_STFS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rA, frD; - uint32 imm; - PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, imm); - IMLReg gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - IMLReg fprRegister = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - - PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r(ppcImlGenContext, fprRegister, gprRegister, imm, PPCREC_FPR_ST_MODE_SINGLE_FROM_PS0, true); - return true; -} - -bool PPCRecompilerImlGen_STFSU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rA, frD; - uint32 imm; - PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, imm); - // get memory gpr register index - IMLReg gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - // add imm to memory register - ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_ADD, gprRegister, gprRegister, (sint32)imm); - // get fpr register index - IMLReg fprRegister = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - - PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r(ppcImlGenContext, fprRegister, gprRegister, 0, PPCREC_FPR_ST_MODE_SINGLE_FROM_PS0, true); - return true; -} - -bool PPCRecompilerImlGen_STFSX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +bool PPCRecompilerImlGen_STFSX_STFSUX_STFDX_STFDUX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool hasUpdate, bool isDouble) { sint32 rA, frS, rB; PPC_OPC_TEMPL_X(opcode, frS, rA, rB); @@ -337,101 +146,25 @@ bool PPCRecompilerImlGen_STFSX(ppcImlGenContext_t* ppcImlGenContext, uint32 opco // get memory gpr registers IMLReg gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); IMLReg gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); - // get fpr register index - IMLReg fprRegister = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frS); + if (hasUpdate) + { + ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_ADD, gprRegister1, gprRegister1, gprRegister2); + } + DefinePS0(fpPs0, frS); + auto mode = isDouble ? PPCREC_FPR_ST_MODE_DOUBLE : PPCREC_FPR_ST_MODE_SINGLE; if( ppcImlGenContext->LSQE ) { - PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r_indexed(ppcImlGenContext, fprRegister, gprRegister1, gprRegister2, 0, PPCREC_FPR_ST_MODE_SINGLE_FROM_PS0, true); + if (hasUpdate) + ppcImlGenContext->emitInst().make_fpr_memory_r(fpPs0, gprRegister1, 0, mode, true); + else + ppcImlGenContext->emitInst().make_fpr_memory_r_indexed(fpPs0, gprRegister1, gprRegister2, 0, mode, true); } else { - PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r_indexed(ppcImlGenContext, fprRegister, gprRegister1, gprRegister2, 0, PPCREC_FPR_ST_MODE_SINGLE_FROM_PS0, true); - } - return true; -} - - -bool PPCRecompilerImlGen_STFSUX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rA, frS, rB; - PPC_OPC_TEMPL_X(opcode, frS, rA, rB); - if( rA == 0 ) - { - debugBreakpoint(); - return false; - } - // get memory gpr registers - IMLReg gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - IMLReg gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); - // get fpr register index - IMLReg fprRegister = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frS); - // calculate EA in rA - ppcImlGenContext->emitInst().make_r_r_r(PPCREC_IML_OP_ADD, gprRegister1, gprRegister1, gprRegister2); - - PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r(ppcImlGenContext, fprRegister, gprRegister1, 0, PPCREC_FPR_ST_MODE_SINGLE_FROM_PS0, true); - return true; -} - -bool PPCRecompilerImlGen_STFD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rA, frD; - uint32 imm; - PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, imm); - if( rA == 0 ) - { - debugBreakpoint(); - return false; - } - // get memory gpr register index - IMLReg gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - // get fpr register index - IMLReg fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r(ppcImlGenContext, fprRegister, gprRegister, imm, PPCREC_FPR_ST_MODE_DOUBLE_FROM_PS0, true); - return true; -} - -bool PPCRecompilerImlGen_STFDU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rA, frD; - uint32 imm; - PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, imm); - if( rA == 0 ) - { - debugBreakpoint(); - return false; - } - // get memory gpr register index - IMLReg gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - // add imm to memory register - ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_ADD, gprRegister, gprRegister, (sint32)imm); - // get fpr register index - IMLReg fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - - PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r(ppcImlGenContext, fprRegister, gprRegister, 0, PPCREC_FPR_ST_MODE_DOUBLE_FROM_PS0, true); - return true; -} - -bool PPCRecompilerImlGen_STFDX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 rA, frS, rB; - PPC_OPC_TEMPL_X(opcode, frS, rA, rB); - if( rA == 0 ) - { - debugBreakpoint(); - return false; - } - // get memory gpr registers - IMLReg gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); - IMLReg gprRegister2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); - // get fpr register index - IMLReg fprRegister = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frS); - if( ppcImlGenContext->LSQE ) - { - PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r_indexed(ppcImlGenContext, fprRegister, gprRegister1, gprRegister2, 0, PPCREC_FPR_ST_MODE_DOUBLE_FROM_PS0, true); - } - else - { - PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r_indexed(ppcImlGenContext, fprRegister, gprRegister1, gprRegister2, 0, PPCREC_FPR_ST_MODE_DOUBLE_FROM_PS0, true); + if (hasUpdate) + ppcImlGenContext->emitInst().make_fpr_memory_r(fpPs0, gprRegister1, 0, mode, true); + else + ppcImlGenContext->emitInst().make_fpr_memory_r_indexed(fpPs0, gprRegister1, gprRegister2, 0, mode, true); } return true; } @@ -440,7 +173,7 @@ bool PPCRecompilerImlGen_STFIWX(ppcImlGenContext_t* ppcImlGenContext, uint32 opc { sint32 rA, frS, rB; PPC_OPC_TEMPL_X(opcode, frS, rA, rB); - // get memory gpr registers + DefinePS0(fpPs0, frS); IMLReg gprRegister1; IMLReg gprRegister2; if( rA != 0 ) @@ -454,12 +187,10 @@ bool PPCRecompilerImlGen_STFIWX(ppcImlGenContext_t* ppcImlGenContext, uint32 opc gprRegister1 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rB); gprRegister2 = IMLREG_INVALID; } - // get fpr register index - IMLReg fprRegister = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frS); if( rA != 0 ) - PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r_indexed(ppcImlGenContext, fprRegister, gprRegister1, gprRegister2, 0, PPCREC_FPR_ST_MODE_UI32_FROM_PS0, true); + ppcImlGenContext->emitInst().make_fpr_memory_r_indexed(fpPs0, gprRegister1, gprRegister2, 0, PPCREC_FPR_ST_MODE_UI32_FROM_PS0, true); else - PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r(ppcImlGenContext, fprRegister, gprRegister1, 0, PPCREC_FPR_ST_MODE_UI32_FROM_PS0, true); + ppcImlGenContext->emitInst().make_fpr_memory_r(fpPs0, gprRegister1, 0, PPCREC_FPR_ST_MODE_UI32_FROM_PS0, true); return true; } @@ -468,13 +199,10 @@ bool PPCRecompilerImlGen_FADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod sint32 frD, frA, frB, frC; PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); PPC_ASSERT(frC==0); - - // load registers - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_BOTTOM, fprRegisterD, fprRegisterA, fprRegisterB); + DefinePS0(fprA, frA); + DefinePS0(fprB, frB); + DefinePS0(fprD, frD); + ppcImlGenContext->emitInst().make_fpr_r_r_r(PPCREC_IML_OP_FPR_ADD, fprD, fprA, fprB); return true; } @@ -483,13 +211,10 @@ bool PPCRecompilerImlGen_FSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod sint32 frD, frA, frB, frC; PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); PPC_ASSERT(frC==0); - - // load registers - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - // subtract bottom double of frB from bottom double of frD - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SUB_BOTTOM, fprRegisterD, fprRegisterA, fprRegisterB); + DefinePS0(fprA, frA); + DefinePS0(fprB, frB); + DefinePS0(fprD, frD); + ppcImlGenContext->emitInst().make_fpr_r_r_r(PPCREC_IML_OP_FPR_SUB, fprD, fprA, fprB); return true; } @@ -504,15 +229,14 @@ bool PPCRecompilerImlGen_FMUL(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod frA = frC; frC = temp; } - // load registers - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + DefinePS0(fprA, frA); + DefinePS0(fprC, frC); + DefinePS0(fprD, frD); // move frA to frD (if different register) - if( fprRegisterD != fprRegisterA ) - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, fprRegisterD, fprRegisterA); // always copy ps0 and ps1 + if( frD != frA ) + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprA); // multiply bottom double of frD with bottom double of frB - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM, fprRegisterD, fprRegisterC); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprD, fprC); return true; } @@ -521,27 +245,25 @@ bool PPCRecompilerImlGen_FDIV(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod sint32 frD, frA, frB, frC_unused; PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC_unused); PPC_ASSERT(frB==0); - // load registers - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frA); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frB); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frD); - + DefinePS0(fprA, frA); + DefinePS0(fprB, frB); + DefinePS0(fprD, frD); if( frB == frD && frA != frB ) { - IMLReg fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); + DefineTempFPR(fprTemp, 0); // move frA to temporary register - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, fprRegisterTemp, fprRegisterA); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTemp, fprA); // divide bottom double of temporary register by bottom double of frB - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_DIVIDE_BOTTOM, fprRegisterTemp, fprRegisterB); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_DIVIDE, fprTemp, fprB); // move result to frD - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterTemp); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprTemp); return true; } // move frA to frD (if different register) - if( fprRegisterD != fprRegisterA ) - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterA); // copy ps0 + if( frD != frA ) + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprA); // copy ps0 // divide bottom double of frD by bottom double of frB - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_DIVIDE_BOTTOM, fprRegisterD, fprRegisterB); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_DIVIDE, fprD, fprB); return true; } @@ -549,38 +271,37 @@ bool PPCRecompilerImlGen_FMADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opco { sint32 frD, frA, frB, frC; PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); - // load registers - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + DefinePS0(fprA, frA); + DefinePS0(fprB, frB); + DefinePS0(fprC, frC); + DefinePS0(fprD, frD); // if frB is already in frD we need a temporary register to store the product of frA*frC if( frB == frD ) { - IMLReg fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); + DefineTempFPR(fprTemp, 0); // move frA to temporary register - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, fprRegisterTemp, fprRegisterA); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTemp, fprA); // multiply bottom double of temporary register with bottom double of frC - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM, fprRegisterTemp, fprRegisterC); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTemp, fprC); // add result to frD - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_BOTTOM, fprRegisterD, fprRegisterTemp); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprD, fprTemp); return true; } // if frC == frD -> swap registers, we assume that frC != frD - if( fprRegisterD == fprRegisterC ) + if( frD == frC ) { // swap frA and frC - IMLReg temp = fprRegisterA; - fprRegisterA = fprRegisterC; - fprRegisterC = temp; + IMLReg temp = fprA; + fprA = fprC; + fprC = temp; } // move frA to frD (if different register) - if( fprRegisterD != fprRegisterA ) - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, fprRegisterD, fprRegisterA); // always copy ps0 and ps1 + if( frD != frA ) + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprA); // always copy ps0 and ps1 // multiply bottom double of frD with bottom double of frC - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM, fprRegisterD, fprRegisterC); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprD, fprC); // add frB - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_BOTTOM, fprRegisterD, fprRegisterB); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprD, fprB); return true; } @@ -588,32 +309,34 @@ bool PPCRecompilerImlGen_FMSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opco { sint32 frD, frA, frB, frC; PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); - // load registers - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - // if frB is already in frD we need a temporary register to store the product of frA*frC + DefinePS0(fprA, frA); + DefinePS0(fprB, frB); + DefinePS0(fprC, frC); + DefinePS0(fprD, frD); if( frB == frD ) { - // not implemented + // if frB is already in frD we need a temporary register to store the product of frA*frC + DefineTempFPR(fprTemp, 0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTemp, fprA); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTemp, fprC); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_SUB, fprTemp, fprB); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprTemp); return false; } - // if frC == frD -> swap registers, we assume that frC != frD - if( fprRegisterD == fprRegisterC ) + if( frD == frC ) { // swap frA and frC - IMLReg temp = fprRegisterA; - fprRegisterA = fprRegisterC; - fprRegisterC = temp; + IMLReg temp = fprA; + fprA = fprC; + fprC = temp; } - // move frA to frD (if different register) - if( fprRegisterD != fprRegisterA ) - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, fprRegisterD, fprRegisterA); // always copy ps0 and ps1 + // move frA to frD + if( frD != frA ) + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprA); // multiply bottom double of frD with bottom double of frC - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM, fprRegisterD, fprRegisterC); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprD, fprC); // sub frB - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SUB_BOTTOM, fprRegisterD, fprRegisterB); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_SUB, fprD, fprB); return true; } @@ -621,51 +344,52 @@ bool PPCRecompilerImlGen_FNMSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opc { sint32 frD, frA, frB, frC; PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); - - // load registers - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + DefinePS0(fprA, frA); + DefinePS0(fprB, frB); + DefinePS0(fprC, frC); + DefinePS0(fprD, frD); // if frB is already in frD we need a temporary register to store the product of frA*frC if( frB == frD ) { - // hCPU->fpr[frD].fpr = -(hCPU->fpr[frA].fpr * hCPU->fpr[frC].fpr - hCPU->fpr[frD].fpr); - IMLReg fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); - //// negate frB/frD - //PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext, NULL,PPCREC_IML_OP_FPR_NEGATE_BOTTOM, fprRegisterD, true); + DefineTempFPR(fprTemp, 0); // move frA to temporary register - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterTemp, fprRegisterA); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTemp, fprA); // multiply bottom double of temporary register with bottom double of frC - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM, fprRegisterTemp, fprRegisterC); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTemp, fprC); // sub frB from temporary register - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SUB_BOTTOM, fprRegisterTemp, fprRegisterB); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_SUB, fprTemp, fprB); // negate result - PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext, NULL,PPCREC_IML_OP_FPR_NEGATE_BOTTOM, fprRegisterTemp); + ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_NEGATE, fprTemp); // move result to frD - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterTemp); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprTemp); return true; } // if frC == frD -> swap registers, we assume that frC != frD - if( fprRegisterD == fprRegisterC ) + if( frD == frC ) { // swap frA and frC - IMLReg temp = fprRegisterA; - fprRegisterA = fprRegisterC; - fprRegisterC = temp; + IMLReg temp = fprA; + fprA = fprC; + fprC = temp; } // move frA to frD (if different register) - if( fprRegisterD != fprRegisterA ) - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterA); // always copy ps0 and ps1 + if( frD != frA ) + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprA); // multiply bottom double of frD with bottom double of frC - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM, fprRegisterD, fprRegisterC); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprD, fprC); // sub frB - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SUB_BOTTOM, fprRegisterD, fprRegisterB); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_SUB, fprD, fprB); // negate result - PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext, NULL,PPCREC_IML_OP_FPR_NEGATE_BOTTOM, fprRegisterD); + ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_NEGATE, fprD); return true; } +#define PSE_CopyResultToPs1() if( ppcImlGenContext->PSE ) \ + { \ + DefinePS1(fprDPS1, frD); \ + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDPS1, fprD); \ + } + bool PPCRecompilerImlGen_FMULS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { sint32 frD, frA, frB_unused, frC; @@ -678,24 +402,18 @@ bool PPCRecompilerImlGen_FMULS(ppcImlGenContext_t* ppcImlGenContext, uint32 opco frA = frC; frC = temp; } - // load registers - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + DefinePS0(fprA, frA); + DefinePS0(fprC, frC); + DefinePS0(fprD, frD); // move frA to frD (if different register) - if( fprRegisterD != fprRegisterA ) - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, fprRegisterD, fprRegisterA); // always copy ps0 and ps1 - + if( frD != frA ) + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprA); // multiply bottom double of frD with bottom double of frB - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM, fprRegisterD, fprRegisterC); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprD, fprC); // adjust accuracy - PPRecompilerImmGen_optionalRoundBottomFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprD); // if paired single mode, copy frD ps0 to ps1 - if( ppcImlGenContext->PSE ) - { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterD); - } - + PSE_CopyResultToPs1(); return true; } @@ -704,44 +422,31 @@ bool PPCRecompilerImlGen_FDIVS(ppcImlGenContext_t* ppcImlGenContext, uint32 opco sint32 frD, frA, frB, frC_unused; PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC_unused); PPC_ASSERT(frB==0); - /*hCPU->fpr[frD].fpr = (float)(hCPU->fpr[frA].fpr / hCPU->fpr[frB].fpr); - if( hCPU->PSE ) - hCPU->fpr[frD].fp1 = hCPU->fpr[frD].fp0;*/ - // load registers - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - + DefinePS0(fprA, frA); + DefinePS0(fprB, frB); + DefinePS0(fprD, frD); if( frB == frD && frA != frB ) { - IMLReg fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); + DefineTempFPR(fprTemp, 0); // move frA to temporary register - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, fprRegisterTemp, fprRegisterA); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTemp, fprA); // divide bottom double of temporary register by bottom double of frB - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_DIVIDE_BOTTOM, fprRegisterTemp, fprRegisterB); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_DIVIDE, fprTemp, fprB); // move result to frD - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterTemp); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprTemp); // adjust accuracy - PPRecompilerImmGen_optionalRoundBottomFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); - // if paired single mode, copy frD ps0 to ps1 - if( ppcImlGenContext->PSE ) - { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterD); - } + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprD); + PSE_CopyResultToPs1(); return true; } // move frA to frD (if different register) - if( fprRegisterD != fprRegisterA ) - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, fprRegisterD, fprRegisterA); // always copy ps0 and ps1 + if( frD != frA ) + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprA); // subtract bottom double of frB from bottom double of frD - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_DIVIDE_BOTTOM, fprRegisterD, fprRegisterB); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_DIVIDE, fprD, fprB); // adjust accuracy - PPRecompilerImmGen_optionalRoundBottomFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); - // if paired single mode, copy frD ps0 to ps1 - if( ppcImlGenContext->PSE ) - { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterD); - } + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprD); + PSE_CopyResultToPs1(); return true; } @@ -757,22 +462,17 @@ bool PPCRecompilerImlGen_FADDS(ppcImlGenContext_t* ppcImlGenContext, uint32 opco frA = frB; frB = temp; } - // load registers - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + DefinePS0(fprA, frA); + DefinePS0(fprB, frB); + DefinePS0(fprD, frD); // move frA to frD (if different register) - if( fprRegisterD != fprRegisterA ) - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_ASSIGN, fprRegisterD, fprRegisterA); // always copy ps0 and ps1 + if( frD != frA ) + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprA); // add bottom double of frD and bottom double of frB - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_BOTTOM, fprRegisterD, fprRegisterB); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprD, fprB); // adjust accuracy - PPRecompilerImmGen_optionalRoundBottomFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); - // if paired single mode, copy frD ps0 to ps1 - if( ppcImlGenContext->PSE ) - { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterD); - } + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprD); + PSE_CopyResultToPs1(); return true; } @@ -781,20 +481,12 @@ bool PPCRecompilerImlGen_FSUBS(ppcImlGenContext_t* ppcImlGenContext, uint32 opco int frD, frA, frB, frC; PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); PPC_ASSERT(frB==0); - - // load registers - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - // subtract bottom - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SUB_BOTTOM, fprRegisterD, fprRegisterA, fprRegisterB); - // adjust accuracy - PPRecompilerImmGen_optionalRoundBottomFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); - // if paired single mode, copy frD ps0 to ps1 - if( ppcImlGenContext->PSE ) - { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterD); - } + DefinePS0(fprA, frA); + DefinePS0(fprB, frB); + DefinePS0(fprD, frD); + ppcImlGenContext->emitInst().make_fpr_r_r_r(PPCREC_IML_OP_FPR_SUB, fprD, fprA, fprB); + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprD); + PSE_CopyResultToPs1(); return true; } @@ -802,34 +494,26 @@ bool PPCRecompilerImlGen_FMADDS(ppcImlGenContext_t* ppcImlGenContext, uint32 opc { sint32 frD, frA, frB, frC; PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); - //FPRD(RD) = FPRD(RA) * FPRD(RC) + FPRD(RB); - //hCPU->fpr[frD].fpr = hCPU->fpr[frA].fpr * hCPU->fpr[frC].fpr + hCPU->fpr[frB].fpr; - //if( hCPU->PSE ) - // hCPU->fpr[frD].fp1 = hCPU->fpr[frD].fp0; - // load registers - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - IMLReg fprRegisterTemp; + DefinePS0(fprA, frA); + DefinePS0(fprB, frB); + DefinePS0(fprC, frC); + DefinePS0(fprD, frD); // if none of the operand registers overlap with the result register then we can avoid the usage of a temporary register - if( fprRegisterD != fprRegisterA && fprRegisterD != fprRegisterB && fprRegisterD != fprRegisterC ) - fprRegisterTemp = fprRegisterD; + IMLReg fprRegisterTemp; + if( frD != frA && frD != frB && frD != frC ) + fprRegisterTemp = fprD; else - fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM, fprRegisterTemp, fprRegisterA, fprRegisterC); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_BOTTOM, fprRegisterTemp, fprRegisterB); + fprRegisterTemp = _GetFPRTemp(ppcImlGenContext, 0); + ppcImlGenContext->emitInst().make_fpr_r_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprRegisterTemp, fprA, fprC); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprRegisterTemp, fprB); // adjust accuracy - PPRecompilerImmGen_optionalRoundBottomFPRToSinglePrecision(ppcImlGenContext, fprRegisterTemp); + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprRegisterTemp); // set result - if( ppcImlGenContext->PSE ) - { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterTemp); - } - else if( fprRegisterD != fprRegisterTemp ) + if( fprD != fprRegisterTemp ) { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterTemp); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprRegisterTemp); } + PSE_CopyResultToPs1(); return true; } @@ -837,33 +521,27 @@ bool PPCRecompilerImlGen_FMSUBS(ppcImlGenContext_t* ppcImlGenContext, uint32 opc { sint32 frD, frA, frB, frC; PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); - //hCPU->fpr[frD].fp0 = (float)(hCPU->fpr[frA].fp0 * hCPU->fpr[frC].fp0 - hCPU->fpr[frB].fp0); - //if( hCPU->PSE ) - // hCPU->fpr[frD].fp1 = hCPU->fpr[frD].fp0; - // load registers - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + DefinePS0(fprA, frA); + DefinePS0(fprB, frB); + DefinePS0(fprC, frC); + DefinePS0(fprD, frD); + IMLReg fprRegisterTemp; // if none of the operand registers overlap with the result register then we can avoid the usage of a temporary register - if( fprRegisterD != fprRegisterA && fprRegisterD != fprRegisterB && fprRegisterD != fprRegisterC ) - fprRegisterTemp = fprRegisterD; + if( frD != frA && frD != frB && frD != frC ) + fprRegisterTemp = fprD; else - fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM, fprRegisterTemp, fprRegisterA, fprRegisterC); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SUB_BOTTOM, fprRegisterTemp, fprRegisterB); + fprRegisterTemp = _GetFPRTemp(ppcImlGenContext, 0); + ppcImlGenContext->emitInst().make_fpr_r_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprRegisterTemp, fprA, fprC); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_SUB, fprRegisterTemp, fprB); // adjust accuracy - PPRecompilerImmGen_optionalRoundBottomFPRToSinglePrecision(ppcImlGenContext, fprRegisterTemp); + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprRegisterTemp); // set result - if( ppcImlGenContext->PSE ) - { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterTemp); - } - else if( fprRegisterD != fprRegisterTemp ) + if( fprD != fprRegisterTemp ) { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterTemp); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprRegisterTemp); } + PSE_CopyResultToPs1(); return true; } @@ -871,70 +549,32 @@ bool PPCRecompilerImlGen_FNMSUBS(ppcImlGenContext_t* ppcImlGenContext, uint32 op { sint32 frD, frA, frB, frC; PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); - - //[FP1(RD) = ]FP0(RD) = -(FP0(RA) * FP0(RC) - FP0(RB)); - //hCPU->fpr[frD].fp0 = (float)-(hCPU->fpr[frA].fp0 * hCPU->fpr[frC].fp0 - hCPU->fpr[frB].fp0); - //if( PPC_PSE ) - // hCPU->fpr[frD].fp1 = hCPU->fpr[frD].fp0; - - // load registers - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + DefinePS0(fprA, frA); + DefinePS0(fprB, frB); + DefinePS0(fprC, frC); + DefinePS0(fprD, frD); IMLReg fprRegisterTemp; // if none of the operand registers overlap with the result register then we can avoid the usage of a temporary register - if( fprRegisterD != fprRegisterA && fprRegisterD != fprRegisterB && fprRegisterD != fprRegisterC ) - fprRegisterTemp = fprRegisterD; + if( frD != frA && frD != frB && frD != frC ) + fprRegisterTemp = fprD; else - fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_BOTTOM, fprRegisterTemp, fprRegisterA, fprRegisterC); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SUB_BOTTOM, fprRegisterTemp, fprRegisterB); - PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext, NULL,PPCREC_IML_OP_FPR_NEGATE_BOTTOM, fprRegisterTemp); + fprRegisterTemp = _GetFPRTemp(ppcImlGenContext, 0); + ppcImlGenContext->emitInst().make_fpr_r_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprRegisterTemp, fprA, fprC); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_SUB, fprRegisterTemp, fprB); + ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_NEGATE, fprRegisterTemp); // adjust accuracy - PPRecompilerImmGen_optionalRoundBottomFPRToSinglePrecision(ppcImlGenContext, fprRegisterTemp); + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprRegisterTemp); // set result - if( ppcImlGenContext->PSE ) - { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterTemp); - } - else if( fprRegisterD != fprRegisterTemp ) - { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterTemp); - } + if( fprD != fprRegisterTemp ) + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprRegisterTemp); + PSE_CopyResultToPs1(); return true; } bool PPCRecompilerImlGen_FCMPO(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { - printf("FCMPO: Not implemented\n"); + // Not implemented return false; - - //sint32 crfD, frA, frB; - //PPC_OPC_TEMPL_X(opcode, crfD, frA, frB); - //crfD >>= 2; - //IMLReg regFprA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frA); - //IMLReg regFprB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frB); - - //IMLReg crBitRegLT = _GetCRReg(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_LT); - //IMLReg crBitRegGT = _GetCRReg(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_GT); - //IMLReg crBitRegEQ = _GetCRReg(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_EQ); - //IMLReg crBitRegSO = _GetCRReg(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_SO); - - //ppcImlGenContext->emitInst().make_fpr_compare(regFprA, regFprB, crBitRegLT, IMLCondition::UNORDERED_LT); - //ppcImlGenContext->emitInst().make_fpr_compare(regFprA, regFprB, crBitRegGT, IMLCondition::UNORDERED_GT); - //ppcImlGenContext->emitInst().make_fpr_compare(regFprA, regFprB, crBitRegEQ, IMLCondition::UNORDERED_EQ); - //ppcImlGenContext->emitInst().make_fpr_compare(regFprA, regFprB, crBitRegSO, IMLCondition::UNORDERED_U); - - // todo - set fpscr - - //sint32 crfD, frA, frB; - //PPC_OPC_TEMPL_X(opcode, crfD, frA, frB); - //crfD >>= 2; - //uint32 fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - //uint32 fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - //PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_FCMPO_BOTTOM, fprRegisterA, fprRegisterB, crfD); - return true; } bool PPCRecompilerImlGen_FCMPU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) @@ -942,18 +582,18 @@ bool PPCRecompilerImlGen_FCMPU(ppcImlGenContext_t* ppcImlGenContext, uint32 opco sint32 crfD, frA, frB; PPC_OPC_TEMPL_X(opcode, crfD, frA, frB); crfD >>= 2; - IMLReg regFprA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frA); - IMLReg regFprB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frB); + DefinePS0(fprA, frA); + DefinePS0(fprB, frB); IMLReg crBitRegLT = _GetRegCR(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_LT); IMLReg crBitRegGT = _GetRegCR(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_GT); IMLReg crBitRegEQ = _GetRegCR(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_EQ); IMLReg crBitRegSO = _GetRegCR(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_SO); - ppcImlGenContext->emitInst().make_fpr_compare(regFprA, regFprB, crBitRegLT, IMLCondition::UNORDERED_LT); - ppcImlGenContext->emitInst().make_fpr_compare(regFprA, regFprB, crBitRegGT, IMLCondition::UNORDERED_GT); - ppcImlGenContext->emitInst().make_fpr_compare(regFprA, regFprB, crBitRegEQ, IMLCondition::UNORDERED_EQ); - ppcImlGenContext->emitInst().make_fpr_compare(regFprA, regFprB, crBitRegSO, IMLCondition::UNORDERED_U); + ppcImlGenContext->emitInst().make_fpr_compare(fprA, fprB, crBitRegLT, IMLCondition::UNORDERED_LT); + ppcImlGenContext->emitInst().make_fpr_compare(fprA, fprB, crBitRegGT, IMLCondition::UNORDERED_GT); + ppcImlGenContext->emitInst().make_fpr_compare(fprA, fprB, crBitRegEQ, IMLCondition::UNORDERED_EQ); + ppcImlGenContext->emitInst().make_fpr_compare(fprA, fprB, crBitRegSO, IMLCondition::UNORDERED_U); // todo: set fpscr @@ -964,9 +604,9 @@ bool PPCRecompilerImlGen_FMR(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode { sint32 frD, rA, frB; PPC_OPC_TEMPL_X(opcode, frD, rA, frB); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterB); + DefinePS0(fprB, frB); + DefinePS0(fprD, frD); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprB); return true; } @@ -975,14 +615,11 @@ bool PPCRecompilerImlGen_FABS(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod sint32 frD, frA, frB; PPC_OPC_TEMPL_X(opcode, frD, frA, frB); PPC_ASSERT(frA==0); - // load registers - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - // move frB to frD (if different register) - if( fprRegisterD != fprRegisterB ) - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterB); - // abs frD - PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext, NULL,PPCREC_IML_OP_FPR_ABS_BOTTOM, fprRegisterD); + DefinePS0(fprB, frB); + DefinePS0(fprD, frD); + if( frD != frB ) + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprB); + ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_ABS, fprD); return true; } @@ -991,14 +628,11 @@ bool PPCRecompilerImlGen_FNABS(ppcImlGenContext_t* ppcImlGenContext, uint32 opco sint32 frD, frA, frB; PPC_OPC_TEMPL_X(opcode, frD, frA, frB); PPC_ASSERT(frA==0); - // load registers - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - // move frB to frD (if different register) - if( fprRegisterD != fprRegisterB ) - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterB); - // abs frD - PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext, NULL,PPCREC_IML_OP_FPR_NEGATIVE_ABS_BOTTOM, fprRegisterD); + DefinePS0(fprB, frB); + DefinePS0(fprD, frD); + if( frD != frB ) + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprB); + ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_NEGATIVE_ABS, fprD); return true; } @@ -1007,15 +641,12 @@ bool PPCRecompilerImlGen_FRES(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod sint32 frD, frA, frB; PPC_OPC_TEMPL_X(opcode, frD, frA, frB); PPC_ASSERT(frA==0); - // load registers - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - ppcImlGenContext->emitInst().make_call_imm((uintptr_t)fres_espresso, fprRegisterB, IMLREG_INVALID, IMLREG_INVALID, fprRegisterD); + DefinePS0(fprB, frB); + DefinePS0(fprD, frD); + ppcImlGenContext->emitInst().make_call_imm((uintptr_t)fres_espresso, fprB, IMLREG_INVALID, IMLREG_INVALID, fprD); // adjust accuracy - PPRecompilerImmGen_optionalRoundBottomFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); - // copy result to top - if( ppcImlGenContext->PSE ) - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterD); + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprD); + PSE_CopyResultToPs1(); return true; } @@ -1024,15 +655,12 @@ bool PPCRecompilerImlGen_FRSP(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod sint32 frD, frA, frB; PPC_OPC_TEMPL_X(opcode, frD, frA, frB); PPC_ASSERT(frA==0); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - if( fprRegisterD != fprRegisterB ) - { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterB); - } - PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext, NULL,PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_BOTTOM, fprRegisterD); - if( ppcImlGenContext->PSE ) - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterD); + DefinePS0(fprB, frB); + DefinePS0(fprD, frD); + if( fprD != fprB ) + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprB); + ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_BOTTOM, fprD); + PSE_CopyResultToPs1(); return true; } @@ -1042,17 +670,12 @@ bool PPCRecompilerImlGen_FNEG(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod PPC_OPC_TEMPL_X(opcode, frD, frA, frB); PPC_ASSERT(frA==0); if( opcode&PPC_OPC_RC ) - { return false; - } - // load registers - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - // move frB to frD (if different register) - if( fprRegisterD != fprRegisterB ) - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterB); - // negate frD - PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext, NULL,PPCREC_IML_OP_FPR_NEGATE_BOTTOM, fprRegisterD); + DefinePS0(fprB, frB); + DefinePS0(fprD, frD); + if( frD != frB ) + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprD, fprB); + ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_NEGATE, fprD); return true; } @@ -1064,11 +687,11 @@ bool PPCRecompilerImlGen_FSEL(ppcImlGenContext_t* ppcImlGenContext, uint32 opcod { return false; } - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SELECT_BOTTOM, fprRegisterD, fprRegisterA, fprRegisterB, fprRegisterC); + DefinePS0(fprA, frA); + DefinePS0(fprB, frB); + DefinePS0(fprC, frC); + DefinePS0(fprD, frD); + ppcImlGenContext->emitInst().make_fpr_r_r_r_r(PPCREC_IML_OP_FPR_SELECT, fprD, fprA, fprB, fprC); return true; } @@ -1076,12 +699,11 @@ bool PPCRecompilerImlGen_FRSQRTE(ppcImlGenContext_t* ppcImlGenContext, uint32 op { sint32 frD, frA, frB, frC; PPC_OPC_TEMPL_A(opcode, frD, frA, frB, frC); - // hCPU->fpr[frD].fpr = 1.0 / sqrt(hCPU->fpr[frB].fpr); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - ppcImlGenContext->emitInst().make_call_imm((uintptr_t)frsqrte_espresso, fprRegisterB, IMLREG_INVALID, IMLREG_INVALID, fprRegisterD); + DefinePS0(fprB, frB); + DefinePS0(fprD, frD); + ppcImlGenContext->emitInst().make_call_imm((uintptr_t)frsqrte_espresso, fprB, IMLREG_INVALID, IMLREG_INVALID, fprD); // adjust accuracy - PPRecompilerImmGen_optionalRoundBottomFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprD); return true; } @@ -1089,237 +711,332 @@ bool PPCRecompilerImlGen_FCTIWZ(ppcImlGenContext_t* ppcImlGenContext, uint32 opc { sint32 frD, frA, frB; PPC_OPC_TEMPL_X(opcode, frD, frA, frB); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_BOTTOM_FCTIWZ, fprRegisterD, fprRegisterB); + DefinePS0(fprB, frB); + DefinePS0(fprD, frD); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_FCTIWZ, fprD, fprB); return true; } -bool PPCRecompilerImlGen_PSQ_L(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +bool PPCRecompiler_isUGQRValueKnown(ppcImlGenContext_t* ppcImlGenContext, sint32 gqrIndex, uint32& gqrValue); + +void PPCRecompilerImlGen_ClampInteger(ppcImlGenContext_t* ppcImlGenContext, IMLReg reg, sint32 clampMin, sint32 clampMax) +{ + IMLReg regTmpCondBool = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 1); + // min(reg, clampMax) + ppcImlGenContext->emitInst().make_compare_s32(reg, clampMax, regTmpCondBool, IMLCondition::SIGNED_GT); + ppcImlGenContext->emitInst().make_conditional_jump(regTmpCondBool, false); // condition needs to be inverted because we skip if the condition is true + PPCIMLGen_CreateSegmentBranchedPath(*ppcImlGenContext, *ppcImlGenContext->currentBasicBlock, + [&](ppcImlGenContext_t& genCtx) + { + /* branch not taken */ + genCtx.emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, reg, clampMax); + } + ); + // max(reg, clampMin) + ppcImlGenContext->emitInst().make_compare_s32(reg, clampMin, regTmpCondBool, IMLCondition::SIGNED_LT); + ppcImlGenContext->emitInst().make_conditional_jump(regTmpCondBool, false); + PPCIMLGen_CreateSegmentBranchedPath(*ppcImlGenContext, *ppcImlGenContext->currentBasicBlock, + [&](ppcImlGenContext_t& genCtx) + { + /* branch not taken */ + genCtx.emitInst().make_r_s32(PPCREC_IML_OP_ASSIGN, reg, clampMin); + } + ); +} + +void PPCRecompilerImlGen_EmitPSQLoadCase(ppcImlGenContext_t* ppcImlGenContext, Espresso::PSQ_LOAD_TYPE loadType, bool readPS1, IMLReg gprA, sint32 imm, IMLReg fprDPS0, IMLReg fprDPS1) +{ + if (loadType == Espresso::PSQ_LOAD_TYPE::TYPE_F32) + { + ppcImlGenContext->emitInst().make_fpr_r_memory(fprDPS0, gprA, imm, PPCREC_FPR_LD_MODE_SINGLE, true); + if(readPS1) + { + ppcImlGenContext->emitInst().make_fpr_r_memory(fprDPS1, gprA, imm + 4, PPCREC_FPR_LD_MODE_SINGLE, true); + } + } + if (loadType == Espresso::PSQ_LOAD_TYPE::TYPE_U16 || loadType == Espresso::PSQ_LOAD_TYPE::TYPE_S16) + { + bool isSigned = (loadType == Espresso::PSQ_LOAD_TYPE::TYPE_S16); + IMLReg gprTmp = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 0); + ppcImlGenContext->emitInst().make_r_memory(gprTmp, gprA, imm, 16, isSigned, true); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_INT_TO_FLOAT, fprDPS0, gprTmp); + if(readPS1) + { + ppcImlGenContext->emitInst().make_r_memory(gprTmp, gprA, imm + 2, 16, isSigned, true); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_INT_TO_FLOAT, fprDPS1, gprTmp); + } + } + else if (loadType == Espresso::PSQ_LOAD_TYPE::TYPE_U8 || loadType == Espresso::PSQ_LOAD_TYPE::TYPE_S8) + { + bool isSigned = (loadType == Espresso::PSQ_LOAD_TYPE::TYPE_S8); + IMLReg gprTmp = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 0); + ppcImlGenContext->emitInst().make_r_memory(gprTmp, gprA, imm, 8, isSigned, true); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_INT_TO_FLOAT, fprDPS0, gprTmp); + if(readPS1) + { + ppcImlGenContext->emitInst().make_r_memory(gprTmp, gprA, imm + 1, 8, isSigned, true); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_INT_TO_FLOAT, fprDPS1, gprTmp); + } + } +} + +// PSQ_L and PSQ_LU +bool PPCRecompilerImlGen_PSQ_L(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool withUpdate) { int rA, frD; uint32 immUnused; PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, immUnused); - sint32 gqrIndex = ((opcode >> 12) & 7); uint32 imm = opcode & 0xFFF; if (imm & 0x800) imm |= ~0xFFF; - bool readPS1 = (opcode & 0x8000) == false; - IMLReg gqrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + SPR_UGQR0 + gqrIndex); - IMLReg gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rA); - IMLReg fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frD); - // psq load - PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext, fprRegister, gprRegister, imm, readPS1 ? PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1 : PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0, true, gqrRegister); - return true; -} + IMLReg gprA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + DefinePS0(fprDPS0, frD); + DefinePS1(fprDPS1, frD); + if (!readPS1) + { + // if PS1 is not explicitly read then set it to 1.0 + ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_LOAD_ONE, fprDPS1); + } + if (withUpdate) + { + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_ADD, gprA, gprA, (sint32)imm); + imm = 0; + } + uint32 knownGQRValue = 0; + if ( !PPCRecompiler_isUGQRValueKnown(ppcImlGenContext, gqrIndex, knownGQRValue) ) + { + // generate complex dynamic handler when we dont know the GQR value ahead of time + IMLReg gqrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + SPR_UGQR0 + gqrIndex); + IMLReg loadTypeReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 0); + // extract the load type from the GQR register + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_AND, loadTypeReg, gqrRegister, 0x7); + IMLSegment* caseSegment[6]; + sint32 compareValues[6] = {0, 4, 5, 6, 7}; + PPCIMLGen_CreateSegmentBranchedPathMultiple(*ppcImlGenContext, *ppcImlGenContext->currentBasicBlock, caseSegment, loadTypeReg, compareValues, 5, 0); + for (sint32 i=0; i<5; i++) + { + IMLRedirectInstOutput outputToCase(ppcImlGenContext, caseSegment[i]); // while this is in scope, instructions go to caseSegment[i] + PPCRecompilerImlGen_EmitPSQLoadCase(ppcImlGenContext, static_cast<Espresso::PSQ_LOAD_TYPE>(compareValues[i]), readPS1, gprA, imm, fprDPS0, fprDPS1); + // create the case jump instructions here because we need to add it last + caseSegment[i]->AppendInstruction()->make_jump(); + } + return true; + } -bool PPCRecompilerImlGen_PSQ_LU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - int rA, frD; - uint32 immUnused; - PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, immUnused); - if (rA == 0) + Espresso::PSQ_LOAD_TYPE type = static_cast<Espresso::PSQ_LOAD_TYPE>((knownGQRValue >> 0) & 0x7); + sint32 scale = (knownGQRValue >> 8) & 0x3F; + cemu_assert_debug(scale == 0); // known GQR values always use a scale of 0 (1.0f) + if (scale != 0) return false; - sint32 gqrIndex = ((opcode >> 12) & 7); - uint32 imm = opcode & 0xFFF; - if (imm & 0x800) - imm |= ~0xFFF; - - bool readPS1 = (opcode & 0x8000) == false; - - IMLReg gqrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + SPR_UGQR0 + gqrIndex); - IMLReg gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rA); - - ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_ADD, gprRegister, gprRegister, (sint32)imm); - - IMLReg fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frD); - // paired load - PPCRecompilerImlGen_generateNewInstruction_fpr_r_memory(ppcImlGenContext, fprRegister, gprRegister, 0, readPS1 ? PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0_PS1 : PPCREC_FPR_LD_MODE_PSQ_GENERIC_PS0, true, gqrRegister); - return true; -} - -bool PPCRecompilerImlGen_PSQ_ST(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - int rA, frD; - uint32 immUnused; - PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, immUnused); - uint32 imm = opcode & 0xFFF; - if (imm & 0x800) - imm |= ~0xFFF; - sint32 gqrIndex = ((opcode >> 12) & 7); - - bool storePS1 = (opcode & 0x8000) == false; - - IMLReg gqrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + SPR_UGQR0 + gqrIndex); - IMLReg gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rA); - IMLReg fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frD); - // paired store - PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r(ppcImlGenContext, fprRegister, gprRegister, imm, storePS1 ? PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1 : PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0, true, gqrRegister); - return true; -} - -bool PPCRecompilerImlGen_PSQ_STU(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - int rA, frD; - uint32 immUnused; - PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, immUnused); - if (rA == 0) + if (type == Espresso::PSQ_LOAD_TYPE::TYPE_UNUSED1 || + type == Espresso::PSQ_LOAD_TYPE::TYPE_UNUSED2 || + type == Espresso::PSQ_LOAD_TYPE::TYPE_UNUSED3) + { return false; + } + PPCRecompilerImlGen_EmitPSQLoadCase(ppcImlGenContext, type, readPS1, gprA, imm, fprDPS0, fprDPS1); + return true; +} + +void PPCRecompilerImlGen_EmitPSQStoreCase(ppcImlGenContext_t* ppcImlGenContext, Espresso::PSQ_LOAD_TYPE storeType, bool storePS1, IMLReg gprA, sint32 imm, IMLReg fprDPS0, IMLReg fprDPS1) +{ + cemu_assert_debug(!storePS1 || fprDPS1.IsValid()); + if (storeType == Espresso::PSQ_LOAD_TYPE::TYPE_F32) + { + ppcImlGenContext->emitInst().make_fpr_memory_r(fprDPS0, gprA, imm, PPCREC_FPR_ST_MODE_SINGLE, true); + if(storePS1) + { + ppcImlGenContext->emitInst().make_fpr_memory_r(fprDPS1, gprA, imm + 4, PPCREC_FPR_ST_MODE_SINGLE, true); + } + } + else if (storeType == Espresso::PSQ_LOAD_TYPE::TYPE_U16 || storeType == Espresso::PSQ_LOAD_TYPE::TYPE_S16) + { + bool isSigned = (storeType == Espresso::PSQ_LOAD_TYPE::TYPE_S16); + IMLReg gprTmp = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_FLOAT_TO_INT, gprTmp, fprDPS0); + // todo - scaling + if (isSigned) + PPCRecompilerImlGen_ClampInteger(ppcImlGenContext, gprTmp, -32768, 32767); + else + PPCRecompilerImlGen_ClampInteger(ppcImlGenContext, gprTmp, 0, 65535); + ppcImlGenContext->emitInst().make_memory_r(gprTmp, gprA, imm, 16, true); + if(storePS1) + { + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_FLOAT_TO_INT, gprTmp, fprDPS1); + // todo - scaling + if (isSigned) + PPCRecompilerImlGen_ClampInteger(ppcImlGenContext, gprTmp, -32768, 32767); + else + PPCRecompilerImlGen_ClampInteger(ppcImlGenContext, gprTmp, 0, 65535); + ppcImlGenContext->emitInst().make_memory_r(gprTmp, gprA, imm + 2, 16, true); + } + } + else if (storeType == Espresso::PSQ_LOAD_TYPE::TYPE_U8 || storeType == Espresso::PSQ_LOAD_TYPE::TYPE_S8) + { + bool isSigned = (storeType == Espresso::PSQ_LOAD_TYPE::TYPE_S8); + IMLReg gprTmp = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_FLOAT_TO_INT, gprTmp, fprDPS0); + if (isSigned) + PPCRecompilerImlGen_ClampInteger(ppcImlGenContext, gprTmp, -128, 127); + else + PPCRecompilerImlGen_ClampInteger(ppcImlGenContext, gprTmp, 0, 255); + ppcImlGenContext->emitInst().make_memory_r(gprTmp, gprA, imm, 8, true); + if(storePS1) + { + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_FLOAT_TO_INT, gprTmp, fprDPS1); + // todo - scaling + if (isSigned) + PPCRecompilerImlGen_ClampInteger(ppcImlGenContext, gprTmp, -128, 127); + else + PPCRecompilerImlGen_ClampInteger(ppcImlGenContext, gprTmp, 0, 255); + ppcImlGenContext->emitInst().make_memory_r(gprTmp, gprA, imm + 1, 8, true); + } + } +} + +// PSQ_ST and PSQ_STU +bool PPCRecompilerImlGen_PSQ_ST(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool withUpdate) +{ + int rA, frD; + uint32 immUnused; + PPC_OPC_TEMPL_D_SImm(opcode, frD, rA, immUnused); uint32 imm = opcode & 0xFFF; if (imm & 0x800) imm |= ~0xFFF; sint32 gqrIndex = ((opcode >> 12) & 7); - bool storePS1 = (opcode & 0x8000) == false; - IMLReg gqrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + SPR_UGQR0 + gqrIndex); - IMLReg gprRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0 + rA); - ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_ADD, gprRegister, gprRegister, (sint32)imm); + IMLReg gprA = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_R0+rA); + DefinePS0(fprDPS0, frD); + IMLReg fprDPS1 = storePS1 ? _GetFPRRegPS1(ppcImlGenContext, frD) : IMLREG_INVALID; - IMLReg fprRegister = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frD); - // paired store - PPCRecompilerImlGen_generateNewInstruction_fpr_memory_r(ppcImlGenContext, fprRegister, gprRegister, 0, storePS1 ? PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0_PS1 : PPCREC_FPR_ST_MODE_PSQ_GENERIC_PS0, true, gqrRegister); + if (withUpdate) + { + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_ADD, gprA, gprA, (sint32)imm); + imm = 0; + } + + uint32 gqrValue = 0; + if ( !PPCRecompiler_isUGQRValueKnown(ppcImlGenContext, gqrIndex, gqrValue) ) + { + // generate complex dynamic handler when we dont know the GQR value ahead of time + IMLReg gqrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + SPR_UGQR0 + gqrIndex); + IMLReg loadTypeReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 0); + // extract the load type from the GQR register + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_RIGHT_SHIFT_U, loadTypeReg, gqrRegister, 16); + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_AND, loadTypeReg, loadTypeReg, 0x7); + + IMLSegment* caseSegment[5]; + sint32 compareValues[5] = {0, 4, 5, 6, 7}; + PPCIMLGen_CreateSegmentBranchedPathMultiple(*ppcImlGenContext, *ppcImlGenContext->currentBasicBlock, caseSegment, loadTypeReg, compareValues, 5, 0); + for (sint32 i=0; i<5; i++) + { + IMLRedirectInstOutput outputToCase(ppcImlGenContext, caseSegment[i]); // while this is in scope, instructions go to caseSegment[i] + PPCRecompilerImlGen_EmitPSQStoreCase(ppcImlGenContext, static_cast<Espresso::PSQ_LOAD_TYPE>(compareValues[i]), storePS1, gprA, imm, fprDPS0, fprDPS1); + ppcImlGenContext->emitInst().make_jump(); // finalize case + } + return true; + } + + Espresso::PSQ_LOAD_TYPE type = static_cast<Espresso::PSQ_LOAD_TYPE>((gqrValue >> 16) & 0x7); + sint32 scale = (gqrValue >> 24) & 0x3F; + cemu_assert_debug(scale == 0); // known GQR values always use a scale of 0 (1.0f) + + if (type == Espresso::PSQ_LOAD_TYPE::TYPE_UNUSED1 || + type == Espresso::PSQ_LOAD_TYPE::TYPE_UNUSED2 || + type == Espresso::PSQ_LOAD_TYPE::TYPE_UNUSED3) + { + return false; + } + + PPCRecompilerImlGen_EmitPSQStoreCase(ppcImlGenContext, type, storePS1, gprA, imm, fprDPS0, fprDPS1); return true; } -bool PPCRecompilerImlGen_PS_MULS0(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +// PS_MULS0 and PS_MULS1 +bool PPCRecompilerImlGen_PS_MULSX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool isVariant1) { sint32 frD, frA, frC; frC = (opcode>>6)&0x1F; frA = (opcode>>16)&0x1F; frD = (opcode>>21)&0x1F; - // load registers - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - // we need a temporary register to store frC.fp0 in low and high half - IMLReg fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, fprRegisterTemp, fprRegisterC); - // if frD == frA we can multiply frD immediately and safe a copy instruction - if( frD == frA ) - { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterD, fprRegisterTemp); - } - else - { - // we multiply temporary by frA - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterTemp, fprRegisterA); - // copy result to frD - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterD, fprRegisterTemp); - } - // adjust accuracy - PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + + DefinePS0(fprAps0, frA); + DefinePS1(fprAps1, frA); + DefinePSX(fprC, frC, isVariant1); + DefinePS0(fprDps0, frD); + DefinePS1(fprDps1, frD); + + DefineTempFPR(fprTmp0, 0); + DefineTempFPR(fprTmp1, 1); + + // todo - optimize cases where a temporary is not necessary + // todo - round fprC to 25bit accuracy + + // copy ps0 and ps1 to temporary + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTmp0, fprAps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTmp1, fprAps1); + + // multiply + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTmp0, fprC); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTmp1, fprC); + + // copy back to result + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps0, fprTmp0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps1, fprTmp1); + + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps0); + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps1); + return true; } -bool PPCRecompilerImlGen_PS_MULS1(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 frD, frA, frC; - frC = (opcode>>6)&0x1F; - frA = (opcode>>16)&0x1F; - frD = (opcode>>21)&0x1F; - // load registers - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - // we need a temporary register to store frC.fp0 in low and high half - IMLReg fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM_AND_TOP, fprRegisterTemp, fprRegisterC); - // if frD == frA we can multiply frD immediately and safe a copy instruction - if( frD == frA ) - { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterD, fprRegisterTemp); - } - else - { - // we multiply temporary by frA - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterTemp, fprRegisterA); - // copy result to frD - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterD, fprRegisterTemp); - } - // adjust accuracy - PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); - return true; -} - -bool PPCRecompilerImlGen_PS_MADDS0(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +// PS_MADDS0 and PS_MADDS1 +bool PPCRecompilerImlGen_PS_MADDSX(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool isVariant1) { sint32 frD, frA, frB, frC; frC = (opcode>>6)&0x1F; frB = (opcode>>11)&0x1F; frA = (opcode>>16)&0x1F; frD = (opcode>>21)&0x1F; - //float s0 = (float)(hCPU->fpr[frA].fp0 * hCPU->fpr[frC].fp0 + hCPU->fpr[frB].fp0); - //float s1 = (float)(hCPU->fpr[frA].fp1 * hCPU->fpr[frC].fp0 + hCPU->fpr[frB].fp1); - // load registers - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - // we need a temporary register to store frC.fp0 in low and high half - IMLReg fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, fprRegisterTemp, fprRegisterC); - // if frD == frA and frD != frB we can multiply frD immediately and safe a copy instruction - if( frD == frA && frD != frB ) - { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterD, fprRegisterTemp); - // add frB - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_PAIR, fprRegisterD, fprRegisterB); - } - else - { - // we multiply temporary by frA - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterTemp, fprRegisterA); - // add frB - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_PAIR, fprRegisterTemp, fprRegisterB); - // copy result to frD - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterD, fprRegisterTemp); - } - // adjust accuracy - PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); - return true; -} -bool PPCRecompilerImlGen_PS_MADDS1(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 frD, frA, frB, frC; - frC = (opcode>>6)&0x1F; - frB = (opcode>>11)&0x1F; - frA = (opcode>>16)&0x1F; - frD = (opcode>>21)&0x1F; - //float s0 = (float)(hCPU->fpr[frA].fp0 * hCPU->fpr[frC].fp1 + hCPU->fpr[frB].fp0); - //float s1 = (float)(hCPU->fpr[frA].fp1 * hCPU->fpr[frC].fp1 + hCPU->fpr[frB].fp1); - // load registers - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - // we need a temporary register to store frC.fp1 in bottom and top half - IMLReg fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM_AND_TOP, fprRegisterTemp, fprRegisterC); - // if frD == frA and frD != frB we can multiply frD immediately and safe a copy instruction - if( frD == frA && frD != frB ) - { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterD, fprRegisterTemp); - // add frB - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_PAIR, fprRegisterD, fprRegisterB); - } - else - { - // we multiply temporary by frA - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterTemp, fprRegisterA); - // add frB - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_PAIR, fprRegisterTemp, fprRegisterB); - // copy result to frD - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterD, fprRegisterTemp); - } - // adjust accuracy - PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + DefinePS0(fprAps0, frA); + DefinePS1(fprAps1, frA); + DefinePS0(fprBps0, frB); + DefinePS1(fprBps1, frB); + DefinePSX(fprC, frC, isVariant1); + DefinePS0(fprDps0, frD); + DefinePS1(fprDps1, frD); + + DefineTempFPR(fprTmp0, 0); + DefineTempFPR(fprTmp1, 1); + + // todo - round C to 25bit + // todo - optimize cases where a temporary is not necessary + + // copy ps0 and ps1 to temporary + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTmp0, fprAps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTmp1, fprAps1); + + // multiply + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTmp0, fprC); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTmp1, fprC); + + // add + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprTmp0, fprBps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprTmp1, fprBps1); + + // copy back to result + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps0, fprTmp0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps1, fprTmp1); + + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps0); + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps1); return true; } @@ -1331,25 +1048,34 @@ bool PPCRecompilerImlGen_PS_ADD(ppcImlGenContext_t* ppcImlGenContext, uint32 opc frD = (opcode>>21)&0x1F; //hCPU->fpr[frD].fp0 = hCPU->fpr[frA].fp0 + hCPU->fpr[frB].fp0; //hCPU->fpr[frD].fp1 = hCPU->fpr[frA].fp1 + hCPU->fpr[frB].fp1; - // load registers - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + + DefinePS0(fprDps0, frD); + DefinePS1(fprDps1, frD); + DefinePS0(fprAps0, frA); + DefinePS1(fprAps1, frA); + DefinePS0(fprBps0, frB); + DefinePS1(fprBps1, frB); + if( frD == frA ) { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_PAIR, fprRegisterD, fprRegisterB); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprDps0, fprBps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprDps1, fprBps1); } else if( frD == frB ) { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_PAIR, fprRegisterD, fprRegisterA); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprDps0, fprAps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprDps1, fprAps1); } else { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterD, fprRegisterA); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_PAIR, fprRegisterD, fprRegisterB); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps0, fprAps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps1, fprAps1); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprDps0, fprBps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprDps1, fprBps1); } // adjust accuracy - PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps0); + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps1); return true; } @@ -1361,13 +1087,20 @@ bool PPCRecompilerImlGen_PS_SUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opc frD = (opcode>>21)&0x1F; //hCPU->fpr[frD].fp0 = hCPU->fpr[frA].fp0 - hCPU->fpr[frB].fp0; //hCPU->fpr[frD].fp1 = hCPU->fpr[frA].fp1 - hCPU->fpr[frB].fp1; - // load registers - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SUB_PAIR, fprRegisterD, fprRegisterA, fprRegisterB); + + DefinePS0(fprDps0, frD); + DefinePS1(fprDps1, frD); + DefinePS0(fprAps0, frA); + DefinePS1(fprAps1, frA); + DefinePS0(fprBps0, frB); + DefinePS1(fprBps1, frB); + + ppcImlGenContext->emitInst().make_fpr_r_r_r(PPCREC_IML_OP_FPR_SUB, fprDps0, fprAps0, fprBps0); + ppcImlGenContext->emitInst().make_fpr_r_r_r(PPCREC_IML_OP_FPR_SUB, fprDps1, fprAps1, fprBps1); + // adjust accuracy - PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps0); + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps1); return true; } @@ -1377,28 +1110,37 @@ bool PPCRecompilerImlGen_PS_MUL(ppcImlGenContext_t* ppcImlGenContext, uint32 opc frC = (opcode >> 6) & 0x1F; frA = (opcode >> 16) & 0x1F; frD = (opcode >> 21) & 0x1F; - // load registers - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frA); - IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frC); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frD); - // we need a temporary register - IMLReg fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0 + 0); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterTemp, fprRegisterC); - // todo-optimize: This instruction can be optimized so that it doesn't always use a temporary register - // if frD == frA we can multiply frD immediately and safe a copy instruction + + DefinePS0(fprDps0, frD); + DefinePS1(fprDps1, frD); + DefinePS0(fprAps0, frA); + DefinePS1(fprAps1, frA); + DefinePS0(fprCps0, frC); + DefinePS1(fprCps1, frC); + + DefineTempFPR(fprTemp0, 0); + DefineTempFPR(fprTemp1, 1); + + // todo: Optimize for when a temporary isnt necessary + // todo: Round to 25bit? + + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTemp0, fprCps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTemp1, fprCps1); if (frD == frA) { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterD, fprRegisterTemp); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprDps0, fprTemp0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprDps1, fprTemp1); } else { - // we multiply temporary by frA - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterTemp, fprRegisterA); - // copy result to frD - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterD, fprRegisterTemp); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTemp0, fprAps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTemp1, fprAps1); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps0, fprTemp0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps1, fprTemp1); } // adjust accuracy - PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps0); + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps1); return true; } @@ -1410,28 +1152,35 @@ bool PPCRecompilerImlGen_PS_DIV(ppcImlGenContext_t* ppcImlGenContext, uint32 opc frD = (opcode >> 21) & 0x1F; //hCPU->fpr[frD].fp0 = hCPU->fpr[frA].fp0 / hCPU->fpr[frB].fp0; //hCPU->fpr[frD].fp1 = hCPU->fpr[frA].fp1 / hCPU->fpr[frB].fp1; - // load registers - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frA); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frB); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frD); - // todo-optimize: This instruction can be optimized so that it doesn't always use a temporary register - // if frD == frA we can divide frD immediately and safe a copy instruction + + DefinePS0(fprDps0, frD); + DefinePS1(fprDps1, frD); + DefinePS0(fprAps0, frA); + DefinePS1(fprAps1, frA); + DefinePS0(fprBps0, frB); + DefinePS1(fprBps1, frB); + if (frD == frA) { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_DIVIDE_PAIR, fprRegisterD, fprRegisterB); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_DIVIDE, fprDps0, fprBps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_DIVIDE, fprDps1, fprBps1); } else { - // we need a temporary register - IMLReg fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0 + 0); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterTemp, fprRegisterA); + DefineTempFPR(fprTemp0, 0); + DefineTempFPR(fprTemp1, 1); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTemp0, fprAps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTemp1, fprAps1); // we divide temporary by frB - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_DIVIDE_PAIR, fprRegisterTemp, fprRegisterB); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_DIVIDE, fprTemp0, fprBps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_DIVIDE, fprTemp1, fprBps1); // copy result to frD - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterD, fprRegisterTemp); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps0, fprTemp0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps1, fprTemp1); } // adjust accuracy - PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps0); + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps1); return true; } @@ -1445,33 +1194,61 @@ bool PPCRecompilerImlGen_PS_MADD(ppcImlGenContext_t* ppcImlGenContext, uint32 op //float s0 = (float)(hCPU->fpr[frA].fp0 * hCPU->fpr[frC].fp0 + hCPU->fpr[frB].fp0); //float s1 = (float)(hCPU->fpr[frA].fp1 * hCPU->fpr[frC].fp1 + hCPU->fpr[frB].fp1); - // load registers - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - // we need a temporary register to store frC.fp0 in low and high half - IMLReg fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterTemp, fprRegisterC); - // todo-optimize: This instruction can be optimized so that it doesn't always use a temporary register - // if frD == frA and frD != frB we can multiply frD immediately and save a copy instruction - if( frD == frA && frD != frB ) + DefinePS0(fprDps0, frD); + DefinePS1(fprDps1, frD); + DefinePS0(fprAps0, frA); + DefinePS1(fprAps1, frA); + DefinePS0(fprBps0, frB); + DefinePS1(fprBps1, frB); + DefinePS0(fprCps0, frC); + DefinePS1(fprCps1, frC); + + if (frD != frA && frD != frB) { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterD, fprRegisterTemp); - // add frB - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_PAIR, fprRegisterD, fprRegisterB); + if (frD == frC) + { + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprCps0, fprAps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprCps1, fprAps1); + } + else + { + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps0, fprAps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps1, fprAps1); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprDps0, fprCps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprDps1, fprCps1); + } + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprDps0, fprBps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprDps1, fprBps1); } else { - // we multiply temporary by frA - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterTemp, fprRegisterA); - // add frB - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_PAIR, fprRegisterTemp, fprRegisterB); - // copy result to frD - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterD, fprRegisterTemp); + DefineTempFPR(fprTemp0, 0); + DefineTempFPR(fprTemp1, 1); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTemp0, fprCps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTemp1, fprCps1); + if( frD == frA && frD != frB ) + { + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprDps0, fprTemp0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprDps1, fprTemp1); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprDps0, fprBps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprDps1, fprBps1); + } + else + { + // we multiply temporary by frA + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTemp0, fprAps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTemp1, fprAps1); + // add frB + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprTemp0, fprBps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprTemp1, fprBps1); + // copy result to frD + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps0, fprTemp0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps1, fprTemp1); + } } // adjust accuracy - PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps0); + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps1); return true; } @@ -1483,81 +1260,54 @@ bool PPCRecompilerImlGen_PS_NMADD(ppcImlGenContext_t* ppcImlGenContext, uint32 o frA = (opcode>>16)&0x1F; frD = (opcode>>21)&0x1F; - // load registers - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - // we need a temporary register to store frC.fp0 in low and high half - IMLReg fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterTemp, fprRegisterC); + DefinePS0(fprDps0, frD); + DefinePS1(fprDps1, frD); + DefinePS0(fprAps0, frA); + DefinePS1(fprAps1, frA); + DefinePS0(fprBps0, frB); + DefinePS1(fprBps1, frB); + DefinePS0(fprCps0, frC); + DefinePS1(fprCps1, frC); + + DefineTempFPR(fprTemp0, 0); + DefineTempFPR(fprTemp1, 1); + + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTemp0, fprCps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTemp1, fprCps1); // todo-optimize: This instruction can be optimized so that it doesn't always use a temporary register - // if frD == frA and frD != frB we can multiply frD immediately and safe a copy instruction + // if frD == frA and frD != frB we can multiply frD immediately and save a copy instruction if( frD == frA && frD != frB ) { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterD, fprRegisterTemp); - // add frB - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_PAIR, fprRegisterD, fprRegisterB); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprDps0, fprTemp0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprDps1, fprTemp1); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprDps0, fprBps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprDps1, fprBps1); } else { // we multiply temporary by frA - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterTemp, fprRegisterA); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTemp0, fprAps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTemp1, fprAps1); // add frB - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ADD_PAIR, fprRegisterTemp, fprRegisterB); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprTemp0, fprBps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprTemp1, fprBps1); // copy result to frD - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterD, fprRegisterTemp); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps0, fprTemp0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps1, fprTemp1); } + // negate - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_NEGATE_PAIR, fprRegisterD, fprRegisterD); + ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_NEGATE, fprDps0); + ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_NEGATE, fprDps1); // adjust accuracy //PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); // Splatoon requires that we emulate flush-to-denormals for this instruction - //PPCRecompilerImlGen_generateNewInstruction_fpr_r(ppcImlGenContext, NULL,PPCREC_IML_OP_FPR_ROUND_FLDN_TO_SINGLE_PRECISION_PAIR, fprRegisterD, false); + //ppcImlGenContext->emitInst().make_fpr_r(NULL,PPCREC_IML_OP_FPR_ROUND_FLDN_TO_SINGLE_PRECISION_PAIR, fprRegisterD, false); return true; } -bool PPCRecompilerImlGen_PS_MSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) -{ - sint32 frD, frA, frB, frC; - frC = (opcode>>6)&0x1F; - frB = (opcode>>11)&0x1F; - frA = (opcode>>16)&0x1F; - frD = (opcode>>21)&0x1F; - //hCPU->fpr[frD].fp0 = (hCPU->fpr[frA].fp0 * hCPU->fpr[frC].fp0 - hCPU->fpr[frB].fp0); - //hCPU->fpr[frD].fp1 = (hCPU->fpr[frA].fp1 * hCPU->fpr[frC].fp1 - hCPU->fpr[frB].fp1); - - // load registers - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - // we need a temporary register to store frC.fp0 in low and high half - IMLReg fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterTemp, fprRegisterC); - // todo-optimize: This instruction can be optimized so that it doesn't always use a temporary register - // if frD == frA and frD != frB we can multiply frD immediately and safe a copy instruction - if( frD == frA && frD != frB ) - { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterD, fprRegisterTemp); - // sub frB - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SUB_PAIR, fprRegisterD, fprRegisterB); - } - else - { - // we multiply temporary by frA - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterTemp, fprRegisterA); - // sub frB - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SUB_PAIR, fprRegisterTemp, fprRegisterB); - // copy result to frD - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterD, fprRegisterTemp); - } - // adjust accuracy - PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); - return true; -} - -bool PPCRecompilerImlGen_PS_NMSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) +// PS_MSUB and PS_NMSUB +bool PPCRecompilerImlGen_PS_MSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode, bool withNegative) { sint32 frD, frA, frB, frC; frC = (opcode>>6)&0x1F; @@ -1565,35 +1315,64 @@ bool PPCRecompilerImlGen_PS_NMSUB(ppcImlGenContext_t* ppcImlGenContext, uint32 o frA = (opcode>>16)&0x1F; frD = (opcode>>21)&0x1F; - // load registers - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - // we need a temporary register to store frC.fp0 in low and high half - IMLReg fprRegisterTemp = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY_FPR0+0); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterTemp, fprRegisterC); - // todo-optimize: This instruction can be optimized so that it doesn't always use a temporary register - // if frD == frA and frD != frB we can multiply frD immediately and safe a copy instruction - if( frD == frA && frD != frB ) + DefinePS0(fprDps0, frD); + DefinePS1(fprDps1, frD); + DefinePS0(fprAps0, frA); + DefinePS1(fprAps1, frA); + DefinePS0(fprBps0, frB); + DefinePS1(fprBps1, frB); + DefinePS0(fprCps0, frC); + DefinePS1(fprCps1, frC); + + if (frD != frA && frD != frB) { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterD, fprRegisterTemp); - // sub frB - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SUB_PAIR, fprRegisterD, fprRegisterB); + if (frD == frC) + { + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprCps0, fprAps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprCps1, fprAps1); + } + else + { + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps0, fprAps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps1, fprAps1); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprDps0, fprCps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprDps1, fprCps1); + } + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_SUB, fprDps0, fprBps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_SUB, fprDps1, fprBps1); } else { - // we multiply temporary by frA - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_MULTIPLY_PAIR, fprRegisterTemp, fprRegisterA); - // sub frB - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SUB_PAIR, fprRegisterTemp, fprRegisterB); - // copy result to frD - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterD, fprRegisterTemp); + DefineTempFPR(fprTemp0, 0); + DefineTempFPR(fprTemp1, 1); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTemp0, fprCps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprTemp1, fprCps1); + if( frD == frA && frD != frB ) + { + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprDps0, fprTemp0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprDps1, fprTemp1); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_SUB, fprDps0, fprBps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_SUB, fprDps1, fprBps1); + } + else + { + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTemp0, fprAps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTemp1, fprAps1); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_SUB, fprTemp0, fprBps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_SUB, fprTemp1, fprBps1); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps0, fprTemp0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps1, fprTemp1); + } } // negate result - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_NEGATE_PAIR, fprRegisterD, fprRegisterD); + if (withNegative) + { + ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_NEGATE, fprDps0); + ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_NEGATE, fprDps1); + } // adjust accuracy - PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps0); + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps1); return true; } @@ -1604,18 +1383,27 @@ bool PPCRecompilerImlGen_PS_SUM0(ppcImlGenContext_t* ppcImlGenContext, uint32 op frB = (opcode>>11)&0x1F; frA = (opcode>>16)&0x1F; frD = (opcode>>21)&0x1F; - //float s0 = (float)(hCPU->fpr[frA].fp0 + hCPU->fpr[frB].fp1); - //float s1 = (float)hCPU->fpr[frC].fp1; - //hCPU->fpr[frD].fp0 = s0; - //hCPU->fpr[frD].fp1 = s1; - // load registers - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SUM0, fprRegisterD, fprRegisterA, fprRegisterB, fprRegisterC); + + DefinePS0(fprDps0, frD); + DefinePS1(fprDps1, frD); + DefinePS0(fprAps0, frA); + DefinePS1(fprBps1, frB); + DefinePS1(fprCps1, frC); + + if( frD == frA ) + { + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprDps0, fprBps1); + } + else + { + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps0, fprAps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprDps0, fprBps1); + } + if (fprDps1 != fprCps1) + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps1, fprCps1); // adjust accuracy - PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps0); + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps1); return true; } @@ -1626,18 +1414,26 @@ bool PPCRecompilerImlGen_PS_SUM1(ppcImlGenContext_t* ppcImlGenContext, uint32 op frB = (opcode>>11)&0x1F; frA = (opcode>>16)&0x1F; frD = (opcode>>21)&0x1F; - //float s0 = (float)hCPU->fpr[frC].fp0; - //float s1 = (float)(hCPU->fpr[frA].fp0 + hCPU->fpr[frB].fp1); - //hCPU->fpr[frD].fp0 = s0; - //hCPU->fpr[frD].fp1 = s1; - // load registers - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SUM1, fprRegisterD, fprRegisterA, fprRegisterB, fprRegisterC); + + DefinePS0(fprDps0, frD); + DefinePS1(fprDps1, frD); + DefinePS0(fprAps0, frA); + DefinePS1(fprBps1, frB); + DefinePS0(fprCps0, frC); + + if (frB != frD) + { + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps1, fprAps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprDps1, fprBps1); + } + else + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ADD, fprDps1, fprAps0); + + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps0, fprCps0); + // adjust accuracy - PPRecompilerImmGen_optionalRoundPairFPRToSinglePrecision(ppcImlGenContext, fprRegisterD); + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps0); + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps1); return true; } @@ -1646,12 +1442,20 @@ bool PPCRecompilerImlGen_PS_NEG(ppcImlGenContext_t* ppcImlGenContext, uint32 opc sint32 frD, frB; frB = (opcode>>11)&0x1F; frD = (opcode>>21)&0x1F; - //hCPU->fpr[frD].fp0 = -hCPU->fpr[frB].fp0; - //hCPU->fpr[frD].fp1 = -hCPU->fpr[frB].fp1; - // load registers - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_NEGATE_PAIR, fprRegisterD, fprRegisterB); + + DefinePS0(fprBps0, frB); + DefinePS1(fprBps1, frB); + DefinePS0(fprDps0, frD); + DefinePS1(fprDps1, frD); + + if (frB != frD) + { + // copy + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps0, fprBps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps1, fprBps1); + } + ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_NEGATE, fprDps0); + ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_NEGATE, fprDps1); return true; } @@ -1660,10 +1464,17 @@ bool PPCRecompilerImlGen_PS_ABS(ppcImlGenContext_t* ppcImlGenContext, uint32 opc sint32 frD, frB; frB = (opcode>>11)&0x1F; frD = (opcode>>21)&0x1F; - // load registers - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_ABS_PAIR, fprRegisterD, fprRegisterB); + + DefinePS0(fprBps0, frB); + DefinePS1(fprBps1, frB); + DefinePS0(fprDps0, frD); + DefinePS1(fprDps1, frD); + + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps0, fprBps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps1, fprBps1); + + ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_ABS, fprDps0); + ppcImlGenContext->emitInst().make_fpr_r(PPCREC_IML_OP_FPR_ABS, fprDps1); return true; } @@ -1675,11 +1486,16 @@ bool PPCRecompilerImlGen_PS_RES(ppcImlGenContext_t* ppcImlGenContext, uint32 opc //hCPU->fpr[frD].fp0 = (float)(1.0f / (float)hCPU->fpr[frB].fp0); //hCPU->fpr[frD].fp1 = (float)(1.0f / (float)hCPU->fpr[frB].fp1); - // load registers - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); + DefinePS0(fprBps0, frB); + DefinePS1(fprBps1, frB); + DefinePS0(fprDps0, frD); + DefinePS1(fprDps1, frD); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_FRES_PAIR, fprRegisterD, fprRegisterB); + ppcImlGenContext->emitInst().make_call_imm((uintptr_t)fres_espresso, fprBps0, IMLREG_INVALID, IMLREG_INVALID, fprDps0); + ppcImlGenContext->emitInst().make_call_imm((uintptr_t)fres_espresso, fprBps1, IMLREG_INVALID, IMLREG_INVALID, fprDps1); + // adjust accuracy + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps0); + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps1); return true; } @@ -1688,13 +1504,17 @@ bool PPCRecompilerImlGen_PS_RSQRTE(ppcImlGenContext_t* ppcImlGenContext, uint32 sint32 frD, frB; frB = (opcode>>11)&0x1F; frD = (opcode>>21)&0x1F; - //hCPU->fpr[frD].fp0 = (float)(1.0f / (float)sqrt(hCPU->fpr[frB].fp0)); - //hCPU->fpr[frD].fp1 = (float)(1.0f / (float)sqrt(hCPU->fpr[frB].fp1)); - // load registers - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_FRSQRTE_PAIR, fprRegisterD, fprRegisterB); + DefinePS0(fprBps0, frB); + DefinePS1(fprBps1, frB); + DefinePS0(fprDps0, frD); + DefinePS1(fprDps1, frD); + + ppcImlGenContext->emitInst().make_call_imm((uintptr_t)frsqrte_espresso, fprBps0, IMLREG_INVALID, IMLREG_INVALID, fprDps0); + ppcImlGenContext->emitInst().make_call_imm((uintptr_t)frsqrte_espresso, fprBps1, IMLREG_INVALID, IMLREG_INVALID, fprDps1); + // adjust accuracy + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps0); + PPRecompilerImmGen_roundToSinglePrecision(ppcImlGenContext, fprDps1); return true; } @@ -1703,14 +1523,15 @@ bool PPCRecompilerImlGen_PS_MR(ppcImlGenContext_t* ppcImlGenContext, uint32 opco sint32 frD, frB; frB = (opcode>>11)&0x1F; frD = (opcode>>21)&0x1F; - //hCPU->fpr[frD].fp0 = hCPU->fpr[frB].fp0; - //hCPU->fpr[frD].fp1 = hCPU->fpr[frB].fp1; - // load registers if( frB != frD ) { - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_PAIR, fprRegisterD, fprRegisterB); + DefinePS0(fprBps0, frB); + DefinePS1(fprBps1, frB); + DefinePS0(fprDps0, frD); + DefinePS1(fprDps1, frD); + + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps0, fprBps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, fprDps1, fprBps1); } return true; } @@ -1723,11 +1544,17 @@ bool PPCRecompilerImlGen_PS_SEL(ppcImlGenContext_t* ppcImlGenContext, uint32 opc frA = (opcode>>16)&0x1F; frD = (opcode>>21)&0x1F; - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterC = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frC); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_SELECT_PAIR, fprRegisterD, fprRegisterA, fprRegisterB, fprRegisterC); + DefinePS0(fprAps0, frA); + DefinePS1(fprAps1, frA); + DefinePS0(fprBps0, frB); + DefinePS1(fprBps1, frB); + DefinePS0(fprCps0, frC); + DefinePS1(fprCps1, frC); + DefinePS0(fprDps0, frD); + DefinePS1(fprDps1, frD); + + ppcImlGenContext->emitInst().make_fpr_r_r_r_r(PPCREC_IML_OP_FPR_SELECT, fprDps0, fprAps0, fprBps0, fprCps0); + ppcImlGenContext->emitInst().make_fpr_r_r_r_r(PPCREC_IML_OP_FPR_SELECT, fprDps1, fprAps1, fprBps1, fprCps1); return true; } @@ -1737,26 +1564,13 @@ bool PPCRecompilerImlGen_PS_MERGE00(ppcImlGenContext_t* ppcImlGenContext, uint32 frB = (opcode>>11)&0x1F; frA = (opcode>>16)&0x1F; frD = (opcode>>21)&0x1F; - //float s0 = (float)hCPU->fpr[frA].fp0; - //float s1 = (float)hCPU->fpr[frB].fp0; - //hCPU->fpr[frD].fp0 = s0; - //hCPU->fpr[frD].fp1 = s1; - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - - if( frA == frB ) - { - // simply duplicate bottom into bottom and top of destination register - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterA); - } - else - { - // copy bottom of frB to top first - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_TOP, fprRegisterD, fprRegisterB); - // copy bottom of frA - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterA); - } + DefinePS0(frpAps0, frA); + DefinePS0(frpBps0, frB); + DefinePS0(frpDps0, frD); + DefinePS1(frpDps1, frD); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, frpDps1, frpBps0); + if (frpDps0 != frpAps0) + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, frpDps0, frpAps0); return true; } @@ -1766,17 +1580,14 @@ bool PPCRecompilerImlGen_PS_MERGE01(ppcImlGenContext_t* ppcImlGenContext, uint32 frB = (opcode>>11)&0x1F; frA = (opcode>>16)&0x1F; frD = (opcode>>21)&0x1F; - // hCPU->fpr[frD].fp0 = hCPU->fpr[frA].fp0; - // hCPU->fpr[frD].fp1 = hCPU->fpr[frB].fp1; - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - - if( fprRegisterD != fprRegisterB ) - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_TOP_TO_TOP, fprRegisterD, fprRegisterB); - if( fprRegisterD != fprRegisterA ) - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterA); + DefinePS0(frpAps0, frA); + DefinePS1(frpBps1, frB); + DefinePS0(frpDps0, frD); + DefinePS1(frpDps1, frD); + if (frpDps0 != frpAps0) + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, frpDps0, frpAps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, frpDps1, frpBps1); return true; } @@ -1787,33 +1598,22 @@ bool PPCRecompilerImlGen_PS_MERGE10(ppcImlGenContext_t* ppcImlGenContext, uint32 frA = (opcode>>16)&0x1F; frD = (opcode>>21)&0x1F; - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - if( frA == frB ) + DefinePS1(frpAps1, frA); + DefinePS0(frpBps0, frB); + DefinePS0(frpDps0, frD); + DefinePS1(frpDps1, frD); + + if (frD != frB) { - // swap bottom and top - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_AND_TOP_SWAPPED, fprRegisterD, fprRegisterA); - } - else if( frA == frD ) - { - // copy frB bottom to frD bottom - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterB); - // swap lower and upper half of frD - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_AND_TOP_SWAPPED, fprRegisterD, fprRegisterD); - } - else if( frB == frD ) - { - // copy upper half of frA to upper half of frD - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_TOP_TO_TOP, fprRegisterD, fprRegisterA); - // swap lower and upper half of frD - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_AND_TOP_SWAPPED, fprRegisterD, fprRegisterD); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, frpDps0, frpAps1); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, frpDps1, frpBps0); } else { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterA); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_TO_BOTTOM, fprRegisterD, fprRegisterB); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_BOTTOM_AND_TOP_SWAPPED, fprRegisterD, fprRegisterD); + DefineTempFPR(frpTemp, 0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, frpTemp, frpBps0); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, frpDps0, frpAps1); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, frpDps1, frpTemp); } return true; } @@ -1825,73 +1625,64 @@ bool PPCRecompilerImlGen_PS_MERGE11(ppcImlGenContext_t* ppcImlGenContext, uint32 frA = (opcode>>16)&0x1F; frD = (opcode>>21)&0x1F; - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - IMLReg fprRegisterD = PPCRecompilerImlGen_loadOverwriteFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frD); - if( fprRegisterA == fprRegisterB ) - { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterA); - } - else if( fprRegisterD != fprRegisterB ) - { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM_AND_TOP, fprRegisterD, fprRegisterA); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_TOP_TO_TOP, fprRegisterD, fprRegisterB); - } - else if( fprRegisterD == fprRegisterB ) - { - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_COPY_TOP_TO_BOTTOM, fprRegisterD, fprRegisterA); - } - else - { - debugBreakpoint(); - return false; - } + DefinePS1(frpAps1, frA); + DefinePS1(frpBps1, frB); + DefinePS0(frpDps0, frD); + DefinePS1(frpDps1, frD); + + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, frpDps0, frpAps1); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_ASSIGN, frpDps1, frpBps1); return true; } bool PPCRecompilerImlGen_PS_CMPO0(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { - printf("PS_CMPO0: Not implemented\n"); + // Not implemented return false; - - sint32 crfD, frA, frB; - uint32 c=0; - frB = (opcode>>11)&0x1F; - frA = (opcode>>16)&0x1F; - crfD = (opcode>>23)&0x7; - - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frA); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0+frB); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_FCMPO_BOTTOM, fprRegisterA, fprRegisterB, crfD); - return true; } bool PPCRecompilerImlGen_PS_CMPU0(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { - printf("PS_CMPU0: Not implemented\n"); - return false; - sint32 crfD, frA, frB; frB = (opcode >> 11) & 0x1F; frA = (opcode >> 16) & 0x1F; crfD = (opcode >> 23) & 0x7; - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frA); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frB); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_FCMPU_BOTTOM, fprRegisterA, fprRegisterB, crfD); + + DefinePS0(fprA, frA); + DefinePS0(fprB, frB); + + IMLReg crBitRegLT = _GetRegCR(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_LT); + IMLReg crBitRegGT = _GetRegCR(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_GT); + IMLReg crBitRegEQ = _GetRegCR(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_EQ); + IMLReg crBitRegSO = _GetRegCR(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_SO); + + ppcImlGenContext->emitInst().make_fpr_compare(fprA, fprB, crBitRegLT, IMLCondition::UNORDERED_LT); + ppcImlGenContext->emitInst().make_fpr_compare(fprA, fprB, crBitRegGT, IMLCondition::UNORDERED_GT); + ppcImlGenContext->emitInst().make_fpr_compare(fprA, fprB, crBitRegEQ, IMLCondition::UNORDERED_EQ); + ppcImlGenContext->emitInst().make_fpr_compare(fprA, fprB, crBitRegSO, IMLCondition::UNORDERED_U); + + // todo: set fpscr return true; } bool PPCRecompilerImlGen_PS_CMPU1(ppcImlGenContext_t* ppcImlGenContext, uint32 opcode) { - printf("PS_CMPU1: Not implemented\n"); - return false; - sint32 crfD, frA, frB; frB = (opcode >> 11) & 0x1F; frA = (opcode >> 16) & 0x1F; crfD = (opcode >> 23) & 0x7; - IMLReg fprRegisterA = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frA); - IMLReg fprRegisterB = PPCRecompilerImlGen_loadFPRRegister(ppcImlGenContext, PPCREC_NAME_FPR0 + frB); - PPCRecompilerImlGen_generateNewInstruction_fpr_r_r(ppcImlGenContext, PPCREC_IML_OP_FPR_FCMPU_TOP, fprRegisterA, fprRegisterB, crfD); + + DefinePS1(fprA, frA); + DefinePS1(fprB, frB); + + IMLReg crBitRegLT = _GetRegCR(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_LT); + IMLReg crBitRegGT = _GetRegCR(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_GT); + IMLReg crBitRegEQ = _GetRegCR(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_EQ); + IMLReg crBitRegSO = _GetRegCR(ppcImlGenContext, crfD, Espresso::CR_BIT::CR_BIT_INDEX_SO); + + ppcImlGenContext->emitInst().make_fpr_compare(fprA, fprB, crBitRegLT, IMLCondition::UNORDERED_LT); + ppcImlGenContext->emitInst().make_fpr_compare(fprA, fprB, crBitRegGT, IMLCondition::UNORDERED_GT); + ppcImlGenContext->emitInst().make_fpr_compare(fprA, fprB, crBitRegEQ, IMLCondition::UNORDERED_EQ); + ppcImlGenContext->emitInst().make_fpr_compare(fprA, fprB, crBitRegSO, IMLCondition::UNORDERED_U); return true; -} \ No newline at end of file +} diff --git a/src/asm/CMakeLists.txt b/src/asm/CMakeLists.txt deleted file mode 100644 index 19a7ddd8..00000000 --- a/src/asm/CMakeLists.txt +++ /dev/null @@ -1,53 +0,0 @@ -project(CemuAsm C) - -if (CMAKE_OSX_ARCHITECTURES) - set(CEMU_ASM_ARCHITECTURE ${CMAKE_OSX_ARCHITECTURES}) -else() - set(CEMU_ASM_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR}) -endif() - -if (CEMU_ASM_ARCHITECTURE MATCHES "(x86)|(X86)|(amd64)|(AMD64)") - - if (WIN32) - - enable_language(C ASM_MASM) - - add_library(CemuAsm x64util_masm.asm) - set_source_files_properties(x64util_masm.asm PROPERTIES LANGUAGE ASM_MASM) - - # workaround for cr flag being passed to LINK.exe which considers it an input file and thus fails - # doesn't always seem to happen. The Windows CI builds were fine, but locally I would run into this problem - # possibly related to https://gitlab.kitware.com/cmake/cmake/-/issues/18889 - set(CMAKE_ASM_MASM_CREATE_STATIC_LIBRARY "<CMAKE_AR> /OUT:<TARGET> <LINK_FLAGS> <OBJECTS>") - - set_property(TARGET CemuAsm PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>") - - else() - - # NASM - if (APPLE) - set(CMAKE_ASM_NASM_COMPILE_OBJECT "<CMAKE_ASM_NASM_COMPILER> -g -Fdwarf -f macho64 --prefix _ -o <OBJECT> <SOURCE>") - else() - set(CMAKE_ASM_NASM_COMPILE_OBJECT "<CMAKE_ASM_NASM_COMPILER> -g -Fdwarf -f elf64 -o <OBJECT> <SOURCE>") - endif() - set(CMAKE_ASM_NASM_LINK_EXECUTABLE "ld <FLAGS> <CMAKE_ASM_NASM_LINK_FLAGS> <LINK_FLAGS> -fPIC <OBJECTS> -o <TARGET> <LINK_LIBRARIES>") - - enable_language(C ASM_NASM) - - add_library(CemuAsm x64util_nasm.asm) - set_source_files_properties(x64util_nasm.asm PROPERTIES LANGUAGE ASM_NASM) - - if (APPLE) - set_target_properties(CemuAsm PROPERTIES NASM_OBJ_FORMAT macho64) - else() - set_target_properties(CemuAsm PROPERTIES NASM_OBJ_FORMAT elf64) - endif() - set_target_properties(CemuAsm PROPERTIES LINKER_LANGUAGE C) - - endif() - -elseif(CEMU_ASM_ARCHITECTURE MATCHES "(aarch64)|(AARCH64)|(arm64)|(ARM64)") - add_library(CemuAsm stub.cpp) -else() - message(STATUS "CemuAsm - Unsupported arch: ${CEMU_ASM_ARCHITECTURE}") -endif() diff --git a/src/asm/stub.cpp b/src/asm/stub.cpp deleted file mode 100644 index 8d1c8b69..00000000 --- a/src/asm/stub.cpp +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/asm/x64util.h b/src/asm/x64util.h deleted file mode 100644 index 885c2f63..00000000 --- a/src/asm/x64util.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#if defined(ARCH_X86_64) - -extern "C" void recompiler_fres(); -extern "C" void recompiler_frsqrte(); - -#else - -// stubbed on non-x86 for now -static void recompiler_fres() -{ - cemu_assert_unimplemented(); -} -static void recompiler_frsqrte() -{ - cemu_assert_unimplemented(); -} - -#endif diff --git a/src/asm/x64util_masm.asm b/src/asm/x64util_masm.asm deleted file mode 100644 index 2587c786..00000000 --- a/src/asm/x64util_masm.asm +++ /dev/null @@ -1,233 +0,0 @@ -.code - -recompiler_fres PROC - ; store all modified registers -push rdx -push rcx -push rax -push r8 -lea r8,[asmFresLookupTable] -movq rdx, xmm15 -mov rcx,rdx -shr rcx,2Fh -mov rax,rdx -and ecx,1Fh -shr rax,25h -and eax,3FFh -imul eax,dword ptr [r8+rcx*8+4] -mov r8d,dword ptr [r8+rcx*8] -mov rcx,rdx -shr rcx,34h -inc eax -shr eax,1 -sub r8d,eax -and ecx,7FFh -jne fres_espresso_label3 -mov rax,7FF0000000000000h -or rdx,rax -movq xmm15, rdx -pop r8 -pop rax -pop rcx -pop rdx -ret -fres_espresso_label3: -cmp ecx,7FFh -jne fres_espresso_label4 -mov rax,0FFFFFFFFFFFFFh -test rax,rdx -jne fres_espresso_label1 -test rdx,rdx -jns fres_espresso_label2 -mov rax,8000000000000000h -movq xmm15, rax -pop r8 -pop rax -pop rcx -pop rdx -ret -fres_espresso_label2: -xorps xmm15,xmm15 -pop r8 -pop rax -pop rcx -pop rdx -ret -fres_espresso_label4: -mov eax,7FDh -sub eax,ecx -mov ecx,eax -mov rax,8000000000000000h -and rdx,rax -shl rcx,34h -mov eax,r8d -or rcx,rdx -shl rax,1Dh -add rcx,rax -movq xmm15, rcx -fres_espresso_label1: -pop r8 -pop rax -pop rcx -pop rdx -ret - -recompiler_fres ENDP - -asmFresLookupTable: -DD 07ff800h, 03e1h -DD 0783800h, 03a7h -DD 070ea00h, 0371h -DD 06a0800h, 0340h -DD 0638800h, 0313h -DD 05d6200h, 02eah -DD 0579000h, 02c4h -DD 0520800h, 02a0h -DD 04cc800h, 027fh -DD 047ca00h, 0261h -DD 0430800h, 0245h -DD 03e8000h, 022ah -DD 03a2c00h, 0212h -DD 0360800h, 01fbh -DD 0321400h, 01e5h -DD 02e4a00h, 01d1h -DD 02aa800h, 01beh -DD 0272c00h, 01ach -DD 023d600h, 019bh -DD 0209e00h, 018bh -DD 01d8800h, 017ch -DD 01a9000h, 016eh -DD 017ae00h, 015bh -DD 014f800h, 015bh -DD 0124400h, 0143h -DD 0fbe00h, 0143h -DD 0d3800h, 012dh -DD 0ade00h, 012dh -DD 088400h, 011ah -DD 065000h, 011ah -DD 041c00h, 0108h -DD 020c00h, 0106h - -recompiler_frsqrte PROC - ; store all modified registers -push rdx -push rcx -push rax -push r8 -push r9 -movq r8, xmm15 -mov rax,7FFFFFFFFFFFFFFFh -test rax,r8 -jne frsqrte_espresso_label1 -mov rax,0FFF0000000000000h -and r8,rax -mov rax,7FF0000000000000h -or r8,rax -movq xmm15, r8 -pop r9 -pop r8 -pop rax -pop rcx -pop rdx -ret -frsqrte_espresso_label1: -mov r9,r8 -shr r9,34h -and r9d,7FFh -cmp r9d,7FFh -jne frsqrte_espresso_label2 -mov rax,0FFFFFFFFFFFFFh -test rax,r8 -jne frsqrte_espresso_label3 -test r8,r8 -js frsqrte_espresso_label4 -xorps xmm15,xmm15 -pop r9 -pop r8 -pop rax -pop rcx -pop rdx -ret -frsqrte_espresso_label2: -test r8,r8 -jns frsqrte_espresso_label5 -frsqrte_espresso_label4: -mov rax,7FF8000000000000h -movq xmm15, rax -pop r9 -pop r8 -pop rax -pop rcx -pop rdx -ret -frsqrte_espresso_label5: -lea rdx,[asmFrsqrteLookupTable] -mov rax,r8 -shr rax,30h -mov rcx,r8 -shr rcx,25h -and eax,1Fh -and ecx,7FFh -imul ecx,dword ptr [rdx+rax*8+4] -mov eax,dword ptr [rdx+rax*8] -sub eax,ecx -lea ecx,[r9-3FDh] -shr ecx,1 -movsxd rdx,eax -mov eax,3FFh -sub eax,ecx -shl rdx,1Ah -mov ecx,eax -mov rax,8000000000000000h -and r8,rax -shl rcx,34h -or rcx,r8 -add rdx,rcx -movq xmm15, rdx -frsqrte_espresso_label3: -pop r9 -pop r8 -pop rax -pop rcx -pop rdx -ret - -recompiler_frsqrte ENDP - -asmFrsqrteLookupTable: -DD 01a7e800h, 0568h -DD 017cb800h, 04f3h -DD 01552800h, 048dh -DD 0130c000h, 0435h -DD 010f2000h, 03e7h -DD 0eff000h, 03a2h -DD 0d2e000h, 0365h -DD 0b7c000h, 032eh -DD 09e5000h, 02fch -DD 0867000h, 02d0h -DD 06ff000h, 02a8h -DD 05ab800h, 0283h -DD 046a000h, 0261h -DD 0339800h, 0243h -DD 0218800h, 0226h -DD 0105800h, 020bh -DD 03ffa000h, 07a4h -DD 03c29000h, 0700h -DD 038aa000h, 0670h -DD 03572000h, 05f2h -DD 03279000h, 0584h -DD 02fb7000h, 0524h -DD 02d26000h, 04cch -DD 02ac0000h, 047eh -DD 02881000h, 043ah -DD 02665000h, 03fah -DD 02468000h, 03c2h -DD 02287000h, 038eh -DD 020c1000h, 035eh -DD 01f12000h, 0332h -DD 01d79000h, 030ah -DD 01bf4000h, 02e6h - - - -END \ No newline at end of file diff --git a/src/asm/x64util_nasm.asm b/src/asm/x64util_nasm.asm deleted file mode 100644 index 89878f6e..00000000 --- a/src/asm/x64util_nasm.asm +++ /dev/null @@ -1,237 +0,0 @@ -DEFAULT REL - -SECTION .text - -global udiv128 -global recompiler_fres -global recompiler_frsqrte - -udiv128: - mov rax, rcx - div r8 - mov [r9], rdx - ret - -recompiler_fres: - ; store all modified registers -push rdx -push rcx -push rax -push r8 -lea r8,[asmFresLookupTable] -movq rdx, xmm15 -mov rcx,rdx -shr rcx,2Fh -mov rax,rdx -and ecx,1Fh -shr rax,25h -and eax,3FFh -imul eax,dword [r8+rcx*8+4] -mov r8d,dword [r8+rcx*8] -mov rcx,rdx -shr rcx,34h -inc eax -shr eax,1 -sub r8d,eax -and ecx,7FFh -jne fres_espresso_label3 -mov rax,7FF0000000000000h -or rdx,rax -movq xmm15, rdx -pop r8 -pop rax -pop rcx -pop rdx -ret -fres_espresso_label3: -cmp ecx,7FFh -jne fres_espresso_label4 -mov rax,0FFFFFFFFFFFFFh -test rax,rdx -jne fres_espresso_label1 -test rdx,rdx -jns fres_espresso_label2 -mov rax,8000000000000000h -movq xmm15, rax -pop r8 -pop rax -pop rcx -pop rdx -ret -fres_espresso_label2: -xorps xmm15,xmm15 -pop r8 -pop rax -pop rcx -pop rdx -ret -fres_espresso_label4: -mov eax,7FDh -sub eax,ecx -mov ecx,eax -mov rax,8000000000000000h -and rdx,rax -shl rcx,34h -mov eax,r8d -or rcx,rdx -shl rax,1Dh -add rcx,rax -movq xmm15, rcx -fres_espresso_label1: -pop r8 -pop rax -pop rcx -pop rdx -ret - -asmFresLookupTable: -DD 07ff800h, 03e1h -DD 0783800h, 03a7h -DD 070ea00h, 0371h -DD 06a0800h, 0340h -DD 0638800h, 0313h -DD 05d6200h, 02eah -DD 0579000h, 02c4h -DD 0520800h, 02a0h -DD 04cc800h, 027fh -DD 047ca00h, 0261h -DD 0430800h, 0245h -DD 03e8000h, 022ah -DD 03a2c00h, 0212h -DD 0360800h, 01fbh -DD 0321400h, 01e5h -DD 02e4a00h, 01d1h -DD 02aa800h, 01beh -DD 0272c00h, 01ach -DD 023d600h, 019bh -DD 0209e00h, 018bh -DD 01d8800h, 017ch -DD 01a9000h, 016eh -DD 017ae00h, 015bh -DD 014f800h, 015bh -DD 0124400h, 0143h -DD 0fbe00h, 0143h -DD 0d3800h, 012dh -DD 0ade00h, 012dh -DD 088400h, 011ah -DD 065000h, 011ah -DD 041c00h, 0108h -DD 020c00h, 0106h - -recompiler_frsqrte: - ; store all modified registers -push rdx -push rcx -push rax -push r8 -push r9 -movq r8, xmm15 -mov rax,7FFFFFFFFFFFFFFFh -test rax,r8 -jne frsqrte_espresso_label1 -mov rax,0FFF0000000000000h -and r8,rax -mov rax,7FF0000000000000h -or r8,rax -movq xmm15, r8 -pop r9 -pop r8 -pop rax -pop rcx -pop rdx -ret -frsqrte_espresso_label1: -mov r9,r8 -shr r9,34h -and r9d,7FFh -cmp r9d,7FFh -jne frsqrte_espresso_label2 -mov rax,0FFFFFFFFFFFFFh -test rax,r8 -jne frsqrte_espresso_label3 -test r8,r8 -js frsqrte_espresso_label4 -xorps xmm15,xmm15 -pop r9 -pop r8 -pop rax -pop rcx -pop rdx -ret -frsqrte_espresso_label2: -test r8,r8 -jns frsqrte_espresso_label5 -frsqrte_espresso_label4: -mov rax,7FF8000000000000h -movq xmm15, rax -pop r9 -pop r8 -pop rax -pop rcx -pop rdx -ret -frsqrte_espresso_label5: -lea rdx,[asmFrsqrteLookupTable] -mov rax,r8 -shr rax,30h -mov rcx,r8 -shr rcx,25h -and eax,1Fh -and ecx,7FFh -imul ecx,dword [rdx+rax*8+4] -mov eax,dword [rdx+rax*8] -sub eax,ecx -lea ecx,[r9-3FDh] -shr ecx,1 -movsxd rdx,eax -mov eax,3FFh -sub eax,ecx -shl rdx,1Ah -mov ecx,eax -mov rax,8000000000000000h -and r8,rax -shl rcx,34h -or rcx,r8 -add rdx,rcx -movq xmm15, rdx -frsqrte_espresso_label3: -pop r9 -pop r8 -pop rax -pop rcx -pop rdx -ret - -asmFrsqrteLookupTable: -DD 01a7e800h, 0568h -DD 017cb800h, 04f3h -DD 01552800h, 048dh -DD 0130c000h, 0435h -DD 010f2000h, 03e7h -DD 0eff000h, 03a2h -DD 0d2e000h, 0365h -DD 0b7c000h, 032eh -DD 09e5000h, 02fch -DD 0867000h, 02d0h -DD 06ff000h, 02a8h -DD 05ab800h, 0283h -DD 046a000h, 0261h -DD 0339800h, 0243h -DD 0218800h, 0226h -DD 0105800h, 020bh -DD 03ffa000h, 07a4h -DD 03c29000h, 0700h -DD 038aa000h, 0670h -DD 03572000h, 05f2h -DD 03279000h, 0584h -DD 02fb7000h, 0524h -DD 02d26000h, 04cch -DD 02ac0000h, 047eh -DD 02881000h, 043ah -DD 02665000h, 03fah -DD 02468000h, 03c2h -DD 02287000h, 038eh -DD 020c1000h, 035eh -DD 01f12000h, 0332h -DD 01d79000h, 030ah -DD 01bf4000h, 02e6h From 557aff4024a475b4559be11b850f838ecb1e8896 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Thu, 8 May 2025 08:29:47 +0200 Subject: [PATCH 257/299] PPCRec: Implement PSQ scaling --- .../Recompiler/BackendX64/BackendX64FPU.cpp | 19 +++++ .../Recompiler/IML/IMLInstruction.cpp | 3 +- .../Espresso/Recompiler/IML/IMLInstruction.h | 3 + .../Recompiler/PPCRecompilerImlGenFPU.cpp | 77 +++++++++++++++---- 4 files changed, 85 insertions(+), 17 deletions(-) diff --git a/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64FPU.cpp b/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64FPU.cpp index dc07f9d0..6a8b1b97 100644 --- a/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64FPU.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/BackendX64/BackendX64FPU.cpp @@ -241,6 +241,25 @@ void PPCRecompilerX64Gen_imlInstruction_fpr_r_r(PPCRecFunction_t* PPCRecFunction x64Gen_cvtsi2sd_xmmReg_xmmReg(x64GenContext, regFpr, regGpr); return; } + else if (imlInstruction->operation == PPCREC_IML_OP_FPR_BITCAST_INT_TO_FLOAT) + { + cemu_assert_debug(imlInstruction->op_fpr_r_r.regR.GetRegFormat() == IMLRegFormat::F64); // assuming target is always F64 for now + cemu_assert_debug(imlInstruction->op_fpr_r_r.regA.GetRegFormat() == IMLRegFormat::I32); // supporting only 32bit floats as input for now + // exact operation depends on size of types. Floats are automatically promoted to double if the target is F64 + uint32 regFpr = _regF64(imlInstruction->op_fpr_r_r.regR); + if (imlInstruction->op_fpr_r_r.regA.GetRegFormat() == IMLRegFormat::I32) + { + uint32 regGpr = _regI32(imlInstruction->op_fpr_r_r.regA); + x64Gen_movq_xmmReg_reg64(x64GenContext, regFpr, regGpr); // using reg32 as reg64 param here is ok. We'll refactor later + // float to double + x64Gen_cvtss2sd_xmmReg_xmmReg(x64GenContext, regFpr, regFpr); + } + else + { + cemu_assert_unimplemented(); + } + return; + } uint32 regR = _regF64(imlInstruction->op_fpr_r_r.regR); uint32 regA = _regF64(imlInstruction->op_fpr_r_r.regA); diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.cpp b/src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.cpp index 60b7c6ca..997de4e9 100644 --- a/src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.cpp @@ -277,7 +277,8 @@ void IMLInstruction::CheckRegisterUsage(IMLUsedRegisters* registersUsed) const } else if (operation == PPCREC_IML_OP_FPR_FLOAT_TO_INT || - operation == PPCREC_IML_OP_FPR_INT_TO_FLOAT) + operation == PPCREC_IML_OP_FPR_INT_TO_FLOAT || + operation == PPCREC_IML_OP_FPR_BITCAST_INT_TO_FLOAT) { registersUsed->writtenGPR1 = op_fpr_r_r.regR; registersUsed->readGPR1 = op_fpr_r_r.regA; diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.h b/src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.h index 3b3898e9..4df2a666 100644 --- a/src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.h +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IMLInstruction.h @@ -143,6 +143,9 @@ enum PPCREC_IML_OP_FPR_INT_TO_FLOAT, // convert integer value in gpr to floating point value in fpr PPCREC_IML_OP_FPR_FLOAT_TO_INT, // convert floating point value in fpr to integer value in gpr + // Bitcast (FPR_R_R) + PPCREC_IML_OP_FPR_BITCAST_INT_TO_FLOAT, + // R_R_R + R_R_S32 PPCREC_IML_OP_ADD, // also R_R_R_CARRY PPCREC_IML_OP_SUB, diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGenFPU.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGenFPU.cpp index 7eb8a4b6..6e602b47 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGenFPU.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompilerImlGenFPU.cpp @@ -744,7 +744,21 @@ void PPCRecompilerImlGen_ClampInteger(ppcImlGenContext_t* ppcImlGenContext, IMLR ); } -void PPCRecompilerImlGen_EmitPSQLoadCase(ppcImlGenContext_t* ppcImlGenContext, Espresso::PSQ_LOAD_TYPE loadType, bool readPS1, IMLReg gprA, sint32 imm, IMLReg fprDPS0, IMLReg fprDPS1) +void PPCRecompilerIMLGen_GetPSQScale(ppcImlGenContext_t* ppcImlGenContext, IMLReg gqrRegister, IMLReg fprRegScaleOut, bool isLoad) +{ + IMLReg gprTmp2 = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 2); + // extract scale factor and sign extend it + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_LEFT_SHIFT, gprTmp2, gqrRegister, 32 - ((isLoad ? 24 : 8)+7)); + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_RIGHT_SHIFT_S, gprTmp2, gprTmp2, (32-23)-7); + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_AND, gprTmp2, gprTmp2, 0x1FF<<23); + if (isLoad) + ppcImlGenContext->emitInst().make_r_r(PPCREC_IML_OP_NEG, gprTmp2, gprTmp2); + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_ADD, gprTmp2, gprTmp2, 0x7F<<23); + // gprTmp2 now holds the scale float bits, bitcast to float + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_BITCAST_INT_TO_FLOAT, fprRegScaleOut, gprTmp2); +} + +void PPCRecompilerImlGen_EmitPSQLoadCase(ppcImlGenContext_t* ppcImlGenContext, sint32 gqrIndex, Espresso::PSQ_LOAD_TYPE loadType, bool readPS1, IMLReg gprA, sint32 imm, IMLReg fprDPS0, IMLReg fprDPS1) { if (loadType == Espresso::PSQ_LOAD_TYPE::TYPE_F32) { @@ -756,26 +770,42 @@ void PPCRecompilerImlGen_EmitPSQLoadCase(ppcImlGenContext_t* ppcImlGenContext, E } if (loadType == Espresso::PSQ_LOAD_TYPE::TYPE_U16 || loadType == Espresso::PSQ_LOAD_TYPE::TYPE_S16) { + // get scale factor + IMLReg gqrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + SPR_UGQR0 + gqrIndex); + IMLReg fprScaleReg = _GetFPRTemp(ppcImlGenContext, 2); + PPCRecompilerIMLGen_GetPSQScale(ppcImlGenContext, gqrRegister, fprScaleReg, true); + bool isSigned = (loadType == Espresso::PSQ_LOAD_TYPE::TYPE_S16); IMLReg gprTmp = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 0); ppcImlGenContext->emitInst().make_r_memory(gprTmp, gprA, imm, 16, isSigned, true); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_INT_TO_FLOAT, fprDPS0, gprTmp); + + ppcImlGenContext->emitInst().make_fpr_r_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprDPS0, fprDPS0, fprScaleReg); + if(readPS1) { ppcImlGenContext->emitInst().make_r_memory(gprTmp, gprA, imm + 2, 16, isSigned, true); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_INT_TO_FLOAT, fprDPS1, gprTmp); + ppcImlGenContext->emitInst().make_fpr_r_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprDPS1, fprDPS1, fprScaleReg); } } else if (loadType == Espresso::PSQ_LOAD_TYPE::TYPE_U8 || loadType == Espresso::PSQ_LOAD_TYPE::TYPE_S8) { + // get scale factor + IMLReg gqrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + SPR_UGQR0 + gqrIndex); + IMLReg fprScaleReg = _GetFPRTemp(ppcImlGenContext, 2); + PPCRecompilerIMLGen_GetPSQScale(ppcImlGenContext, gqrRegister, fprScaleReg, true); + bool isSigned = (loadType == Espresso::PSQ_LOAD_TYPE::TYPE_S8); IMLReg gprTmp = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 0); ppcImlGenContext->emitInst().make_r_memory(gprTmp, gprA, imm, 8, isSigned, true); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_INT_TO_FLOAT, fprDPS0, gprTmp); + ppcImlGenContext->emitInst().make_fpr_r_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprDPS0, fprDPS0, fprScaleReg); if(readPS1) { ppcImlGenContext->emitInst().make_r_memory(gprTmp, gprA, imm + 1, 8, isSigned, true); ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_INT_TO_FLOAT, fprDPS1, gprTmp); + ppcImlGenContext->emitInst().make_fpr_r_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprDPS1, fprDPS1, fprScaleReg); } } } @@ -812,14 +842,15 @@ bool PPCRecompilerImlGen_PSQ_L(ppcImlGenContext_t* ppcImlGenContext, uint32 opco IMLReg gqrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + SPR_UGQR0 + gqrIndex); IMLReg loadTypeReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 0); // extract the load type from the GQR register - ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_AND, loadTypeReg, gqrRegister, 0x7); + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_RIGHT_SHIFT_U, loadTypeReg, gqrRegister, 16); + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_AND, loadTypeReg, loadTypeReg, 0x7); IMLSegment* caseSegment[6]; sint32 compareValues[6] = {0, 4, 5, 6, 7}; PPCIMLGen_CreateSegmentBranchedPathMultiple(*ppcImlGenContext, *ppcImlGenContext->currentBasicBlock, caseSegment, loadTypeReg, compareValues, 5, 0); for (sint32 i=0; i<5; i++) { IMLRedirectInstOutput outputToCase(ppcImlGenContext, caseSegment[i]); // while this is in scope, instructions go to caseSegment[i] - PPCRecompilerImlGen_EmitPSQLoadCase(ppcImlGenContext, static_cast<Espresso::PSQ_LOAD_TYPE>(compareValues[i]), readPS1, gprA, imm, fprDPS0, fprDPS1); + PPCRecompilerImlGen_EmitPSQLoadCase(ppcImlGenContext, gqrIndex, static_cast<Espresso::PSQ_LOAD_TYPE>(compareValues[i]), readPS1, gprA, imm, fprDPS0, fprDPS1); // create the case jump instructions here because we need to add it last caseSegment[i]->AppendInstruction()->make_jump(); } @@ -839,11 +870,11 @@ bool PPCRecompilerImlGen_PSQ_L(ppcImlGenContext_t* ppcImlGenContext, uint32 opco return false; } - PPCRecompilerImlGen_EmitPSQLoadCase(ppcImlGenContext, type, readPS1, gprA, imm, fprDPS0, fprDPS1); + PPCRecompilerImlGen_EmitPSQLoadCase(ppcImlGenContext, gqrIndex, type, readPS1, gprA, imm, fprDPS0, fprDPS1); return true; } -void PPCRecompilerImlGen_EmitPSQStoreCase(ppcImlGenContext_t* ppcImlGenContext, Espresso::PSQ_LOAD_TYPE storeType, bool storePS1, IMLReg gprA, sint32 imm, IMLReg fprDPS0, IMLReg fprDPS1) +void PPCRecompilerImlGen_EmitPSQStoreCase(ppcImlGenContext_t* ppcImlGenContext, sint32 gqrIndex, Espresso::PSQ_LOAD_TYPE storeType, bool storePS1, IMLReg gprA, sint32 imm, IMLReg fprDPS0, IMLReg fprDPS1) { cemu_assert_debug(!storePS1 || fprDPS1.IsValid()); if (storeType == Espresso::PSQ_LOAD_TYPE::TYPE_F32) @@ -856,10 +887,18 @@ void PPCRecompilerImlGen_EmitPSQStoreCase(ppcImlGenContext_t* ppcImlGenContext, } else if (storeType == Espresso::PSQ_LOAD_TYPE::TYPE_U16 || storeType == Espresso::PSQ_LOAD_TYPE::TYPE_S16) { + // get scale factor + IMLReg gqrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + SPR_UGQR0 + gqrIndex); + IMLReg fprScaleReg = _GetFPRTemp(ppcImlGenContext, 2); + PPCRecompilerIMLGen_GetPSQScale(ppcImlGenContext, gqrRegister, fprScaleReg, false); + bool isSigned = (storeType == Espresso::PSQ_LOAD_TYPE::TYPE_S16); + IMLReg fprTmp = _GetFPRTemp(ppcImlGenContext, 0); + IMLReg gprTmp = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 0); - ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_FLOAT_TO_INT, gprTmp, fprDPS0); - // todo - scaling + ppcImlGenContext->emitInst().make_fpr_r_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTmp, fprDPS0, fprScaleReg); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_FLOAT_TO_INT, gprTmp, fprTmp); + if (isSigned) PPCRecompilerImlGen_ClampInteger(ppcImlGenContext, gprTmp, -32768, 32767); else @@ -867,8 +906,8 @@ void PPCRecompilerImlGen_EmitPSQStoreCase(ppcImlGenContext_t* ppcImlGenContext, ppcImlGenContext->emitInst().make_memory_r(gprTmp, gprA, imm, 16, true); if(storePS1) { - ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_FLOAT_TO_INT, gprTmp, fprDPS1); - // todo - scaling + ppcImlGenContext->emitInst().make_fpr_r_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTmp, fprDPS1, fprScaleReg); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_FLOAT_TO_INT, gprTmp, fprTmp); if (isSigned) PPCRecompilerImlGen_ClampInteger(ppcImlGenContext, gprTmp, -32768, 32767); else @@ -878,9 +917,16 @@ void PPCRecompilerImlGen_EmitPSQStoreCase(ppcImlGenContext_t* ppcImlGenContext, } else if (storeType == Espresso::PSQ_LOAD_TYPE::TYPE_U8 || storeType == Espresso::PSQ_LOAD_TYPE::TYPE_S8) { + // get scale factor + IMLReg gqrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + SPR_UGQR0 + gqrIndex); + IMLReg fprScaleReg = _GetFPRTemp(ppcImlGenContext, 2); + PPCRecompilerIMLGen_GetPSQScale(ppcImlGenContext, gqrRegister, fprScaleReg, false); + bool isSigned = (storeType == Espresso::PSQ_LOAD_TYPE::TYPE_S8); + IMLReg fprTmp = _GetFPRTemp(ppcImlGenContext, 0); IMLReg gprTmp = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 0); - ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_FLOAT_TO_INT, gprTmp, fprDPS0); + ppcImlGenContext->emitInst().make_fpr_r_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTmp, fprDPS0, fprScaleReg); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_FLOAT_TO_INT, gprTmp, fprTmp); if (isSigned) PPCRecompilerImlGen_ClampInteger(ppcImlGenContext, gprTmp, -128, 127); else @@ -888,8 +934,8 @@ void PPCRecompilerImlGen_EmitPSQStoreCase(ppcImlGenContext_t* ppcImlGenContext, ppcImlGenContext->emitInst().make_memory_r(gprTmp, gprA, imm, 8, true); if(storePS1) { - ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_FLOAT_TO_INT, gprTmp, fprDPS1); - // todo - scaling + ppcImlGenContext->emitInst().make_fpr_r_r_r(PPCREC_IML_OP_FPR_MULTIPLY, fprTmp, fprDPS1, fprScaleReg); + ppcImlGenContext->emitInst().make_fpr_r_r(PPCREC_IML_OP_FPR_FLOAT_TO_INT, gprTmp, fprTmp); if (isSigned) PPCRecompilerImlGen_ClampInteger(ppcImlGenContext, gprTmp, -128, 127); else @@ -928,8 +974,7 @@ bool PPCRecompilerImlGen_PSQ_ST(ppcImlGenContext_t* ppcImlGenContext, uint32 opc IMLReg gqrRegister = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_SPR0 + SPR_UGQR0 + gqrIndex); IMLReg loadTypeReg = PPCRecompilerImlGen_loadRegister(ppcImlGenContext, PPCREC_NAME_TEMPORARY + 0); // extract the load type from the GQR register - ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_RIGHT_SHIFT_U, loadTypeReg, gqrRegister, 16); - ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_AND, loadTypeReg, loadTypeReg, 0x7); + ppcImlGenContext->emitInst().make_r_r_s32(PPCREC_IML_OP_AND, loadTypeReg, gqrRegister, 0x7); IMLSegment* caseSegment[5]; sint32 compareValues[5] = {0, 4, 5, 6, 7}; @@ -937,7 +982,7 @@ bool PPCRecompilerImlGen_PSQ_ST(ppcImlGenContext_t* ppcImlGenContext, uint32 opc for (sint32 i=0; i<5; i++) { IMLRedirectInstOutput outputToCase(ppcImlGenContext, caseSegment[i]); // while this is in scope, instructions go to caseSegment[i] - PPCRecompilerImlGen_EmitPSQStoreCase(ppcImlGenContext, static_cast<Espresso::PSQ_LOAD_TYPE>(compareValues[i]), storePS1, gprA, imm, fprDPS0, fprDPS1); + PPCRecompilerImlGen_EmitPSQStoreCase(ppcImlGenContext, gqrIndex, static_cast<Espresso::PSQ_LOAD_TYPE>(compareValues[i]), storePS1, gprA, imm, fprDPS0, fprDPS1); ppcImlGenContext->emitInst().make_jump(); // finalize case } return true; @@ -954,7 +999,7 @@ bool PPCRecompilerImlGen_PSQ_ST(ppcImlGenContext_t* ppcImlGenContext, uint32 opc return false; } - PPCRecompilerImlGen_EmitPSQStoreCase(ppcImlGenContext, type, storePS1, gprA, imm, fprDPS0, fprDPS1); + PPCRecompilerImlGen_EmitPSQStoreCase(ppcImlGenContext, gqrIndex, type, storePS1, gprA, imm, fprDPS0, fprDPS1); return true; } From ba09daf32853407da1303f0546a9538162de7680 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Fri, 9 May 2025 02:06:08 +0200 Subject: [PATCH 258/299] PPCRec: Reenable float copy optimization --- .../HW/Espresso/Recompiler/IML/IMLDebug.cpp | 35 ++++++++- .../Espresso/Recompiler/IML/IMLOptimizer.cpp | 72 +++++++++++++------ .../HW/Espresso/Recompiler/PPCRecompiler.cpp | 39 ---------- .../HW/Espresso/Recompiler/PPCRecompiler.h | 5 -- 4 files changed, 84 insertions(+), 67 deletions(-) diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IMLDebug.cpp b/src/Cafe/HW/Espresso/Recompiler/IML/IMLDebug.cpp index f736c2a7..cd269869 100644 --- a/src/Cafe/HW/Espresso/Recompiler/IML/IMLDebug.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IMLDebug.cpp @@ -36,6 +36,30 @@ const char* IMLDebug_GetOpcodeName(const IMLInstruction* iml) return "MULS"; else if (op == PPCREC_IML_OP_DIVIDE_SIGNED) return "DIVS"; + else if (op == PPCREC_IML_OP_FPR_ASSIGN) + return "FMOV"; + else if (op == PPCREC_IML_OP_FPR_ADD) + return "FADD"; + else if (op == PPCREC_IML_OP_FPR_SUB) + return "FSUB"; + else if (op == PPCREC_IML_OP_FPR_MULTIPLY) + return "FMUL"; + else if (op == PPCREC_IML_OP_FPR_DIVIDE) + return "FDIV"; + else if (op == PPCREC_IML_OP_FPR_EXPAND_F32_TO_F64) + return "F32TOF64"; + else if (op == PPCREC_IML_OP_FPR_ABS) + return "FABS"; + else if (op == PPCREC_IML_OP_FPR_NEGATE) + return "FNEG"; + else if (op == PPCREC_IML_OP_FPR_NEGATIVE_ABS) + return "FNABS"; + else if (op == PPCREC_IML_OP_FPR_FLOAT_TO_INT) + return "F2I"; + else if (op == PPCREC_IML_OP_FPR_INT_TO_FLOAT) + return "I2F"; + else if (op == PPCREC_IML_OP_FPR_BITCAST_INT_TO_FLOAT) + return "BITMOVE"; sprintf(_tempOpcodename, "OP0%02x_T%d", iml->operation, iml->type); return _tempOpcodename; @@ -409,19 +433,24 @@ void IMLDebug_DisassembleInstruction(const IMLInstruction& inst, std::string& di strOutput.addFmt("{} [t{}+{}]", inst.op_storeLoad.copyWidth / 8, inst.op_storeLoad.registerMem.GetRegID(), inst.op_storeLoad.immS32); strOutput.addFmt(" = {} mode {}", IMLDebug_GetRegName(inst.op_storeLoad.registerData), inst.op_storeLoad.mode); } + else if (inst.type == PPCREC_IML_TYPE_FPR_R) + { + strOutput.addFmt("{:<6} ", IMLDebug_GetOpcodeName(&inst)); + strOutput.addFmt("{}", IMLDebug_GetRegName(inst.op_fpr_r.regR)); + } else if (inst.type == PPCREC_IML_TYPE_FPR_R_R) { - strOutput.addFmt("{:>6} ", IMLDebug_GetOpcodeName(&inst)); + strOutput.addFmt("{:<6} ", IMLDebug_GetOpcodeName(&inst)); strOutput.addFmt("{}, {}", IMLDebug_GetRegName(inst.op_fpr_r_r.regR), IMLDebug_GetRegName(inst.op_fpr_r_r.regA)); } else if (inst.type == PPCREC_IML_TYPE_FPR_R_R_R_R) { - strOutput.addFmt("{:>6} ", IMLDebug_GetOpcodeName(&inst)); + strOutput.addFmt("{:<6} ", IMLDebug_GetOpcodeName(&inst)); strOutput.addFmt("{}, {}, {}, {}", IMLDebug_GetRegName(inst.op_fpr_r_r_r_r.regR), IMLDebug_GetRegName(inst.op_fpr_r_r_r_r.regA), IMLDebug_GetRegName(inst.op_fpr_r_r_r_r.regB), IMLDebug_GetRegName(inst.op_fpr_r_r_r_r.regC)); } else if (inst.type == PPCREC_IML_TYPE_FPR_R_R_R) { - strOutput.addFmt("{:>6} ", IMLDebug_GetOpcodeName(&inst)); + strOutput.addFmt("{:<6} ", IMLDebug_GetOpcodeName(&inst)); strOutput.addFmt("{}, {}, {}", IMLDebug_GetRegName(inst.op_fpr_r_r_r.regR), IMLDebug_GetRegName(inst.op_fpr_r_r_r.regA), IMLDebug_GetRegName(inst.op_fpr_r_r_r.regB)); } else if (inst.type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK) diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IMLOptimizer.cpp b/src/Cafe/HW/Espresso/Recompiler/IML/IMLOptimizer.cpp index d0348e5a..d5693846 100644 --- a/src/Cafe/HW/Espresso/Recompiler/IML/IMLOptimizer.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IMLOptimizer.cpp @@ -23,7 +23,7 @@ void PPCRecompiler_optimizeDirectFloatCopiesScanForward(ppcImlGenContext_t* ppcI IMLInstruction* imlInstructionLoad = imlSegment->imlList.data() + imlIndexLoad; if (imlInstructionLoad->op_storeLoad.flags2.notExpanded) return; - + boost::container::static_vector<sint32, 4> trackedMoves; // only track up to 4 copies IMLUsedRegisters registersUsed; sint32 scanRangeEnd = std::min<sint32>(imlIndexLoad + 25, imlSegment->imlList.size()); // don't scan too far (saves performance and also the chances we can merge the load+store become low at high distances) bool foundMatch = false; @@ -54,8 +54,24 @@ void PPCRecompiler_optimizeDirectFloatCopiesScanForward(ppcImlGenContext_t* ppcI continue; } } - - // check if FPR is overwritten (we can actually ignore read operations?) + // if the FPR is copied then keep track of it. We can expand the copies instead of the original + if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R && imlInstruction->operation == PPCREC_IML_OP_FPR_ASSIGN && imlInstruction->op_fpr_r_r.regA.GetRegID() == fprIndex) + { + if (imlInstruction->op_fpr_r_r.regR.GetRegID() == fprIndex) + { + // unexpected no-op + break; + } + if (trackedMoves.size() >= trackedMoves.capacity()) + { + // we cant track any more moves, expand here + lastStore = i; + break; + } + trackedMoves.push_back(i); + continue; + } + // check if FPR is overwritten imlInstruction->CheckRegisterUsage(®istersUsed); if (registersUsed.writtenGPR1.IsValidAndSameRegID(fprIndex) || registersUsed.writtenGPR2.IsValidAndSameRegID(fprIndex)) break; @@ -71,6 +87,24 @@ void PPCRecompiler_optimizeDirectFloatCopiesScanForward(ppcImlGenContext_t* ppcI if (foundMatch) { + // insert expand instructions for each target register of a move + sint32 positionBias = 0; + for (auto& trackedMove : trackedMoves) + { + sint32 realPosition = trackedMove + positionBias; + IMLInstruction* imlMoveInstruction = imlSegment->imlList.data() + realPosition; + if (realPosition >= lastStore) + break; // expand is inserted before this move + else + lastStore++; + + cemu_assert_debug(imlMoveInstruction->type == PPCREC_IML_TYPE_FPR_R_R && imlMoveInstruction->op_fpr_r_r.regA.GetRegID() == fprIndex); + cemu_assert_debug(imlMoveInstruction->op_fpr_r_r.regA.GetRegFormat() == IMLRegFormat::F64); + auto dstReg = imlMoveInstruction->op_fpr_r_r.regR; + IMLInstruction* newExpand = PPCRecompiler_insertInstruction(imlSegment, realPosition+1); // one after the move + newExpand->make_fpr_r(PPCREC_IML_OP_FPR_EXPAND_F32_TO_F64, dstReg); + positionBias++; + } // insert expand instruction after store IMLInstruction* newExpand = PPCRecompiler_insertInstruction(imlSegment, lastStore); newExpand->make_fpr_r(PPCREC_IML_OP_FPR_EXPAND_F32_TO_F64, _FPRRegFromID(fprIndex)); @@ -90,23 +124,21 @@ void PPCRecompiler_optimizeDirectFloatCopiesScanForward(ppcImlGenContext_t* ppcI */ void IMLOptimizer_OptimizeDirectFloatCopies(ppcImlGenContext_t* ppcImlGenContext) { - cemuLog_logDebugOnce(LogType::Force, "IMLOptimizer_OptimizeDirectFloatCopies(): Currently disabled\n"); - return; - // for (IMLSegment* segIt : ppcImlGenContext->segmentList2) - // { - // for (sint32 i = 0; i < segIt->imlList.size(); i++) - // { - // IMLInstruction* imlInstruction = segIt->imlList.data() + i; - // if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD && imlInstruction->op_storeLoad.mode == PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1) - // { - // PPCRecompiler_optimizeDirectFloatCopiesScanForward(ppcImlGenContext, segIt, i, imlInstruction->op_storeLoad.registerData); - // } - // else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED && imlInstruction->op_storeLoad.mode == PPCREC_FPR_LD_MODE_SINGLE_INTO_PS0_PS1) - // { - // PPCRecompiler_optimizeDirectFloatCopiesScanForward(ppcImlGenContext, segIt, i, imlInstruction->op_storeLoad.registerData); - // } - // } - // } + for (IMLSegment* segIt : ppcImlGenContext->segmentList2) + { + for (sint32 i = 0; i < segIt->imlList.size(); i++) + { + IMLInstruction* imlInstruction = segIt->imlList.data() + i; + if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD && imlInstruction->op_storeLoad.mode == PPCREC_FPR_LD_MODE_SINGLE) + { + PPCRecompiler_optimizeDirectFloatCopiesScanForward(ppcImlGenContext, segIt, i, imlInstruction->op_storeLoad.registerData); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED && imlInstruction->op_storeLoad.mode == PPCREC_FPR_LD_MODE_SINGLE) + { + PPCRecompiler_optimizeDirectFloatCopiesScanForward(ppcImlGenContext, segIt, i, imlInstruction->op_storeLoad.registerData); + } + } + } } void PPCRecompiler_optimizeDirectIntegerCopiesScanForward(ppcImlGenContext_t* ppcImlGenContext, IMLSegment* imlSegment, sint32 imlIndexLoad, IMLReg gprReg) diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp index 0dbc073b..087b90f5 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp @@ -685,45 +685,6 @@ void PPCRecompiler_init() PPCRecompiler_allocateRange(mmuRange_TRAMPOLINE_AREA.getBase(), mmuRange_TRAMPOLINE_AREA.getSize()); PPCRecompiler_allocateRange(mmuRange_CODECAVE.getBase(), mmuRange_CODECAVE.getSize()); - // setup GQR scale tables - - for (uint32 i = 0; i < 32; i++) - { - float a = 1.0f / (float)(1u << i); - float b = 0; - if (i == 0) - b = 4294967296.0f; - else - b = (float)(1u << (32u - i)); - - float ar = (float)(1u << i); - float br = 0; - if (i == 0) - br = 1.0f / 4294967296.0f; - else - br = 1.0f / (float)(1u << (32u - i)); - - ppcRecompilerInstanceData->_psq_ld_scale_ps0_1[i * 2 + 0] = a; - ppcRecompilerInstanceData->_psq_ld_scale_ps0_1[i * 2 + 1] = 1.0f; - ppcRecompilerInstanceData->_psq_ld_scale_ps0_1[(i + 32) * 2 + 0] = b; - ppcRecompilerInstanceData->_psq_ld_scale_ps0_1[(i + 32) * 2 + 1] = 1.0f; - - ppcRecompilerInstanceData->_psq_ld_scale_ps0_ps1[i * 2 + 0] = a; - ppcRecompilerInstanceData->_psq_ld_scale_ps0_ps1[i * 2 + 1] = a; - ppcRecompilerInstanceData->_psq_ld_scale_ps0_ps1[(i + 32) * 2 + 0] = b; - ppcRecompilerInstanceData->_psq_ld_scale_ps0_ps1[(i + 32) * 2 + 1] = b; - - ppcRecompilerInstanceData->_psq_st_scale_ps0_1[i * 2 + 0] = ar; - ppcRecompilerInstanceData->_psq_st_scale_ps0_1[i * 2 + 1] = 1.0f; - ppcRecompilerInstanceData->_psq_st_scale_ps0_1[(i + 32) * 2 + 0] = br; - ppcRecompilerInstanceData->_psq_st_scale_ps0_1[(i + 32) * 2 + 1] = 1.0f; - - ppcRecompilerInstanceData->_psq_st_scale_ps0_ps1[i * 2 + 0] = ar; - ppcRecompilerInstanceData->_psq_st_scale_ps0_ps1[i * 2 + 1] = ar; - ppcRecompilerInstanceData->_psq_st_scale_ps0_ps1[(i + 32) * 2 + 0] = br; - ppcRecompilerInstanceData->_psq_st_scale_ps0_ps1[(i + 32) * 2 + 1] = br; - } - PPCRecompiler_initPlatform(); cemuLog_log(LogType::Force, "Recompiler initialized"); diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.h b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.h index 706855d4..47902630 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.h +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.h @@ -136,11 +136,6 @@ typedef struct alignas(16) float _x64XMM_constFloatMin[2]; alignas(16) uint32 _x64XMM_flushDenormalMask1[4]; alignas(16) uint32 _x64XMM_flushDenormalMaskResetSignBits[4]; - // PSQ load/store scale tables - double _psq_ld_scale_ps0_ps1[64 * 2]; - double _psq_ld_scale_ps0_1[64 * 2]; - double _psq_st_scale_ps0_ps1[64 * 2]; - double _psq_st_scale_ps0_1[64 * 2]; // MXCSR uint32 _x64XMM_mxCsr_ftzOn; uint32 _x64XMM_mxCsr_ftzOff; From d13dab0fd8a0a3eb4b1d72e57888ba0c4d778574 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Fri, 9 May 2025 10:00:38 +0200 Subject: [PATCH 259/299] Vulkan: During shutdown submit buffer before deleting resources --- src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index bfe21bcc..a88c3818 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -1652,10 +1652,10 @@ void VulkanRenderer::Initialize() void VulkanRenderer::Shutdown() { - DeleteFontTextures(); - Renderer::Shutdown(); SubmitCommandBuffer(); WaitDeviceIdle(); + DeleteFontTextures(); + Renderer::Shutdown(); if (m_imguiRenderPass != VK_NULL_HANDLE) { vkDestroyRenderPass(m_logicalDevice, m_imguiRenderPass, nullptr); From 081ebead5f32fca99eb9a9116abf8c2e77f70e7d Mon Sep 17 00:00:00 2001 From: SSimco <37044560+SSimco@users.noreply.github.com> Date: Fri, 9 May 2025 13:47:22 +0300 Subject: [PATCH 260/299] Add AArch64 recompiler backend (#1556) --- .gitmodules | 3 + CMakeLists.txt | 4 + dependencies/xbyak_aarch64 | 1 + src/Cafe/CMakeLists.txt | 8 + .../BackendAArch64/BackendAArch64.cpp | 1693 +++++++++++++++++ .../BackendAArch64/BackendAArch64.h | 18 + .../Espresso/Recompiler/IML/IMLOptimizer.cpp | 2 + .../Recompiler/IML/IMLRegisterAllocator.cpp | 14 +- .../HW/Espresso/Recompiler/PPCRecompiler.cpp | 30 +- 9 files changed, 1766 insertions(+), 7 deletions(-) create mode 160000 dependencies/xbyak_aarch64 create mode 100644 src/Cafe/HW/Espresso/Recompiler/BackendAArch64/BackendAArch64.cpp create mode 100644 src/Cafe/HW/Espresso/Recompiler/BackendAArch64/BackendAArch64.h diff --git a/.gitmodules b/.gitmodules index dc69c441..8f9772d3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -18,3 +18,6 @@ path = dependencies/imgui url = https://github.com/ocornut/imgui shallow = true +[submodule "dependencies/xbyak_aarch64"] + path = dependencies/xbyak_aarch64 + url = https://github.com/fujitsu/xbyak_aarch64 diff --git a/CMakeLists.txt b/CMakeLists.txt index 560728f2..eb848ce7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -222,6 +222,10 @@ endif() add_subdirectory("dependencies/ih264d" EXCLUDE_FROM_ALL) +if(CMAKE_SYSTEM_PROCESSOR MATCHES "(aarch64)|(AARCH64)") + add_subdirectory("dependencies/xbyak_aarch64" EXCLUDE_FROM_ALL) +endif() + find_package(ZArchive) if (NOT ZArchive_FOUND) add_subdirectory("dependencies/ZArchive" EXCLUDE_FROM_ALL) diff --git a/dependencies/xbyak_aarch64 b/dependencies/xbyak_aarch64 new file mode 160000 index 00000000..904b8923 --- /dev/null +++ b/dependencies/xbyak_aarch64 @@ -0,0 +1 @@ +Subproject commit 904b8923457f3ec0d6f82ea2d6832a792851194d diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index f4834260..71866b21 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -537,6 +537,14 @@ if(APPLE) target_sources(CemuCafe PRIVATE "HW/Latte/Renderer/Vulkan/CocoaSurface.mm") endif() +if(CMAKE_SYSTEM_PROCESSOR MATCHES "(aarch64)|(AARCH64)") + target_sources(CemuCafe PRIVATE + HW/Espresso/Recompiler/BackendAArch64/BackendAArch64.cpp + HW/Espresso/Recompiler/BackendAArch64/BackendAArch64.h + ) + target_link_libraries(CemuCafe PRIVATE xbyak_aarch64) +endif() + set_property(TARGET CemuCafe PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>") target_include_directories(CemuCafe PUBLIC "../") diff --git a/src/Cafe/HW/Espresso/Recompiler/BackendAArch64/BackendAArch64.cpp b/src/Cafe/HW/Espresso/Recompiler/BackendAArch64/BackendAArch64.cpp new file mode 100644 index 00000000..cb71234d --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/BackendAArch64/BackendAArch64.cpp @@ -0,0 +1,1693 @@ +#include "BackendAArch64.h" + +#pragma push_macro("CSIZE") +#undef CSIZE +#include <xbyak_aarch64.h> +#pragma pop_macro("CSIZE") +#include <xbyak_aarch64_util.h> + +#include <cstddef> + +#include "../PPCRecompiler.h" +#include "Common/precompiled.h" +#include "Common/cpu_features.h" +#include "HW/Espresso/Interpreter/PPCInterpreterInternal.h" +#include "HW/Espresso/Interpreter/PPCInterpreterHelper.h" +#include "HW/Espresso/PPCState.h" + +using namespace Xbyak_aarch64; + +constexpr uint32 TEMP_GPR_1_ID = 25; +constexpr uint32 TEMP_GPR_2_ID = 26; +constexpr uint32 PPC_RECOMPILER_INSTANCE_DATA_REG_ID = 27; +constexpr uint32 MEMORY_BASE_REG_ID = 28; +constexpr uint32 HCPU_REG_ID = 29; + +constexpr uint32 TEMP_FPR_ID = 31; + +struct FPReg +{ + explicit FPReg(size_t index) + : index(index), VReg(index), QReg(index), DReg(index), SReg(index), HReg(index), BReg(index) + { + } + const size_t index; + const VReg VReg; + const QReg QReg; + const DReg DReg; + const SReg SReg; + const HReg HReg; + const BReg BReg; +}; + +struct GPReg +{ + explicit GPReg(size_t index) + : index(index), XReg(index), WReg(index) + { + } + const size_t index; + const XReg XReg; + const WReg WReg; +}; + +static const XReg HCPU_REG{HCPU_REG_ID}, PPC_REC_INSTANCE_REG{PPC_RECOMPILER_INSTANCE_DATA_REG_ID}, MEM_BASE_REG{MEMORY_BASE_REG_ID}; +static const GPReg TEMP_GPR1{TEMP_GPR_1_ID}; +static const GPReg TEMP_GPR2{TEMP_GPR_2_ID}; +static const GPReg LR{TEMP_GPR_2_ID}; + +static const FPReg TEMP_FPR{TEMP_FPR_ID}; + +static const util::Cpu s_cpu; + +class AArch64Allocator : public Allocator +{ + private: +#ifdef XBYAK_USE_MMAP_ALLOCATOR + inline static MmapAllocator s_allocator; +#else + inline static Allocator s_allocator; +#endif + Allocator* m_allocatorImpl; + bool m_freeDisabled = false; + + public: + AArch64Allocator() + : m_allocatorImpl(reinterpret_cast<Allocator*>(&s_allocator)) {} + + uint32* alloc(size_t size) override + { + return m_allocatorImpl->alloc(size); + } + + void setFreeDisabled(bool disabled) + { + m_freeDisabled = disabled; + } + + void free(uint32* p) override + { + if (!m_freeDisabled) + m_allocatorImpl->free(p); + } + + [[nodiscard]] bool useProtect() const override + { + return !m_freeDisabled && m_allocatorImpl->useProtect(); + } +}; + +struct UnconditionalJumpInfo +{ + IMLSegment* target; +}; + +struct ConditionalRegJumpInfo +{ + IMLSegment* target; + WReg regBool; + bool mustBeTrue; +}; + +struct NegativeRegValueJumpInfo +{ + IMLSegment* target; + WReg regValue; +}; + +using JumpInfo = std::variant< + UnconditionalJumpInfo, + ConditionalRegJumpInfo, + NegativeRegValueJumpInfo>; + +struct AArch64GenContext_t : CodeGenerator +{ + explicit AArch64GenContext_t(Allocator* allocator = nullptr); + void enterRecompilerCode(); + void leaveRecompilerCode(); + + void r_name(IMLInstruction* imlInstruction); + void name_r(IMLInstruction* imlInstruction); + bool r_s32(IMLInstruction* imlInstruction); + bool r_r(IMLInstruction* imlInstruction); + bool r_r_s32(IMLInstruction* imlInstruction); + bool r_r_s32_carry(IMLInstruction* imlInstruction); + bool r_r_r(IMLInstruction* imlInstruction); + bool r_r_r_carry(IMLInstruction* imlInstruction); + void compare(IMLInstruction* imlInstruction); + void compare_s32(IMLInstruction* imlInstruction); + bool load(IMLInstruction* imlInstruction, bool indexed); + bool store(IMLInstruction* imlInstruction, bool indexed); + void atomic_cmp_store(IMLInstruction* imlInstruction); + bool macro(IMLInstruction* imlInstruction); + void call_imm(IMLInstruction* imlInstruction); + bool fpr_load(IMLInstruction* imlInstruction, bool indexed); + bool fpr_store(IMLInstruction* imlInstruction, bool indexed); + void fpr_r_r(IMLInstruction* imlInstruction); + void fpr_r_r_r(IMLInstruction* imlInstruction); + void fpr_r_r_r_r(IMLInstruction* imlInstruction); + void fpr_r(IMLInstruction* imlInstruction); + void fpr_compare(IMLInstruction* imlInstruction); + void cjump(IMLInstruction* imlInstruction, IMLSegment* imlSegment); + void jump(IMLSegment* imlSegment); + void conditionalJumpCycleCheck(IMLSegment* imlSegment); + + static constexpr size_t MAX_JUMP_INSTR_COUNT = 2; + std::list<std::pair<size_t, JumpInfo>> jumps; + void prepareJump(JumpInfo&& jumpInfo) + { + jumps.emplace_back(getSize(), jumpInfo); + for (int i = 0; i < MAX_JUMP_INSTR_COUNT; ++i) + nop(); + } + + std::map<IMLSegment*, size_t> segmentStarts; + void storeSegmentStart(IMLSegment* imlSegment) + { + segmentStarts[imlSegment] = getSize(); + } + + bool processAllJumps() + { + for (auto&& [jumpStart, jumpInfo] : jumps) + { + bool success = std::visit( + [&, this](const auto& jump) { + setSize(jumpStart); + sint64 targetAddress = segmentStarts.at(jump.target); + sint64 addressOffset = targetAddress - jumpStart; + return handleJump(addressOffset, jump); + }, + jumpInfo); + if (!success) + { + return false; + } + } + return true; + } + + bool handleJump(sint64 addressOffset, const UnconditionalJumpInfo& jump) + { + // in +/-128MB + if (-0x8000000 <= addressOffset && addressOffset <= 0x7ffffff) + { + b(addressOffset); + return true; + } + + cemu_assert_suspicious(); + + return false; + } + + bool handleJump(sint64 addressOffset, const ConditionalRegJumpInfo& jump) + { + bool mustBeTrue = jump.mustBeTrue; + + // in +/-32KB + if (-0x8000 <= addressOffset && addressOffset <= 0x7fff) + { + if (mustBeTrue) + tbnz(jump.regBool, 0, addressOffset); + else + tbz(jump.regBool, 0, addressOffset); + return true; + } + + // in +/-1MB + if (-0x100000 <= addressOffset && addressOffset <= 0xfffff) + { + if (mustBeTrue) + cbnz(jump.regBool, addressOffset); + else + cbz(jump.regBool, addressOffset); + return true; + } + + Label skipJump; + if (mustBeTrue) + tbz(jump.regBool, 0, skipJump); + else + tbnz(jump.regBool, 0, skipJump); + addressOffset -= 4; + + // in +/-128MB + if (-0x8000000 <= addressOffset && addressOffset <= 0x7ffffff) + { + b(addressOffset); + L(skipJump); + return true; + } + + cemu_assert_suspicious(); + + return false; + } + + bool handleJump(sint64 addressOffset, const NegativeRegValueJumpInfo& jump) + { + // in +/-32KB + if (-0x8000 <= addressOffset && addressOffset <= 0x7fff) + { + tbnz(jump.regValue, 31, addressOffset); + return true; + } + + // in +/-1MB + if (-0x100000 <= addressOffset && addressOffset <= 0xfffff) + { + tst(jump.regValue, 0x80000000); + addressOffset -= 4; + bne(addressOffset); + return true; + } + + Label skipJump; + tbz(jump.regValue, 31, skipJump); + addressOffset -= 4; + + // in +/-128MB + if (-0x8000000 <= addressOffset && addressOffset <= 0x7ffffff) + { + b(addressOffset); + L(skipJump); + return true; + } + + cemu_assert_suspicious(); + + return false; + } +}; + +template<std::derived_from<VRegSc> T> +T fpReg(const IMLReg& imlReg) +{ + cemu_assert_debug(imlReg.GetRegFormat() == IMLRegFormat::F64); + auto regId = imlReg.GetRegID(); + cemu_assert_debug(regId >= IMLArchAArch64::PHYSREG_FPR_BASE && regId < IMLArchAArch64::PHYSREG_FPR_BASE + IMLArchAArch64::PHYSREG_FPR_COUNT); + return T(regId - IMLArchAArch64::PHYSREG_FPR_BASE); +} + +template<std::derived_from<RReg> T> +T gpReg(const IMLReg& imlReg) +{ + auto regFormat = imlReg.GetRegFormat(); + if (std::is_same_v<T, WReg>) + cemu_assert_debug(regFormat == IMLRegFormat::I32); + else if (std::is_same_v<T, XReg>) + cemu_assert_debug(regFormat == IMLRegFormat::I64); + else + cemu_assert_unimplemented(); + + auto regId = imlReg.GetRegID(); + cemu_assert_debug(regId >= IMLArchAArch64::PHYSREG_GPR_BASE && regId < IMLArchAArch64::PHYSREG_GPR_BASE + IMLArchAArch64::PHYSREG_GPR_COUNT); + return T(regId - IMLArchAArch64::PHYSREG_GPR_BASE); +} + +template<std::derived_from<VRegSc> To, std::derived_from<VRegSc> From> +To aliasAs(const From& reg) +{ + return To(reg.getIdx()); +} + +template<std::derived_from<RReg> To, std::derived_from<RReg> From> +To aliasAs(const From& reg) +{ + return To(reg.getIdx()); +} + +AArch64GenContext_t::AArch64GenContext_t(Allocator* allocator) + : CodeGenerator(DEFAULT_MAX_CODE_SIZE, AutoGrow, allocator) +{ +} + +constexpr uint64 ones(uint32 size) +{ + return (size == 64) ? 0xffffffffffffffff : ((uint64)1 << size) - 1; +} + +constexpr bool isAdrImmValidFPR(sint32 imm, uint32 bits) +{ + uint32 times = bits / 8; + uint32 sh = std::countr_zero(times); + return (0 <= imm && imm <= 4095 * times) && ((uint64)imm & ones(sh)) == 0; +} + +constexpr bool isAdrImmValidGPR(sint32 imm, uint32 bits = 32) +{ + uint32 size = std::countr_zero(bits / 8u); + sint32 times = 1 << size; + return (0 <= imm && imm <= 4095 * times) && ((uint64)imm & ones(size)) == 0; +} + +constexpr bool isAdrImmRangeValid(sint32 rangeStart, sint32 rangeOffset, sint32 bits, std::invocable<sint32, uint32> auto check) +{ + for (sint32 i = rangeStart; i <= rangeStart + rangeOffset; i += bits / 8) + if (!check(i, bits)) + return false; + return true; +} + +constexpr bool isAdrImmRangeValidGPR(sint32 rangeStart, sint32 rangeOffset, sint32 bits = 32) +{ + return isAdrImmRangeValid(rangeStart, rangeOffset, bits, isAdrImmValidGPR); +} + +constexpr bool isAdrImmRangeValidFpr(sint32 rangeStart, sint32 rangeOffset, sint32 bits) +{ + return isAdrImmRangeValid(rangeStart, rangeOffset, bits, isAdrImmValidFPR); +} + +// Verify that all of the offsets for the PPCInterpreter_t members that we use in r_name/name_r have a valid imm value for AdrUimm +static_assert(isAdrImmRangeValidGPR(offsetof(PPCInterpreter_t, gpr), sizeof(uint32) * 31)); +static_assert(isAdrImmValidGPR(offsetof(PPCInterpreter_t, spr.LR))); +static_assert(isAdrImmValidGPR(offsetof(PPCInterpreter_t, spr.CTR))); +static_assert(isAdrImmValidGPR(offsetof(PPCInterpreter_t, spr.XER))); +static_assert(isAdrImmRangeValidGPR(offsetof(PPCInterpreter_t, spr.UGQR), sizeof(PPCInterpreter_t::spr.UGQR[0]) * (SPR_UGQR7 - SPR_UGQR0))); +static_assert(isAdrImmRangeValidGPR(offsetof(PPCInterpreter_t, temporaryGPR_reg), sizeof(uint32) * 3)); +static_assert(isAdrImmValidGPR(offsetof(PPCInterpreter_t, xer_ca), 8)); +static_assert(isAdrImmValidGPR(offsetof(PPCInterpreter_t, xer_so), 8)); +static_assert(isAdrImmRangeValidGPR(offsetof(PPCInterpreter_t, cr), PPCREC_NAME_CR_LAST - PPCREC_NAME_CR, 8)); +static_assert(isAdrImmValidGPR(offsetof(PPCInterpreter_t, reservedMemAddr))); +static_assert(isAdrImmValidGPR(offsetof(PPCInterpreter_t, reservedMemValue))); +static_assert(isAdrImmRangeValidFpr(offsetof(PPCInterpreter_t, fpr), sizeof(FPR_t) * 63, 64)); +static_assert(isAdrImmRangeValidFpr(offsetof(PPCInterpreter_t, temporaryFPR), sizeof(FPR_t) * 7, 128)); + +void AArch64GenContext_t::r_name(IMLInstruction* imlInstruction) +{ + uint32 name = imlInstruction->op_r_name.name; + + if (imlInstruction->op_r_name.regR.GetBaseFormat() == IMLRegFormat::I64) + { + XReg regRXReg = gpReg<XReg>(imlInstruction->op_r_name.regR); + WReg regR = aliasAs<WReg>(regRXReg); + if (name >= PPCREC_NAME_R0 && name < PPCREC_NAME_R0 + 32) + { + ldr(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, gpr) + sizeof(uint32) * (name - PPCREC_NAME_R0))); + } + else if (name >= PPCREC_NAME_SPR0 && name < PPCREC_NAME_SPR0 + 999) + { + uint32 sprIndex = (name - PPCREC_NAME_SPR0); + if (sprIndex == SPR_LR) + ldr(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, spr.LR))); + else if (sprIndex == SPR_CTR) + ldr(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, spr.CTR))); + else if (sprIndex == SPR_XER) + ldr(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, spr.XER))); + else if (sprIndex >= SPR_UGQR0 && sprIndex <= SPR_UGQR7) + ldr(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, spr.UGQR) + sizeof(PPCInterpreter_t::spr.UGQR[0]) * (sprIndex - SPR_UGQR0))); + else + cemu_assert_suspicious(); + } + else if (name >= PPCREC_NAME_TEMPORARY && name < PPCREC_NAME_TEMPORARY + 4) + { + ldr(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, temporaryGPR_reg) + sizeof(uint32) * (name - PPCREC_NAME_TEMPORARY))); + } + else if (name == PPCREC_NAME_XER_CA) + { + ldrb(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, xer_ca))); + } + else if (name == PPCREC_NAME_XER_SO) + { + ldrb(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, xer_so))); + } + else if (name >= PPCREC_NAME_CR && name <= PPCREC_NAME_CR_LAST) + { + ldrb(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, cr) + (name - PPCREC_NAME_CR))); + } + else if (name == PPCREC_NAME_CPU_MEMRES_EA) + { + ldr(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, reservedMemAddr))); + } + else if (name == PPCREC_NAME_CPU_MEMRES_VAL) + { + ldr(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, reservedMemValue))); + } + else + { + cemu_assert_suspicious(); + } + } + else if (imlInstruction->op_r_name.regR.GetBaseFormat() == IMLRegFormat::F64) + { + auto imlRegR = imlInstruction->op_r_name.regR; + + if (name >= PPCREC_NAME_FPR_HALF && name < (PPCREC_NAME_FPR_HALF + 64)) + { + uint32 regIndex = (name - PPCREC_NAME_FPR_HALF) / 2; + uint32 pairIndex = (name - PPCREC_NAME_FPR_HALF) % 2; + uint32 offset = offsetof(PPCInterpreter_t, fpr) + sizeof(FPR_t) * regIndex + (pairIndex ? sizeof(double) : 0); + ldr(fpReg<DReg>(imlRegR), AdrUimm(HCPU_REG, offset)); + } + else if (name >= PPCREC_NAME_TEMPORARY_FPR0 && name < (PPCREC_NAME_TEMPORARY_FPR0 + 8)) + { + ldr(fpReg<QReg>(imlRegR), AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, temporaryFPR) + sizeof(FPR_t) * (name - PPCREC_NAME_TEMPORARY_FPR0))); + } + else + { + cemu_assert_suspicious(); + } + } + else + { + cemu_assert_suspicious(); + } +} + +void AArch64GenContext_t::name_r(IMLInstruction* imlInstruction) +{ + uint32 name = imlInstruction->op_r_name.name; + + if (imlInstruction->op_r_name.regR.GetBaseFormat() == IMLRegFormat::I64) + { + XReg regRXReg = gpReg<XReg>(imlInstruction->op_r_name.regR); + WReg regR = aliasAs<WReg>(regRXReg); + if (name >= PPCREC_NAME_R0 && name < PPCREC_NAME_R0 + 32) + { + str(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, gpr) + sizeof(uint32) * (name - PPCREC_NAME_R0))); + } + else if (name >= PPCREC_NAME_SPR0 && name < PPCREC_NAME_SPR0 + 999) + { + uint32 sprIndex = (name - PPCREC_NAME_SPR0); + if (sprIndex == SPR_LR) + str(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, spr.LR))); + else if (sprIndex == SPR_CTR) + str(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, spr.CTR))); + else if (sprIndex == SPR_XER) + str(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, spr.XER))); + else if (sprIndex >= SPR_UGQR0 && sprIndex <= SPR_UGQR7) + str(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, spr.UGQR) + sizeof(PPCInterpreter_t::spr.UGQR[0]) * (sprIndex - SPR_UGQR0))); + else + cemu_assert_suspicious(); + } + else if (name >= PPCREC_NAME_TEMPORARY && name < PPCREC_NAME_TEMPORARY + 4) + { + str(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, temporaryGPR_reg) + sizeof(uint32) * (name - PPCREC_NAME_TEMPORARY))); + } + else if (name == PPCREC_NAME_XER_CA) + { + strb(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, xer_ca))); + } + else if (name == PPCREC_NAME_XER_SO) + { + strb(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, xer_so))); + } + else if (name >= PPCREC_NAME_CR && name <= PPCREC_NAME_CR_LAST) + { + strb(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, cr) + (name - PPCREC_NAME_CR))); + } + else if (name == PPCREC_NAME_CPU_MEMRES_EA) + { + str(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, reservedMemAddr))); + } + else if (name == PPCREC_NAME_CPU_MEMRES_VAL) + { + str(regR, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, reservedMemValue))); + } + else + { + cemu_assert_suspicious(); + } + } + else if (imlInstruction->op_r_name.regR.GetBaseFormat() == IMLRegFormat::F64) + { + auto imlRegR = imlInstruction->op_r_name.regR; + if (name >= PPCREC_NAME_FPR_HALF && name < (PPCREC_NAME_FPR_HALF + 64)) + { + uint32 regIndex = (name - PPCREC_NAME_FPR_HALF) / 2; + uint32 pairIndex = (name - PPCREC_NAME_FPR_HALF) % 2; + sint32 offset = offsetof(PPCInterpreter_t, fpr) + sizeof(FPR_t) * regIndex + pairIndex * sizeof(double); + str(fpReg<DReg>(imlRegR), AdrUimm(HCPU_REG, offset)); + } + else if (name >= PPCREC_NAME_TEMPORARY_FPR0 && name < (PPCREC_NAME_TEMPORARY_FPR0 + 8)) + { + str(fpReg<QReg>(imlRegR), AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, temporaryFPR) + sizeof(FPR_t) * (name - PPCREC_NAME_TEMPORARY_FPR0))); + } + else + { + cemu_assert_suspicious(); + } + } + else + { + cemu_assert_suspicious(); + } +} + +bool AArch64GenContext_t::r_r(IMLInstruction* imlInstruction) +{ + WReg regR = gpReg<WReg>(imlInstruction->op_r_r.regR); + WReg regA = gpReg<WReg>(imlInstruction->op_r_r.regA); + + if (imlInstruction->operation == PPCREC_IML_OP_ASSIGN) + { + mov(regR, regA); + } + else if (imlInstruction->operation == PPCREC_IML_OP_ENDIAN_SWAP) + { + rev(regR, regA); + } + else if (imlInstruction->operation == PPCREC_IML_OP_ASSIGN_S8_TO_S32) + { + sxtb(regR, regA); + } + else if (imlInstruction->operation == PPCREC_IML_OP_ASSIGN_S16_TO_S32) + { + sxth(regR, regA); + } + else if (imlInstruction->operation == PPCREC_IML_OP_NOT) + { + mvn(regR, regA); + } + else if (imlInstruction->operation == PPCREC_IML_OP_NEG) + { + neg(regR, regA); + } + else if (imlInstruction->operation == PPCREC_IML_OP_CNTLZW) + { + clz(regR, regA); + } + else + { + cemuLog_log(LogType::Recompiler, "PPCRecompilerAArch64Gen_imlInstruction_r_r(): Unsupported operation {:x}", imlInstruction->operation); + return false; + } + return true; +} + +bool AArch64GenContext_t::r_s32(IMLInstruction* imlInstruction) +{ + sint32 imm32 = imlInstruction->op_r_immS32.immS32; + WReg reg = gpReg<WReg>(imlInstruction->op_r_immS32.regR); + + if (imlInstruction->operation == PPCREC_IML_OP_ASSIGN) + { + mov(reg, imm32); + } + else if (imlInstruction->operation == PPCREC_IML_OP_LEFT_ROTATE) + { + ror(reg, reg, 32 - (imm32 & 0x1f)); + } + else + { + cemuLog_log(LogType::Recompiler, "PPCRecompilerAArch64Gen_imlInstruction_r_s32(): Unsupported operation {:x}", imlInstruction->operation); + return false; + } + return true; +} + +bool AArch64GenContext_t::r_r_s32(IMLInstruction* imlInstruction) +{ + WReg regR = gpReg<WReg>(imlInstruction->op_r_r_s32.regR); + WReg regA = gpReg<WReg>(imlInstruction->op_r_r_s32.regA); + sint32 immS32 = imlInstruction->op_r_r_s32.immS32; + + if (imlInstruction->operation == PPCREC_IML_OP_ADD) + { + add_imm(regR, regA, immS32, TEMP_GPR1.WReg); + } + else if (imlInstruction->operation == PPCREC_IML_OP_SUB) + { + sub_imm(regR, regA, immS32, TEMP_GPR1.WReg); + } + else if (imlInstruction->operation == PPCREC_IML_OP_AND) + { + mov(TEMP_GPR1.WReg, immS32); + and_(regR, regA, TEMP_GPR1.WReg); + } + else if (imlInstruction->operation == PPCREC_IML_OP_OR) + { + mov(TEMP_GPR1.WReg, immS32); + orr(regR, regA, TEMP_GPR1.WReg); + } + else if (imlInstruction->operation == PPCREC_IML_OP_XOR) + { + mov(TEMP_GPR1.WReg, immS32); + eor(regR, regA, TEMP_GPR1.WReg); + } + else if (imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_SIGNED) + { + mov(TEMP_GPR1.WReg, immS32); + mul(regR, regA, TEMP_GPR1.WReg); + } + else if (imlInstruction->operation == PPCREC_IML_OP_LEFT_SHIFT) + { + lsl(regR, regA, (uint32)immS32 & 0x1f); + } + else if (imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_U) + { + lsr(regR, regA, (uint32)immS32 & 0x1f); + } + else if (imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_S) + { + asr(regR, regA, (uint32)immS32 & 0x1f); + } + else + { + cemuLog_log(LogType::Recompiler, "PPCRecompilerAArch64Gen_imlInstruction_r_r_s32(): Unsupported operation {:x}", imlInstruction->operation); + cemu_assert_suspicious(); + return false; + } + return true; +} + +bool AArch64GenContext_t::r_r_s32_carry(IMLInstruction* imlInstruction) +{ + WReg regR = gpReg<WReg>(imlInstruction->op_r_r_s32_carry.regR); + WReg regA = gpReg<WReg>(imlInstruction->op_r_r_s32_carry.regA); + WReg regCarry = gpReg<WReg>(imlInstruction->op_r_r_s32_carry.regCarry); + + sint32 immS32 = imlInstruction->op_r_r_s32_carry.immS32; + if (imlInstruction->operation == PPCREC_IML_OP_ADD) + { + adds_imm(regR, regA, immS32, TEMP_GPR1.WReg); + cset(regCarry, Cond::CS); + } + else if (imlInstruction->operation == PPCREC_IML_OP_ADD_WITH_CARRY) + { + mov(TEMP_GPR1.WReg, immS32); + cmp(regCarry, 1); + adcs(regR, regA, TEMP_GPR1.WReg); + cset(regCarry, Cond::CS); + } + else + { + cemu_assert_suspicious(); + return false; + } + + return true; +} + +bool AArch64GenContext_t::r_r_r(IMLInstruction* imlInstruction) +{ + WReg regResult = gpReg<WReg>(imlInstruction->op_r_r_r.regR); + XReg reg64Result = aliasAs<XReg>(regResult); + WReg regOperand1 = gpReg<WReg>(imlInstruction->op_r_r_r.regA); + WReg regOperand2 = gpReg<WReg>(imlInstruction->op_r_r_r.regB); + + if (imlInstruction->operation == PPCREC_IML_OP_ADD) + { + add(regResult, regOperand1, regOperand2); + } + else if (imlInstruction->operation == PPCREC_IML_OP_SUB) + { + sub(regResult, regOperand1, regOperand2); + } + else if (imlInstruction->operation == PPCREC_IML_OP_OR) + { + orr(regResult, regOperand1, regOperand2); + } + else if (imlInstruction->operation == PPCREC_IML_OP_AND) + { + and_(regResult, regOperand1, regOperand2); + } + else if (imlInstruction->operation == PPCREC_IML_OP_XOR) + { + eor(regResult, regOperand1, regOperand2); + } + else if (imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_SIGNED) + { + mul(regResult, regOperand1, regOperand2); + } + else if (imlInstruction->operation == PPCREC_IML_OP_SLW) + { + tst(regOperand2, 32); + lsl(regResult, regOperand1, regOperand2); + csel(regResult, regResult, wzr, Cond::EQ); + } + else if (imlInstruction->operation == PPCREC_IML_OP_SRW) + { + tst(regOperand2, 32); + lsr(regResult, regOperand1, regOperand2); + csel(regResult, regResult, wzr, Cond::EQ); + } + else if (imlInstruction->operation == PPCREC_IML_OP_LEFT_ROTATE) + { + neg(TEMP_GPR1.WReg, regOperand2); + ror(regResult, regOperand1, TEMP_GPR1.WReg); + } + else if (imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_S) + { + asr(regResult, regOperand1, regOperand2); + } + else if (imlInstruction->operation == PPCREC_IML_OP_RIGHT_SHIFT_U) + { + lsr(regResult, regOperand1, regOperand2); + } + else if (imlInstruction->operation == PPCREC_IML_OP_LEFT_SHIFT) + { + lsl(regResult, regOperand1, regOperand2); + } + else if (imlInstruction->operation == PPCREC_IML_OP_DIVIDE_SIGNED) + { + sdiv(regResult, regOperand1, regOperand2); + } + else if (imlInstruction->operation == PPCREC_IML_OP_DIVIDE_UNSIGNED) + { + udiv(regResult, regOperand1, regOperand2); + } + else if (imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_HIGH_SIGNED) + { + smull(reg64Result, regOperand1, regOperand2); + lsr(reg64Result, reg64Result, 32); + } + else if (imlInstruction->operation == PPCREC_IML_OP_MULTIPLY_HIGH_UNSIGNED) + { + umull(reg64Result, regOperand1, regOperand2); + lsr(reg64Result, reg64Result, 32); + } + else + { + cemuLog_log(LogType::Recompiler, "PPCRecompilerAArch64Gen_imlInstruction_r_r_r(): Unsupported operation {:x}", imlInstruction->operation); + return false; + } + return true; +} + +bool AArch64GenContext_t::r_r_r_carry(IMLInstruction* imlInstruction) +{ + WReg regR = gpReg<WReg>(imlInstruction->op_r_r_r_carry.regR); + WReg regA = gpReg<WReg>(imlInstruction->op_r_r_r_carry.regA); + WReg regB = gpReg<WReg>(imlInstruction->op_r_r_r_carry.regB); + WReg regCarry = gpReg<WReg>(imlInstruction->op_r_r_r_carry.regCarry); + + if (imlInstruction->operation == PPCREC_IML_OP_ADD) + { + adds(regR, regA, regB); + cset(regCarry, Cond::CS); + } + else if (imlInstruction->operation == PPCREC_IML_OP_ADD_WITH_CARRY) + { + cmp(regCarry, 1); + adcs(regR, regA, regB); + cset(regCarry, Cond::CS); + } + else + { + cemu_assert_suspicious(); + return false; + } + + return true; +} + +Cond ImlCondToArm64Cond(IMLCondition condition) +{ + switch (condition) + { + case IMLCondition::EQ: + return Cond::EQ; + case IMLCondition::NEQ: + return Cond::NE; + case IMLCondition::UNSIGNED_GT: + return Cond::HI; + case IMLCondition::UNSIGNED_LT: + return Cond::LO; + case IMLCondition::SIGNED_GT: + return Cond::GT; + case IMLCondition::SIGNED_LT: + return Cond::LT; + default: + { + cemu_assert_suspicious(); + return Cond::EQ; + } + } +} + +void AArch64GenContext_t::compare(IMLInstruction* imlInstruction) +{ + WReg regR = gpReg<WReg>(imlInstruction->op_compare.regR); + WReg regA = gpReg<WReg>(imlInstruction->op_compare.regA); + WReg regB = gpReg<WReg>(imlInstruction->op_compare.regB); + Cond cond = ImlCondToArm64Cond(imlInstruction->op_compare.cond); + cmp(regA, regB); + cset(regR, cond); +} + +void AArch64GenContext_t::compare_s32(IMLInstruction* imlInstruction) +{ + WReg regR = gpReg<WReg>(imlInstruction->op_compare.regR); + WReg regA = gpReg<WReg>(imlInstruction->op_compare.regA); + sint32 imm = imlInstruction->op_compare_s32.immS32; + auto cond = ImlCondToArm64Cond(imlInstruction->op_compare.cond); + cmp_imm(regA, imm, TEMP_GPR1.WReg); + cset(regR, cond); +} + +void AArch64GenContext_t::cjump(IMLInstruction* imlInstruction, IMLSegment* imlSegment) +{ + auto regBool = gpReg<WReg>(imlInstruction->op_conditional_jump.registerBool); + prepareJump(ConditionalRegJumpInfo{ + .target = imlSegment->nextSegmentBranchTaken, + .regBool = regBool, + .mustBeTrue = imlInstruction->op_conditional_jump.mustBeTrue, + }); +} + +void AArch64GenContext_t::jump(IMLSegment* imlSegment) +{ + prepareJump(UnconditionalJumpInfo{.target = imlSegment->nextSegmentBranchTaken}); +} + +void AArch64GenContext_t::conditionalJumpCycleCheck(IMLSegment* imlSegment) +{ + ldr(TEMP_GPR1.WReg, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, remainingCycles))); + prepareJump(NegativeRegValueJumpInfo{ + .target = imlSegment->nextSegmentBranchTaken, + .regValue = TEMP_GPR1.WReg, + }); +} + +void* PPCRecompiler_virtualHLE(PPCInterpreter_t* ppcInterpreter, uint32 hleFuncId) +{ + void* prevRSPTemp = ppcInterpreter->rspTemp; + if (hleFuncId == 0xFFD0) + { + ppcInterpreter->remainingCycles -= 500; // let subtract about 500 cycles for each HLE call + ppcInterpreter->gpr[3] = 0; + PPCInterpreter_nextInstruction(ppcInterpreter); + return PPCInterpreter_getCurrentInstance(); + } + else + { + auto hleCall = PPCInterpreter_getHLECall(hleFuncId); + cemu_assert(hleCall != nullptr); + hleCall(ppcInterpreter); + } + ppcInterpreter->rspTemp = prevRSPTemp; + return PPCInterpreter_getCurrentInstance(); +} + +bool AArch64GenContext_t::macro(IMLInstruction* imlInstruction) +{ + if (imlInstruction->operation == PPCREC_IML_MACRO_B_TO_REG) + { + WReg branchDstReg = gpReg<WReg>(imlInstruction->op_macro.paramReg); + + mov(TEMP_GPR1.WReg, offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); + add(TEMP_GPR1.WReg, TEMP_GPR1.WReg, branchDstReg, ShMod::LSL, 1); + ldr(TEMP_GPR1.XReg, AdrExt(PPC_REC_INSTANCE_REG, TEMP_GPR1.WReg, ExtMod::UXTW)); + mov(LR.WReg, branchDstReg); + br(TEMP_GPR1.XReg); + return true; + } + else if (imlInstruction->operation == PPCREC_IML_MACRO_BL) + { + uint32 newLR = imlInstruction->op_macro.param + 4; + + mov(TEMP_GPR1.WReg, newLR); + str(TEMP_GPR1.WReg, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, spr.LR))); + + uint32 newIP = imlInstruction->op_macro.param2; + uint64 lookupOffset = (uint64)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable) + (uint64)newIP * 2ULL; + mov(TEMP_GPR1.XReg, lookupOffset); + ldr(TEMP_GPR1.XReg, AdrReg(PPC_REC_INSTANCE_REG, TEMP_GPR1.XReg)); + mov(LR.WReg, newIP); + br(TEMP_GPR1.XReg); + return true; + } + else if (imlInstruction->operation == PPCREC_IML_MACRO_B_FAR) + { + uint32 newIP = imlInstruction->op_macro.param2; + uint64 lookupOffset = (uint64)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable) + (uint64)newIP * 2ULL; + mov(TEMP_GPR1.XReg, lookupOffset); + ldr(TEMP_GPR1.XReg, AdrReg(PPC_REC_INSTANCE_REG, TEMP_GPR1.XReg)); + mov(LR.WReg, newIP); + br(TEMP_GPR1.XReg); + return true; + } + else if (imlInstruction->operation == PPCREC_IML_MACRO_LEAVE) + { + uint32 currentInstructionAddress = imlInstruction->op_macro.param; + mov(TEMP_GPR1.XReg, (uint64)offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); // newIP = 0 special value for recompiler exit + ldr(TEMP_GPR1.XReg, AdrReg(PPC_REC_INSTANCE_REG, TEMP_GPR1.XReg)); + mov(LR.WReg, currentInstructionAddress); + br(TEMP_GPR1.XReg); + return true; + } + else if (imlInstruction->operation == PPCREC_IML_MACRO_DEBUGBREAK) + { + brk(0xf000); + return true; + } + else if (imlInstruction->operation == PPCREC_IML_MACRO_COUNT_CYCLES) + { + uint32 cycleCount = imlInstruction->op_macro.param; + AdrUimm adrCycles = AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, remainingCycles)); + ldr(TEMP_GPR1.WReg, adrCycles); + sub_imm(TEMP_GPR1.WReg, TEMP_GPR1.WReg, cycleCount, TEMP_GPR2.WReg); + str(TEMP_GPR1.WReg, adrCycles); + return true; + } + else if (imlInstruction->operation == PPCREC_IML_MACRO_HLE) + { + uint32 ppcAddress = imlInstruction->op_macro.param; + uint32 funcId = imlInstruction->op_macro.param2; + Label cyclesLeftLabel; + + // update instruction pointer + mov(TEMP_GPR1.WReg, ppcAddress); + str(TEMP_GPR1.WReg, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, instructionPointer))); + // set parameters + str(x30, AdrPreImm(sp, -16)); + + mov(x0, HCPU_REG); + mov(w1, funcId); + // call HLE function + + mov(TEMP_GPR1.XReg, (uint64)PPCRecompiler_virtualHLE); + blr(TEMP_GPR1.XReg); + + mov(HCPU_REG, x0); + + ldr(x30, AdrPostImm(sp, 16)); + + // check if cycles where decreased beyond zero, if yes -> leave recompiler + ldr(TEMP_GPR1.WReg, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, remainingCycles))); + tbz(TEMP_GPR1.WReg, 31, cyclesLeftLabel); // check if negative + + mov(TEMP_GPR1.XReg, offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); + ldr(TEMP_GPR1.XReg, AdrReg(PPC_REC_INSTANCE_REG, TEMP_GPR1.XReg)); + ldr(LR.WReg, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, instructionPointer))); + // branch to recompiler exit + br(TEMP_GPR1.XReg); + + L(cyclesLeftLabel); + // check if instruction pointer was changed + // assign new instruction pointer to LR.WReg + ldr(LR.WReg, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, instructionPointer))); + mov(TEMP_GPR1.XReg, offsetof(PPCRecompilerInstanceData_t, ppcRecompilerDirectJumpTable)); + add(TEMP_GPR1.XReg, TEMP_GPR1.XReg, LR.XReg, ShMod::LSL, 1); + ldr(TEMP_GPR1.XReg, AdrReg(PPC_REC_INSTANCE_REG, TEMP_GPR1.XReg)); + // branch to [ppcRecompilerDirectJumpTable + PPCInterpreter_t::instructionPointer * 2] + br(TEMP_GPR1.XReg); + return true; + } + else + { + cemuLog_log(LogType::Recompiler, "Unknown recompiler macro operation %d\n", imlInstruction->operation); + cemu_assert_suspicious(); + } + return false; +} + +bool AArch64GenContext_t::load(IMLInstruction* imlInstruction, bool indexed) +{ + cemu_assert_debug(imlInstruction->op_storeLoad.registerData.GetRegFormat() == IMLRegFormat::I32); + cemu_assert_debug(imlInstruction->op_storeLoad.registerMem.GetRegFormat() == IMLRegFormat::I32); + if (indexed) + cemu_assert_debug(imlInstruction->op_storeLoad.registerMem2.GetRegFormat() == IMLRegFormat::I32); + + sint32 memOffset = imlInstruction->op_storeLoad.immS32; + bool signExtend = imlInstruction->op_storeLoad.flags2.signExtend; + bool switchEndian = imlInstruction->op_storeLoad.flags2.swapEndian; + WReg memReg = gpReg<WReg>(imlInstruction->op_storeLoad.registerMem); + WReg dataReg = gpReg<WReg>(imlInstruction->op_storeLoad.registerData); + + add_imm(TEMP_GPR1.WReg, memReg, memOffset, TEMP_GPR1.WReg); + if (indexed) + add(TEMP_GPR1.WReg, TEMP_GPR1.WReg, gpReg<WReg>(imlInstruction->op_storeLoad.registerMem2)); + + auto adr = AdrExt(MEM_BASE_REG, TEMP_GPR1.WReg, ExtMod::UXTW); + if (imlInstruction->op_storeLoad.copyWidth == 32) + { + ldr(dataReg, adr); + if (switchEndian) + rev(dataReg, dataReg); + } + else if (imlInstruction->op_storeLoad.copyWidth == 16) + { + if (switchEndian) + { + ldrh(dataReg, adr); + rev(dataReg, dataReg); + if (signExtend) + asr(dataReg, dataReg, 16); + else + lsr(dataReg, dataReg, 16); + } + else + { + if (signExtend) + ldrsh(dataReg, adr); + else + ldrh(dataReg, adr); + } + } + else if (imlInstruction->op_storeLoad.copyWidth == 8) + { + if (signExtend) + ldrsb(dataReg, adr); + else + ldrb(dataReg, adr); + } + else + { + return false; + } + return true; +} + +bool AArch64GenContext_t::store(IMLInstruction* imlInstruction, bool indexed) +{ + cemu_assert_debug(imlInstruction->op_storeLoad.registerData.GetRegFormat() == IMLRegFormat::I32); + cemu_assert_debug(imlInstruction->op_storeLoad.registerMem.GetRegFormat() == IMLRegFormat::I32); + if (indexed) + cemu_assert_debug(imlInstruction->op_storeLoad.registerMem2.GetRegFormat() == IMLRegFormat::I32); + + WReg dataReg = gpReg<WReg>(imlInstruction->op_storeLoad.registerData); + WReg memReg = gpReg<WReg>(imlInstruction->op_storeLoad.registerMem); + sint32 memOffset = imlInstruction->op_storeLoad.immS32; + bool swapEndian = imlInstruction->op_storeLoad.flags2.swapEndian; + + add_imm(TEMP_GPR1.WReg, memReg, memOffset, TEMP_GPR1.WReg); + if (indexed) + add(TEMP_GPR1.WReg, TEMP_GPR1.WReg, gpReg<WReg>(imlInstruction->op_storeLoad.registerMem2)); + AdrExt adr = AdrExt(MEM_BASE_REG, TEMP_GPR1.WReg, ExtMod::UXTW); + if (imlInstruction->op_storeLoad.copyWidth == 32) + { + if (swapEndian) + { + rev(TEMP_GPR2.WReg, dataReg); + str(TEMP_GPR2.WReg, adr); + } + else + { + str(dataReg, adr); + } + } + else if (imlInstruction->op_storeLoad.copyWidth == 16) + { + if (swapEndian) + { + rev(TEMP_GPR2.WReg, dataReg); + lsr(TEMP_GPR2.WReg, TEMP_GPR2.WReg, 16); + strh(TEMP_GPR2.WReg, adr); + } + else + { + strh(dataReg, adr); + } + } + else if (imlInstruction->op_storeLoad.copyWidth == 8) + { + strb(dataReg, adr); + } + else + { + return false; + } + return true; +} + +void AArch64GenContext_t::atomic_cmp_store(IMLInstruction* imlInstruction) +{ + WReg outReg = gpReg<WReg>(imlInstruction->op_atomic_compare_store.regBoolOut); + WReg eaReg = gpReg<WReg>(imlInstruction->op_atomic_compare_store.regEA); + WReg valReg = gpReg<WReg>(imlInstruction->op_atomic_compare_store.regWriteValue); + WReg cmpValReg = gpReg<WReg>(imlInstruction->op_atomic_compare_store.regCompareValue); + + if (s_cpu.isAtomicSupported()) + { + mov(TEMP_GPR2.WReg, cmpValReg); + add(TEMP_GPR1.XReg, MEM_BASE_REG, eaReg, ExtMod::UXTW); + casal(TEMP_GPR2.WReg, valReg, AdrNoOfs(TEMP_GPR1.XReg)); + cmp(TEMP_GPR2.WReg, cmpValReg); + cset(outReg, Cond::EQ); + } + else + { + Label notEqual; + Label storeFailed; + + add(TEMP_GPR1.XReg, MEM_BASE_REG, eaReg, ExtMod::UXTW); + L(storeFailed); + ldaxr(TEMP_GPR2.WReg, AdrNoOfs(TEMP_GPR1.XReg)); + cmp(TEMP_GPR2.WReg, cmpValReg); + bne(notEqual); + stlxr(TEMP_GPR2.WReg, valReg, AdrNoOfs(TEMP_GPR1.XReg)); + cbnz(TEMP_GPR2.WReg, storeFailed); + + L(notEqual); + cset(outReg, Cond::EQ); + } +} + +bool AArch64GenContext_t::fpr_load(IMLInstruction* imlInstruction, bool indexed) +{ + const IMLReg& dataReg = imlInstruction->op_storeLoad.registerData; + SReg dataSReg = fpReg<SReg>(dataReg); + DReg dataDReg = fpReg<DReg>(dataReg); + WReg realRegisterMem = gpReg<WReg>(imlInstruction->op_storeLoad.registerMem); + WReg indexReg = indexed ? gpReg<WReg>(imlInstruction->op_storeLoad.registerMem2) : wzr; + sint32 adrOffset = imlInstruction->op_storeLoad.immS32; + uint8 mode = imlInstruction->op_storeLoad.mode; + + if (mode == PPCREC_FPR_LD_MODE_SINGLE) + { + add_imm(TEMP_GPR1.WReg, realRegisterMem, adrOffset, TEMP_GPR1.WReg); + if (indexed) + add(TEMP_GPR1.WReg, TEMP_GPR1.WReg, indexReg); + ldr(TEMP_GPR2.WReg, AdrExt(MEM_BASE_REG, TEMP_GPR1.WReg, ExtMod::UXTW)); + rev(TEMP_GPR2.WReg, TEMP_GPR2.WReg); + fmov(dataSReg, TEMP_GPR2.WReg); + + if (imlInstruction->op_storeLoad.flags2.notExpanded) + { + // leave value as single + } + else + { + fcvt(dataDReg, dataSReg); + } + } + else if (mode == PPCREC_FPR_LD_MODE_DOUBLE) + { + add_imm(TEMP_GPR1.WReg, realRegisterMem, adrOffset, TEMP_GPR1.WReg); + if (indexed) + add(TEMP_GPR1.WReg, TEMP_GPR1.WReg, indexReg); + ldr(TEMP_GPR2.XReg, AdrExt(MEM_BASE_REG, TEMP_GPR1.WReg, ExtMod::UXTW)); + rev(TEMP_GPR2.XReg, TEMP_GPR2.XReg); + fmov(dataDReg, TEMP_GPR2.XReg); + } + else + { + return false; + } + return true; +} + +// store to memory +bool AArch64GenContext_t::fpr_store(IMLInstruction* imlInstruction, bool indexed) +{ + const IMLReg& dataImlReg = imlInstruction->op_storeLoad.registerData; + DReg dataDReg = fpReg<DReg>(dataImlReg); + SReg dataSReg = fpReg<SReg>(dataImlReg); + WReg memReg = gpReg<WReg>(imlInstruction->op_storeLoad.registerMem); + WReg indexReg = indexed ? gpReg<WReg>(imlInstruction->op_storeLoad.registerMem2) : wzr; + sint32 memOffset = imlInstruction->op_storeLoad.immS32; + uint8 mode = imlInstruction->op_storeLoad.mode; + + if (mode == PPCREC_FPR_ST_MODE_SINGLE) + { + add_imm(TEMP_GPR1.WReg, memReg, memOffset, TEMP_GPR1.WReg); + if (indexed) + add(TEMP_GPR1.WReg, TEMP_GPR1.WReg, indexReg); + + if (imlInstruction->op_storeLoad.flags2.notExpanded) + { + // value is already in single format + fmov(TEMP_GPR2.WReg, dataSReg); + } + else + { + fcvt(TEMP_FPR.SReg, dataDReg); + fmov(TEMP_GPR2.WReg, TEMP_FPR.SReg); + } + rev(TEMP_GPR2.WReg, TEMP_GPR2.WReg); + str(TEMP_GPR2.WReg, AdrExt(MEM_BASE_REG, TEMP_GPR1.WReg, ExtMod::UXTW)); + } + else if (mode == PPCREC_FPR_ST_MODE_DOUBLE) + { + add_imm(TEMP_GPR1.WReg, memReg, memOffset, TEMP_GPR1.WReg); + if (indexed) + add(TEMP_GPR1.WReg, TEMP_GPR1.WReg, indexReg); + fmov(TEMP_GPR2.XReg, dataDReg); + rev(TEMP_GPR2.XReg, TEMP_GPR2.XReg); + str(TEMP_GPR2.XReg, AdrExt(MEM_BASE_REG, TEMP_GPR1.WReg, ExtMod::UXTW)); + } + else if (mode == PPCREC_FPR_ST_MODE_UI32_FROM_PS0) + { + add_imm(TEMP_GPR1.WReg, memReg, memOffset, TEMP_GPR1.WReg); + if (indexed) + add(TEMP_GPR1.WReg, TEMP_GPR1.WReg, indexReg); + fmov(TEMP_GPR2.WReg, dataSReg); + rev(TEMP_GPR2.WReg, TEMP_GPR2.WReg); + str(TEMP_GPR2.WReg, AdrExt(MEM_BASE_REG, TEMP_GPR1.WReg, ExtMod::UXTW)); + } + else + { + cemu_assert_suspicious(); + cemuLog_log(LogType::Recompiler, "PPCRecompilerAArch64Gen_imlInstruction_fpr_store(): Unsupported mode %d\n", mode); + return false; + } + return true; +} + +// FPR op FPR +void AArch64GenContext_t::fpr_r_r(IMLInstruction* imlInstruction) +{ + auto imlRegR = imlInstruction->op_fpr_r_r.regR; + auto imlRegA = imlInstruction->op_fpr_r_r.regA; + + if (imlInstruction->operation == PPCREC_IML_OP_FPR_FLOAT_TO_INT) + { + fcvtzs(gpReg<WReg>(imlRegR), fpReg<DReg>(imlRegA)); + return; + } + else if (imlInstruction->operation == PPCREC_IML_OP_FPR_INT_TO_FLOAT) + { + scvtf(fpReg<DReg>(imlRegR), gpReg<WReg>(imlRegA)); + return; + } + else if (imlInstruction->operation == PPCREC_IML_OP_FPR_BITCAST_INT_TO_FLOAT) + { + cemu_assert_debug(imlRegR.GetRegFormat() == IMLRegFormat::F64); // assuming target is always F64 for now + // exact operation depends on size of types. Floats are automatically promoted to double if the target is F64 + DReg regFprDReg = fpReg<DReg>(imlRegR); + SReg regFprSReg = fpReg<SReg>(imlRegR); + if (imlRegA.GetRegFormat() == IMLRegFormat::I32) + { + fmov(regFprSReg, gpReg<WReg>(imlRegA)); + // float to double + fcvt(regFprDReg, regFprSReg); + } + else if (imlRegA.GetRegFormat() == IMLRegFormat::I64) + { + fmov(regFprDReg, gpReg<XReg>(imlRegA)); + } + else + { + cemu_assert_unimplemented(); + } + return; + } + + DReg regR = fpReg<DReg>(imlRegR); + DReg regA = fpReg<DReg>(imlRegA); + + if (imlInstruction->operation == PPCREC_IML_OP_FPR_ASSIGN) + { + fmov(regR, regA); + } + else if (imlInstruction->operation == PPCREC_IML_OP_FPR_MULTIPLY) + { + fmul(regR, regR, regA); + } + else if (imlInstruction->operation == PPCREC_IML_OP_FPR_DIVIDE) + { + fdiv(regR, regR, regA); + } + else if (imlInstruction->operation == PPCREC_IML_OP_FPR_ADD) + { + fadd(regR, regR, regA); + } + else if (imlInstruction->operation == PPCREC_IML_OP_FPR_SUB) + { + fsub(regR, regR, regA); + } + else if (imlInstruction->operation == PPCREC_IML_OP_FPR_FCTIWZ) + { + fcvtzs(regR, regA); + } + else + { + cemu_assert_suspicious(); + } +} + +void AArch64GenContext_t::fpr_r_r_r(IMLInstruction* imlInstruction) +{ + DReg regR = fpReg<DReg>(imlInstruction->op_fpr_r_r_r.regR); + DReg regA = fpReg<DReg>(imlInstruction->op_fpr_r_r_r.regA); + DReg regB = fpReg<DReg>(imlInstruction->op_fpr_r_r_r.regB); + + if (imlInstruction->operation == PPCREC_IML_OP_FPR_MULTIPLY) + { + fmul(regR, regA, regB); + } + else if (imlInstruction->operation == PPCREC_IML_OP_FPR_ADD) + { + fadd(regR, regA, regB); + } + else if (imlInstruction->operation == PPCREC_IML_OP_FPR_SUB) + { + fsub(regR, regA, regB); + } + else + { + cemu_assert_suspicious(); + } +} + +/* + * FPR = op (fprA, fprB, fprC) + */ +void AArch64GenContext_t::fpr_r_r_r_r(IMLInstruction* imlInstruction) +{ + DReg regR = fpReg<DReg>(imlInstruction->op_fpr_r_r_r_r.regR); + DReg regA = fpReg<DReg>(imlInstruction->op_fpr_r_r_r_r.regA); + DReg regB = fpReg<DReg>(imlInstruction->op_fpr_r_r_r_r.regB); + DReg regC = fpReg<DReg>(imlInstruction->op_fpr_r_r_r_r.regC); + + if (imlInstruction->operation == PPCREC_IML_OP_FPR_SELECT) + { + fcmp(regA, 0.0); + fcsel(regR, regC, regB, Cond::GE); + } + else + { + cemu_assert_suspicious(); + } +} + +void AArch64GenContext_t::fpr_r(IMLInstruction* imlInstruction) +{ + DReg regRDReg = fpReg<DReg>(imlInstruction->op_fpr_r.regR); + SReg regRSReg = fpReg<SReg>(imlInstruction->op_fpr_r.regR); + + if (imlInstruction->operation == PPCREC_IML_OP_FPR_NEGATE) + { + fneg(regRDReg, regRDReg); + } + else if (imlInstruction->operation == PPCREC_IML_OP_FPR_LOAD_ONE) + { + fmov(regRDReg, 1.0); + } + else if (imlInstruction->operation == PPCREC_IML_OP_FPR_ABS) + { + fabs(regRDReg, regRDReg); + } + else if (imlInstruction->operation == PPCREC_IML_OP_FPR_NEGATIVE_ABS) + { + fabs(regRDReg, regRDReg); + fneg(regRDReg, regRDReg); + } + else if (imlInstruction->operation == PPCREC_IML_OP_FPR_ROUND_TO_SINGLE_PRECISION_BOTTOM) + { + // convert to 32bit single + fcvt(regRSReg, regRDReg); + // convert back to 64bit double + fcvt(regRDReg, regRSReg); + } + else if (imlInstruction->operation == PPCREC_IML_OP_FPR_EXPAND_F32_TO_F64) + { + // convert bottom to 64bit double + fcvt(regRDReg, regRSReg); + } + else + { + cemu_assert_unimplemented(); + } +} + +Cond ImlFPCondToArm64Cond(IMLCondition cond) +{ + switch (cond) + { + case IMLCondition::UNORDERED_GT: + return Cond::GT; + case IMLCondition::UNORDERED_LT: + return Cond::MI; + case IMLCondition::UNORDERED_EQ: + return Cond::EQ; + case IMLCondition::UNORDERED_U: + return Cond::VS; + default: + { + cemu_assert_suspicious(); + return Cond::EQ; + } + } +} + +void AArch64GenContext_t::fpr_compare(IMLInstruction* imlInstruction) +{ + WReg regR = gpReg<WReg>(imlInstruction->op_fpr_compare.regR); + DReg regA = fpReg<DReg>(imlInstruction->op_fpr_compare.regA); + DReg regB = fpReg<DReg>(imlInstruction->op_fpr_compare.regB); + auto cond = ImlFPCondToArm64Cond(imlInstruction->op_fpr_compare.cond); + fcmp(regA, regB); + cset(regR, cond); +} + +void AArch64GenContext_t::call_imm(IMLInstruction* imlInstruction) +{ + str(x30, AdrPreImm(sp, -16)); + mov(TEMP_GPR1.XReg, imlInstruction->op_call_imm.callAddress); + blr(TEMP_GPR1.XReg); + ldr(x30, AdrPostImm(sp, 16)); +} + +bool PPCRecompiler_generateAArch64Code(struct PPCRecFunction_t* PPCRecFunction, struct ppcImlGenContext_t* ppcImlGenContext) +{ + AArch64Allocator allocator; + AArch64GenContext_t aarch64GenContext{&allocator}; + + // generate iml instruction code + bool codeGenerationFailed = false; + for (IMLSegment* segIt : ppcImlGenContext->segmentList2) + { + if (codeGenerationFailed) + break; + segIt->x64Offset = aarch64GenContext.getSize(); + + aarch64GenContext.storeSegmentStart(segIt); + + for (size_t i = 0; i < segIt->imlList.size(); i++) + { + IMLInstruction* imlInstruction = segIt->imlList.data() + i; + if (imlInstruction->type == PPCREC_IML_TYPE_R_NAME) + { + aarch64GenContext.r_name(imlInstruction); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_NAME_R) + { + aarch64GenContext.name_r(imlInstruction); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_R_R) + { + if (!aarch64GenContext.r_r(imlInstruction)) + codeGenerationFailed = true; + } + else if (imlInstruction->type == PPCREC_IML_TYPE_R_S32) + { + if (!aarch64GenContext.r_s32(imlInstruction)) + codeGenerationFailed = true; + } + else if (imlInstruction->type == PPCREC_IML_TYPE_R_R_S32) + { + if (!aarch64GenContext.r_r_s32(imlInstruction)) + codeGenerationFailed = true; + } + else if (imlInstruction->type == PPCREC_IML_TYPE_R_R_S32_CARRY) + { + if (!aarch64GenContext.r_r_s32_carry(imlInstruction)) + codeGenerationFailed = true; + } + else if (imlInstruction->type == PPCREC_IML_TYPE_R_R_R) + { + if (!aarch64GenContext.r_r_r(imlInstruction)) + codeGenerationFailed = true; + } + else if (imlInstruction->type == PPCREC_IML_TYPE_R_R_R_CARRY) + { + if (!aarch64GenContext.r_r_r_carry(imlInstruction)) + codeGenerationFailed = true; + } + else if (imlInstruction->type == PPCREC_IML_TYPE_COMPARE) + { + aarch64GenContext.compare(imlInstruction); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_COMPARE_S32) + { + aarch64GenContext.compare_s32(imlInstruction); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_CONDITIONAL_JUMP) + { + aarch64GenContext.cjump(imlInstruction, segIt); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_JUMP) + { + aarch64GenContext.jump(segIt); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_CJUMP_CYCLE_CHECK) + { + aarch64GenContext.conditionalJumpCycleCheck(segIt); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_MACRO) + { + if (!aarch64GenContext.macro(imlInstruction)) + codeGenerationFailed = true; + } + else if (imlInstruction->type == PPCREC_IML_TYPE_LOAD) + { + if (!aarch64GenContext.load(imlInstruction, false)) + codeGenerationFailed = true; + } + else if (imlInstruction->type == PPCREC_IML_TYPE_LOAD_INDEXED) + { + if (!aarch64GenContext.load(imlInstruction, true)) + codeGenerationFailed = true; + } + else if (imlInstruction->type == PPCREC_IML_TYPE_STORE) + { + if (!aarch64GenContext.store(imlInstruction, false)) + codeGenerationFailed = true; + } + else if (imlInstruction->type == PPCREC_IML_TYPE_STORE_INDEXED) + { + if (!aarch64GenContext.store(imlInstruction, true)) + codeGenerationFailed = true; + } + else if (imlInstruction->type == PPCREC_IML_TYPE_ATOMIC_CMP_STORE) + { + aarch64GenContext.atomic_cmp_store(imlInstruction); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_CALL_IMM) + { + aarch64GenContext.call_imm(imlInstruction); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_NO_OP) + { + // no op + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD) + { + if (!aarch64GenContext.fpr_load(imlInstruction, false)) + codeGenerationFailed = true; + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_LOAD_INDEXED) + { + if (!aarch64GenContext.fpr_load(imlInstruction, true)) + codeGenerationFailed = true; + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE) + { + if (!aarch64GenContext.fpr_store(imlInstruction, false)) + codeGenerationFailed = true; + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_STORE_INDEXED) + { + if (!aarch64GenContext.fpr_store(imlInstruction, true)) + codeGenerationFailed = true; + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R) + { + aarch64GenContext.fpr_r_r(imlInstruction); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R) + { + aarch64GenContext.fpr_r_r_r(imlInstruction); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R_R_R_R) + { + aarch64GenContext.fpr_r_r_r_r(imlInstruction); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_R) + { + aarch64GenContext.fpr_r(imlInstruction); + } + else if (imlInstruction->type == PPCREC_IML_TYPE_FPR_COMPARE) + { + aarch64GenContext.fpr_compare(imlInstruction); + } + else + { + codeGenerationFailed = true; + cemu_assert_suspicious(); + cemuLog_log(LogType::Recompiler, "PPCRecompiler_generateAArch64Code(): Unsupported iml type {}", imlInstruction->type); + } + } + } + + // handle failed code generation + if (codeGenerationFailed) + { + return false; + } + + if (!aarch64GenContext.processAllJumps()) + { + cemuLog_log(LogType::Recompiler, "PPCRecompiler_generateAArch64Code(): some jumps exceeded the +/-128MB offset."); + return false; + } + + aarch64GenContext.readyRE(); + + // set code + PPCRecFunction->x86Code = aarch64GenContext.getCode<void*>(); + PPCRecFunction->x86Size = aarch64GenContext.getMaxSize(); + // set free disabled to skip freeing the code from the CodeGenerator destructor + allocator.setFreeDisabled(true); + return true; +} + +void PPCRecompiler_cleanupAArch64Code(void* code, size_t size) +{ + AArch64Allocator allocator; + if (allocator.useProtect()) + CodeArray::protect(code, size, CodeArray::PROTECT_RW); + allocator.free(static_cast<uint32*>(code)); +} + +void AArch64GenContext_t::enterRecompilerCode() +{ + constexpr size_t STACK_SIZE = 160 /* x19 .. x30 + v8.d[0] .. v15.d[0] */; + static_assert(STACK_SIZE % 16 == 0); + sub(sp, sp, STACK_SIZE); + mov(x9, sp); + + stp(x19, x20, AdrPostImm(x9, 16)); + stp(x21, x22, AdrPostImm(x9, 16)); + stp(x23, x24, AdrPostImm(x9, 16)); + stp(x25, x26, AdrPostImm(x9, 16)); + stp(x27, x28, AdrPostImm(x9, 16)); + stp(x29, x30, AdrPostImm(x9, 16)); + st4((v8.d - v11.d)[0], AdrPostImm(x9, 32)); + st4((v12.d - v15.d)[0], AdrPostImm(x9, 32)); + mov(HCPU_REG, x1); // call argument 2 + mov(PPC_REC_INSTANCE_REG, (uint64)ppcRecompilerInstanceData); + mov(MEM_BASE_REG, (uint64)memory_base); + + // branch to recFunc + blr(x0); // call argument 1 + + mov(x9, sp); + ldp(x19, x20, AdrPostImm(x9, 16)); + ldp(x21, x22, AdrPostImm(x9, 16)); + ldp(x23, x24, AdrPostImm(x9, 16)); + ldp(x25, x26, AdrPostImm(x9, 16)); + ldp(x27, x28, AdrPostImm(x9, 16)); + ldp(x29, x30, AdrPostImm(x9, 16)); + ld4((v8.d - v11.d)[0], AdrPostImm(x9, 32)); + ld4((v12.d - v15.d)[0], AdrPostImm(x9, 32)); + + add(sp, sp, STACK_SIZE); + + ret(); +} + +void AArch64GenContext_t::leaveRecompilerCode() +{ + str(LR.WReg, AdrUimm(HCPU_REG, offsetof(PPCInterpreter_t, instructionPointer))); + ret(); +} + +bool initializedInterfaceFunctions = false; +AArch64GenContext_t enterRecompilerCode_ctx{}; + +AArch64GenContext_t leaveRecompilerCode_unvisited_ctx{}; +AArch64GenContext_t leaveRecompilerCode_visited_ctx{}; +void PPCRecompilerAArch64Gen_generateRecompilerInterfaceFunctions() +{ + if (initializedInterfaceFunctions) + return; + initializedInterfaceFunctions = true; + + enterRecompilerCode_ctx.enterRecompilerCode(); + enterRecompilerCode_ctx.readyRE(); + PPCRecompiler_enterRecompilerCode = enterRecompilerCode_ctx.getCode<decltype(PPCRecompiler_enterRecompilerCode)>(); + + leaveRecompilerCode_unvisited_ctx.leaveRecompilerCode(); + leaveRecompilerCode_unvisited_ctx.readyRE(); + PPCRecompiler_leaveRecompilerCode_unvisited = leaveRecompilerCode_unvisited_ctx.getCode<decltype(PPCRecompiler_leaveRecompilerCode_unvisited)>(); + + leaveRecompilerCode_visited_ctx.leaveRecompilerCode(); + leaveRecompilerCode_visited_ctx.readyRE(); + PPCRecompiler_leaveRecompilerCode_visited = leaveRecompilerCode_visited_ctx.getCode<decltype(PPCRecompiler_leaveRecompilerCode_visited)>(); +} diff --git a/src/Cafe/HW/Espresso/Recompiler/BackendAArch64/BackendAArch64.h b/src/Cafe/HW/Espresso/Recompiler/BackendAArch64/BackendAArch64.h new file mode 100644 index 00000000..b610ee04 --- /dev/null +++ b/src/Cafe/HW/Espresso/Recompiler/BackendAArch64/BackendAArch64.h @@ -0,0 +1,18 @@ +#pragma once + +#include "HW/Espresso/Recompiler/IML/IMLInstruction.h" +#include "../PPCRecompiler.h" + +bool PPCRecompiler_generateAArch64Code(struct PPCRecFunction_t* PPCRecFunction, struct ppcImlGenContext_t* ppcImlGenContext); +void PPCRecompiler_cleanupAArch64Code(void* code, size_t size); + +void PPCRecompilerAArch64Gen_generateRecompilerInterfaceFunctions(); + +// architecture specific constants +namespace IMLArchAArch64 +{ + static constexpr int PHYSREG_GPR_BASE = 0; + static constexpr int PHYSREG_GPR_COUNT = 25; + static constexpr int PHYSREG_FPR_BASE = PHYSREG_GPR_COUNT; + static constexpr int PHYSREG_FPR_COUNT = 31; +}; // namespace IMLArchAArch64 \ No newline at end of file diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IMLOptimizer.cpp b/src/Cafe/HW/Espresso/Recompiler/IML/IMLOptimizer.cpp index d5693846..7671a163 100644 --- a/src/Cafe/HW/Espresso/Recompiler/IML/IMLOptimizer.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IMLOptimizer.cpp @@ -702,8 +702,10 @@ void IMLOptimizer_StandardOptimizationPassForSegment(IMLOptimizerRegIOAnalysis& { IMLOptimizer_RemoveDeadCodeFromSegment(regIoAnalysis, seg); +#ifdef ARCH_X86_64 // x86 specific optimizations IMLOptimizerX86_SubstituteCJumpForEflagsJump(regIoAnalysis, seg); // this pass should be applied late since it creates invisible eflags dependencies (which would break further register dependency analysis) +#endif } void IMLOptimizer_StandardOptimizationPass(ppcImlGenContext_t& ppcImlGenContext) diff --git a/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocator.cpp b/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocator.cpp index 5de1408b..935e61ac 100644 --- a/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocator.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/IML/IMLRegisterAllocator.cpp @@ -6,6 +6,9 @@ #include "IMLRegisterAllocatorRanges.h" #include "../BackendX64/BackendX64.h" +#ifdef __aarch64__ +#include "../BackendAArch64/BackendAArch64.h" +#endif #include <boost/container/static_vector.hpp> #include <boost/container/small_vector.hpp> @@ -127,23 +130,22 @@ static void GetInstructionFixedRegisters(IMLInstruction* instruction, IMLFixedRe fixedRegs.listInput.clear(); fixedRegs.listOutput.clear(); - // code below for aarch64 has not been tested // The purpose of GetInstructionFixedRegisters() is to constraint virtual registers to specific physical registers for instructions which need it // on x86 this is used for instructions like SHL <reg>, CL where the CL register is hardwired. On aarch it's probably only necessary for setting up the calling convention - cemu_assert_unimplemented(); -#ifdef 0 if (instruction->type == PPCREC_IML_TYPE_CALL_IMM) { const IMLPhysReg intParamToPhysReg[3] = {IMLArchAArch64::PHYSREG_GPR_BASE + 0, IMLArchAArch64::PHYSREG_GPR_BASE + 1, IMLArchAArch64::PHYSREG_GPR_BASE + 2}; const IMLPhysReg floatParamToPhysReg[3] = {IMLArchAArch64::PHYSREG_FPR_BASE + 0, IMLArchAArch64::PHYSREG_FPR_BASE + 1, IMLArchAArch64::PHYSREG_FPR_BASE + 2}; IMLPhysRegisterSet volatileRegs; - for (int i=0; i<19; i++) // x0 to x18 are volatile + for (int i = 0; i <= 17; i++) // x0 to x17 are volatile volatileRegs.SetAvailable(IMLArchAArch64::PHYSREG_GPR_BASE + i); - for (int i = 0; i <= 31; i++) // which float registers are volatile? + // v0-v7 & v16-v31 are volatile. For v8-v15 only the high 64 bits are volatile. + for (int i = 0; i <= 7; i++) + volatileRegs.SetAvailable(IMLArchAArch64::PHYSREG_FPR_BASE + i); + for (int i = 16; i <= 31; i++) volatileRegs.SetAvailable(IMLArchAArch64::PHYSREG_FPR_BASE + i); SetupCallingConvention(instruction, fixedRegs, intParamToPhysReg, floatParamToPhysReg, IMLArchAArch64::PHYSREG_GPR_BASE + 0, IMLArchAArch64::PHYSREG_FPR_BASE + 0, volatileRegs); } -#endif } #else // x86-64 diff --git a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp index 087b90f5..6125c7da 100644 --- a/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/PPCRecompiler.cpp @@ -16,6 +16,9 @@ #include "IML/IML.h" #include "IML/IMLRegisterAllocator.h" #include "BackendX64/BackendX64.h" +#ifdef __aarch64__ +#include "BackendAArch64/BackendAArch64.h" +#endif #include "util/highresolutiontimer/HighResolutionTimer.h" #define PPCREC_FORCE_SYNCHRONOUS_COMPILATION 0 // if 1, then function recompilation will block and execute on the thread that called PPCRecompiler_visitAddressNoBlock @@ -220,12 +223,20 @@ PPCRecFunction_t* PPCRecompiler_recompileFunction(PPCFunctionBoundaryTracker::PP return nullptr; } +#if defined(ARCH_X86_64) // emit x64 code bool x64GenerationSuccess = PPCRecompiler_generateX64Code(ppcRecFunc, &ppcImlGenContext); if (x64GenerationSuccess == false) { return nullptr; } +#elif defined(__aarch64__) + bool aarch64GenerationSuccess = PPCRecompiler_generateAArch64Code(ppcRecFunc, &ppcImlGenContext); + if (aarch64GenerationSuccess == false) + { + return nullptr; + } +#endif if (ActiveSettings::DumpRecompilerFunctionsEnabled()) { FileStream* fs = FileStream::createFile2(ActiveSettings::GetUserDataPath(fmt::format("dump/recompiler/ppc_{:08x}.bin", ppcRecFunc->ppcAddress))); @@ -270,6 +281,7 @@ void PPCRecompiler_NativeRegisterAllocatorPass(ppcImlGenContext_t& ppcImlGenCont for (auto& it : ppcImlGenContext.mappedRegs) raParam.regIdToName.try_emplace(it.second.GetRegID(), it.first); +#if defined(ARCH_X86_64) auto& gprPhysPool = raParam.GetPhysRegPool(IMLRegFormat::I64); gprPhysPool.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_RAX); gprPhysPool.SetAvailable(IMLArchX86::PHYSREG_GPR_BASE + X86_REG_RDX); @@ -301,6 +313,19 @@ void PPCRecompiler_NativeRegisterAllocatorPass(ppcImlGenContext_t& ppcImlGenCont fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 12); fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 13); fprPhysPool.SetAvailable(IMLArchX86::PHYSREG_FPR_BASE + 14); +#elif defined(__aarch64__) + auto& gprPhysPool = raParam.GetPhysRegPool(IMLRegFormat::I64); + for (auto i = IMLArchAArch64::PHYSREG_GPR_BASE; i < IMLArchAArch64::PHYSREG_GPR_BASE + IMLArchAArch64::PHYSREG_GPR_COUNT; i++) + { + if (i == IMLArchAArch64::PHYSREG_GPR_BASE + 18) + continue; // Skip reserved platform register + gprPhysPool.SetAvailable(i); + } + + auto& fprPhysPool = raParam.GetPhysRegPool(IMLRegFormat::F64); + for (auto i = IMLArchAArch64::PHYSREG_FPR_BASE; i < IMLArchAArch64::PHYSREG_FPR_BASE + IMLArchAArch64::PHYSREG_FPR_COUNT; i++) + fprPhysPool.SetAvailable(i); +#endif IMLRegisterAllocator_AllocateRegisters(&ppcImlGenContext, raParam); } @@ -679,8 +704,11 @@ void PPCRecompiler_init() debug_printf("Allocating %dMB for recompiler instance data...\n", (sint32)(sizeof(PPCRecompilerInstanceData_t) / 1024 / 1024)); ppcRecompilerInstanceData = (PPCRecompilerInstanceData_t*)MemMapper::ReserveMemory(nullptr, sizeof(PPCRecompilerInstanceData_t), MemMapper::PAGE_PERMISSION::P_RW); MemMapper::AllocateMemory(&(ppcRecompilerInstanceData->_x64XMM_xorNegateMaskBottom), sizeof(PPCRecompilerInstanceData_t) - offsetof(PPCRecompilerInstanceData_t, _x64XMM_xorNegateMaskBottom), MemMapper::PAGE_PERMISSION::P_RW, true); +#ifdef ARCH_X86_64 PPCRecompilerX64Gen_generateRecompilerInterfaceFunctions(); - +#elif defined(__aarch64__) + PPCRecompilerAArch64Gen_generateRecompilerInterfaceFunctions(); +#endif PPCRecompiler_allocateRange(0, 0x1000); // the first entry is used for fallback to interpreter PPCRecompiler_allocateRange(mmuRange_TRAMPOLINE_AREA.getBase(), mmuRange_TRAMPOLINE_AREA.getSize()); PPCRecompiler_allocateRange(mmuRange_CODECAVE.getBase(), mmuRange_CODECAVE.getSize()); From 61484598fc21fb127d8499691b72309942ee5e97 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 10 May 2025 09:48:35 +0200 Subject: [PATCH 261/299] Vulkan: Use per-pipeline buffer robustness And if the extension is not supported then fallback to enabling robust buffer access for all shaders. --- .../Vulkan/VulkanPipelineCompiler.cpp | 51 ++++++++++++++++++- .../Renderer/Vulkan/VulkanPipelineCompiler.h | 5 +- .../Vulkan/VulkanPipelineStableCache.cpp | 8 +-- .../Latte/Renderer/Vulkan/VulkanRenderer.cpp | 40 +++++++++++++-- .../HW/Latte/Renderer/Vulkan/VulkanRenderer.h | 1 + .../Renderer/Vulkan/VulkanRendererCore.cpp | 3 +- 6 files changed, 96 insertions(+), 12 deletions(-) diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp index 1ea522dc..7555c03a 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.cpp @@ -873,7 +873,7 @@ void PipelineCompiler::InitDynamicState(PipelineInfo* pipelineInfo, bool usesBle dynamicState.pDynamicStates = dynamicStates.data(); } -bool PipelineCompiler::InitFromCurrentGPUState(PipelineInfo* pipelineInfo, const LatteContextRegister& latteRegister, VKRObjectRenderPass* renderPassObj) +bool PipelineCompiler::InitFromCurrentGPUState(PipelineInfo* pipelineInfo, const LatteContextRegister& latteRegister, VKRObjectRenderPass* renderPassObj, bool requireRobustBufferAccess) { VulkanRenderer* vkRenderer = VulkanRenderer::GetInstance(); @@ -888,6 +888,7 @@ bool PipelineCompiler::InitFromCurrentGPUState(PipelineInfo* pipelineInfo, const m_vkGeometryShader = pipelineInfo->geometryShaderVk; m_vkrObjPipeline = pipelineInfo->m_vkrObjPipeline; m_renderPassObj = renderPassObj; + m_requestRobustBufferAccess = requireRobustBufferAccess; // if required generate RECT emulation geometry shader if (!vkRenderer->m_featureControl.deviceExtensions.nv_fill_rectangle && isPrimitiveRect) @@ -998,6 +999,8 @@ bool PipelineCompiler::Compile(bool forceCompile, bool isRenderThread, bool show if (!forceCompile) pipelineInfo.flags |= VK_PIPELINE_CREATE_FAIL_ON_PIPELINE_COMPILE_REQUIRED_BIT_EXT; + void* prevStruct = nullptr; + VkPipelineCreationFeedbackCreateInfoEXT creationFeedbackInfo; VkPipelineCreationFeedbackEXT creationFeedback; std::vector<VkPipelineCreationFeedbackEXT> creationStageFeedback(0); @@ -1015,9 +1018,25 @@ bool PipelineCompiler::Compile(bool forceCompile, bool isRenderThread, bool show creationFeedbackInfo.pPipelineCreationFeedback = &creationFeedback; creationFeedbackInfo.pPipelineStageCreationFeedbacks = creationStageFeedback.data(); creationFeedbackInfo.pipelineStageCreationFeedbackCount = pipelineInfo.stageCount; - pipelineInfo.pNext = &creationFeedbackInfo; + creationFeedbackInfo.pNext = prevStruct; + prevStruct = &creationFeedbackInfo; } + VkPipelineRobustnessCreateInfoEXT pipelineRobustnessCreateInfo{}; + if (vkRenderer->m_featureControl.deviceExtensions.pipeline_robustness && m_requestRobustBufferAccess) + { + // per-pipeline handling of robust buffer access, if the extension is not available then we fall back to device feature robustBufferAccess + pipelineRobustnessCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_ROBUSTNESS_CREATE_INFO_EXT; + pipelineRobustnessCreateInfo.pNext = prevStruct; + prevStruct = &pipelineRobustnessCreateInfo; + pipelineRobustnessCreateInfo.storageBuffers = VK_PIPELINE_ROBUSTNESS_BUFFER_BEHAVIOR_ROBUST_BUFFER_ACCESS_EXT; + pipelineRobustnessCreateInfo.uniformBuffers = VK_PIPELINE_ROBUSTNESS_BUFFER_BEHAVIOR_ROBUST_BUFFER_ACCESS_EXT; + pipelineRobustnessCreateInfo.vertexInputs = VK_PIPELINE_ROBUSTNESS_BUFFER_BEHAVIOR_DEVICE_DEFAULT_EXT; + pipelineRobustnessCreateInfo.images = VK_PIPELINE_ROBUSTNESS_IMAGE_BEHAVIOR_DEVICE_DEFAULT_EXT; + } + + pipelineInfo.pNext = prevStruct; + VkPipeline pipeline = VK_NULL_HANDLE; VkResult result; uint8 retryCount = 0; @@ -1075,3 +1094,31 @@ void PipelineCompiler::TrackAsCached(uint64 baseHash, uint64 pipelineStateHash) return; pipelineCache.AddCurrentStateToCache(baseHash, pipelineStateHash); } + +// calculate whether the pipeline requires robust buffer access +// if there is a potential risk for a shader to do out-of-bounds reads or writes we need to enable robust buffer access +// this can happen when: +// - Streamout is used with too small of a buffer (probably? Could also be some issue with how the streamout array index is calculated -> We can maybe fix this in the future) +// - The shader uses dynamic indices for uniform access. This will trigger the uniform mode to be FULL_CBANK +bool PipelineCompiler::CalcRobustBufferAccessRequirement(LatteDecompilerShader* vertexShader, LatteDecompilerShader* pixelShader, LatteDecompilerShader* geometryShader) +{ + bool requiresRobustBufferAcces = false; + if (vertexShader) + { + cemu_assert_debug(vertexShader->shaderType == LatteConst::ShaderType::Vertex); + requiresRobustBufferAcces |= vertexShader->hasStreamoutBufferWrite; + requiresRobustBufferAcces |= vertexShader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK; + } + if (geometryShader) + { + cemu_assert_debug(geometryShader->shaderType == LatteConst::ShaderType::Geometry); + requiresRobustBufferAcces |= geometryShader->hasStreamoutBufferWrite; + requiresRobustBufferAcces |= geometryShader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK; + } + if (pixelShader) + { + cemu_assert_debug(pixelShader->shaderType == LatteConst::ShaderType::Pixel); + requiresRobustBufferAcces |= pixelShader->uniformMode == LATTE_DECOMPILER_UNIFORM_MODE_FULL_CBANK; + } + return requiresRobustBufferAcces; +} diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.h index 7879b932..7297049e 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineCompiler.h @@ -38,11 +38,14 @@ public: RendererShaderVk* m_vkPixelShader{}; RendererShaderVk* m_vkGeometryShader{}; - bool InitFromCurrentGPUState(PipelineInfo* pipelineInfo, const LatteContextRegister& latteRegister, VKRObjectRenderPass* renderPassObj); + bool InitFromCurrentGPUState(PipelineInfo* pipelineInfo, const LatteContextRegister& latteRegister, VKRObjectRenderPass* renderPassObj, bool requireRobustBufferAccess); void TrackAsCached(uint64 baseHash, uint64 pipelineStateHash); // stores pipeline to permanent cache if not yet cached. Must be called synchronously from render thread due to dependency on GPU state + static bool CalcRobustBufferAccessRequirement(LatteDecompilerShader* vertexShader, LatteDecompilerShader* pixelShader, LatteDecompilerShader* geometryShader); + VkPipelineLayout m_pipelineLayout; VKRObjectRenderPass* m_renderPassObj{}; + bool m_requestRobustBufferAccess{false}; /* shader stages */ std::vector<VkPipelineShaderStageCreateInfo> shaderStages; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp index 123120d3..9f8f4491 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanPipelineStableCache.cpp @@ -277,8 +277,9 @@ void VulkanPipelineStableCache::LoadPipelineFromCache(std::span<uint8> fileData) m_pipelineIsCachedLock.unlock(); // compile { - PipelineCompiler pp; - if (!pp.InitFromCurrentGPUState(pipelineInfo, *lcr, renderPass)) + PipelineCompiler pipelineCompiler; + bool requiresRobustBufferAccess = PipelineCompiler::CalcRobustBufferAccessRequirement(vertexShader, pixelShader, geometryShader); + if (!pipelineCompiler.InitFromCurrentGPUState(pipelineInfo, *lcr, renderPass, requiresRobustBufferAccess)) { s_spinlockSharedInternal.lock(); delete lcr; @@ -286,8 +287,7 @@ void VulkanPipelineStableCache::LoadPipelineFromCache(std::span<uint8> fileData) s_spinlockSharedInternal.unlock(); return; } - pp.Compile(true, true, false); - // destroy pp early + pipelineCompiler.Compile(true, true, false); } // on success, calculate pipeline hash and flag as present in cache uint64 pipelineBaseHash = vertexShader->baseHash; diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp index a88c3818..aed0db25 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.cpp @@ -50,7 +50,8 @@ const std::vector<const char*> kOptionalDeviceExtensions = VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME, VK_KHR_PRESENT_WAIT_EXTENSION_NAME, VK_KHR_PRESENT_ID_EXTENSION_NAME, - VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME + VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME, + VK_EXT_PIPELINE_ROBUSTNESS_EXTENSION_NAME }; const std::vector<const char*> kRequiredDeviceExtensions = @@ -263,6 +264,14 @@ void VulkanRenderer::GetDeviceFeatures() pwf.pNext = prevStruct; prevStruct = &pwf; + VkPhysicalDevicePipelineRobustnessFeaturesEXT pprf{}; + if (m_featureControl.deviceExtensions.pipeline_robustness) + { + pprf.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_ROBUSTNESS_FEATURES_EXT; + pprf.pNext = prevStruct; + prevStruct = &pprf; + } + VkPhysicalDeviceFeatures2 physicalDeviceFeatures2{}; physicalDeviceFeatures2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; physicalDeviceFeatures2.pNext = prevStruct; @@ -317,6 +326,11 @@ void VulkanRenderer::GetDeviceFeatures() { cemuLog_log(LogType::Force, "VK_EXT_depth_clip_enable not supported"); } + if (m_featureControl.deviceExtensions.pipeline_robustness) + { + if ( pprf.pipelineRobustness != VK_TRUE ) + m_featureControl.deviceExtensions.pipeline_robustness = false; + } // get limits m_featureControl.limits.minUniformBufferOffsetAlignment = std::max(prop2.properties.limits.minUniformBufferOffsetAlignment, (VkDeviceSize)4); m_featureControl.limits.nonCoherentAtomSize = std::max(prop2.properties.limits.nonCoherentAtomSize, (VkDeviceSize)4); @@ -475,11 +489,17 @@ VulkanRenderer::VulkanRenderer() deviceFeatures.occlusionQueryPrecise = VK_TRUE; deviceFeatures.depthClamp = VK_TRUE; deviceFeatures.depthBiasClamp = VK_TRUE; - if (m_vendor == GfxVendor::AMD) + + if (m_featureControl.deviceExtensions.pipeline_robustness) { - deviceFeatures.robustBufferAccess = VK_TRUE; - cemuLog_log(LogType::Force, "Enable robust buffer access"); + deviceFeatures.robustBufferAccess = VK_FALSE; } + else + { + cemuLog_log(LogType::Force, "VK_EXT_pipeline_robustness not supported. Falling back to robustBufferAccess"); + deviceFeatures.robustBufferAccess = VK_TRUE; + } + if (m_featureControl.mode.useTFEmulationViaSSBO) { deviceFeatures.vertexPipelineStoresAndAtomics = true; @@ -524,6 +544,15 @@ VulkanRenderer::VulkanRenderer() deviceExtensionFeatures = &presentWaitFeature; presentWaitFeature.presentWait = VK_TRUE; } + // enable VK_EXT_pipeline_robustness + VkPhysicalDevicePipelineRobustnessFeaturesEXT pipelineRobustnessFeature{}; + if (m_featureControl.deviceExtensions.pipeline_robustness) + { + pipelineRobustnessFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PIPELINE_ROBUSTNESS_FEATURES_EXT; + pipelineRobustnessFeature.pNext = deviceExtensionFeatures; + deviceExtensionFeatures = &pipelineRobustnessFeature; + pipelineRobustnessFeature.pipelineRobustness = VK_TRUE; + } std::vector<const char*> used_extensions; VkDeviceCreateInfo createInfo = CreateDeviceCreateInfo(queueCreateInfos, deviceFeatures, deviceExtensionFeatures, used_extensions); @@ -1127,6 +1156,8 @@ VkDeviceCreateInfo VulkanRenderer::CreateDeviceCreateInfo(const std::vector<VkDe used_extensions.emplace_back(VK_KHR_PRESENT_ID_EXTENSION_NAME); used_extensions.emplace_back(VK_KHR_PRESENT_WAIT_EXTENSION_NAME); } + if (m_featureControl.deviceExtensions.pipeline_robustness) + used_extensions.emplace_back(VK_EXT_PIPELINE_ROBUSTNESS_EXTENSION_NAME); VkDeviceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; @@ -1224,6 +1255,7 @@ bool VulkanRenderer::CheckDeviceExtensionSupport(const VkPhysicalDevice device, info.deviceExtensions.shader_float_controls = isExtensionAvailable(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME); info.deviceExtensions.dynamic_rendering = false; // isExtensionAvailable(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); info.deviceExtensions.depth_clip_enable = isExtensionAvailable(VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME); + info.deviceExtensions.pipeline_robustness = isExtensionAvailable(VK_EXT_PIPELINE_ROBUSTNESS_EXTENSION_NAME); // dynamic rendering doesn't provide any benefits for us right now. Driver implementations are very unoptimized as of Feb 2022 info.deviceExtensions.present_wait = isExtensionAvailable(VK_KHR_PRESENT_WAIT_EXTENSION_NAME) && isExtensionAvailable(VK_KHR_PRESENT_ID_EXTENSION_NAME); diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h index 933043d3..5cc0a6f1 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h @@ -453,6 +453,7 @@ private: bool shader_float_controls = false; // VK_KHR_shader_float_controls bool present_wait = false; // VK_KHR_present_wait bool depth_clip_enable = false; // VK_EXT_depth_clip_enable + bool pipeline_robustness = false; // VK_EXT_pipeline_robustness }deviceExtensions; struct diff --git a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp index bd49a69e..32ef7007 100644 --- a/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp +++ b/src/Cafe/HW/Latte/Renderer/Vulkan/VulkanRendererCore.cpp @@ -298,7 +298,8 @@ PipelineInfo* VulkanRenderer::draw_createGraphicsPipeline(uint32 indexCount) // init pipeline compiler PipelineCompiler* pipelineCompiler = new PipelineCompiler(); - pipelineCompiler->InitFromCurrentGPUState(pipelineInfo, LatteGPUState.contextNew, vkFBO->GetRenderPassObj()); + bool requiresRobustBufferAccess = PipelineCompiler::CalcRobustBufferAccessRequirement(vertexShader, pixelShader, geometryShader); + pipelineCompiler->InitFromCurrentGPUState(pipelineInfo, LatteGPUState.contextNew, vkFBO->GetRenderPassObj(), requiresRobustBufferAccess); pipelineCompiler->TrackAsCached(vsBaseHash, pipelineHash); // use heuristics based on parameter patterns to determine if the current drawcall is essential (non-skipable) From f801fc1fe85f1e3444f0c1aa7636f52d7c1b7966 Mon Sep 17 00:00:00 2001 From: neebyA <126654084+neebyA@users.noreply.github.com> Date: Sun, 11 May 2025 15:08:25 -0700 Subject: [PATCH 262/299] Fix Mac build for Xcode 16.3 (#1558) --- src/util/helpers/StringHelpers.h | 86 ++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/src/util/helpers/StringHelpers.h b/src/util/helpers/StringHelpers.h index 54141808..a98344d6 100644 --- a/src/util/helpers/StringHelpers.h +++ b/src/util/helpers/StringHelpers.h @@ -2,6 +2,92 @@ #include "boost/nowide/convert.hpp" #include <charconv> +// Definition for removed templates in Apple Clang 17 +#if defined(__apple_build_version__) && (__apple_build_version__ >= 17000000) +namespace std { + template<> + struct char_traits<uint16be> { + using char_type = uint16be; + using int_type = int; + using off_type = streamoff; + using pos_type = streampos; + using state_type = mbstate_t; + + static inline void constexpr assign(char_type& c1, const char_type& c2) noexcept { + c1 = c2; + } + + static inline constexpr bool eq(char_type c1, char_type c2) noexcept { + return c1 == c2; + } + + static inline constexpr bool lt(char_type c1, char_type c2) noexcept { + return c1 < c2; + } + + static constexpr int compare(const char_type* s1, const char_type* s2, size_t n) { + for (; n; --n, ++s1, ++s2) { + if (lt(*s1, *s2)) return -1; + if (lt(*s2, *s1)) return 1; + } + return 0; + } + + static constexpr size_t length(const char_type* s) { + size_t len = 0; + for (; !eq(*s, char_type(0)); ++s) ++len; + return len; + } + + static constexpr const char_type* find(const char_type* s, size_t n, const char_type& a) { + for (; n; --n) { + if (eq(*s, a)) + return s; + ++s; + } + return nullptr; + } + + static constexpr char_type* move(char_type* s1, const char_type* s2, size_t n) { + if (n == 0) return s1; + return static_cast<char_type*>(memmove(s1, s2, n * sizeof(char_type))); + } + + static constexpr char_type* copy(char_type* s1, const char_type* s2, size_t n) { + if (n == 0) return s1; + return static_cast<char_type*>(memcpy(s1, s2, n * sizeof(char_type))); + } + + static constexpr char_type* assign(char_type* s, size_t n, char_type a) { + char_type* r = s; + for (; n; --n, ++s) + assign(*s, a); + return r; + } + + static inline constexpr char_type to_char_type(int_type c) noexcept { + return char_type(c); + } + + static inline constexpr int_type to_int_type(char_type c) noexcept { + return int_type(c); + } + + static inline constexpr bool eq_int_type(int_type c1, int_type c2) noexcept { + return c1 == c2; + } + + static inline constexpr int_type eof() noexcept { + return static_cast<int_type>(EOF); + } + + static inline constexpr int_type not_eof(int_type c) noexcept { + return eq_int_type(c, eof()) ? ~eof() : c; + } + }; +} +#endif + // todo - move the Cafe/PPC specific parts to CafeString.h eventually namespace StringHelpers { From caef34f2ff8623b1cbdc8b3d8e9ede619da8f5fb Mon Sep 17 00:00:00 2001 From: Joshua de Reeper <joshua@dereeper.co.nz> Date: Mon, 12 May 2025 09:28:01 +0200 Subject: [PATCH 263/299] nsyshid: Add Kamen Rider USB Device to Whitelist (#1560) --- src/Cafe/OS/libs/nsyshid/Whitelist.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Cafe/OS/libs/nsyshid/Whitelist.cpp b/src/Cafe/OS/libs/nsyshid/Whitelist.cpp index ae965096..783384ec 100644 --- a/src/Cafe/OS/libs/nsyshid/Whitelist.cpp +++ b/src/Cafe/OS/libs/nsyshid/Whitelist.cpp @@ -20,6 +20,8 @@ namespace nsyshid m_devices.emplace_back(0x1430, 0x1F17); // disney infinity base m_devices.emplace_back(0x0e6f, 0x0129); + // kamen rider ride gate + m_devices.emplace_back(0x0e6f, 0x200A); } } From 05617a332b059fd630420784b04d12099079d25a Mon Sep 17 00:00:00 2001 From: Mefiresu <15063879+Mefiresu@users.noreply.github.com> Date: Mon, 12 May 2025 18:16:14 +0200 Subject: [PATCH 264/299] dmae: Implement 16bit endian swap for DMAECopyMem (#1564) --- src/Cafe/OS/libs/dmae/dmae.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Cafe/OS/libs/dmae/dmae.cpp b/src/Cafe/OS/libs/dmae/dmae.cpp index 7c513784..c35fce2e 100644 --- a/src/Cafe/OS/libs/dmae/dmae.cpp +++ b/src/Cafe/OS/libs/dmae/dmae.cpp @@ -36,6 +36,16 @@ void dmaeExport_DMAECopyMem(PPCInterpreter_t* hCPU) dstBuffer[i] = _swapEndianU32(srcBuffer[i]); } } + else if( hCPU->gpr[6] == DMAE_ENDIAN_16 ) + { + // swap per uint16 + uint16* srcBuffer = (uint16*)memory_getPointerFromVirtualOffset(hCPU->gpr[4]); + uint16* dstBuffer = (uint16*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + for(uint32 i=0; i<hCPU->gpr[5]*2; i++) + { + dstBuffer[i] = _swapEndianU16(srcBuffer[i]); + } + } else { cemuLog_logDebug(LogType::Force, "DMAECopyMem(): Unsupported endian swap\n"); From 996539fce85ebb902d9e91ccd905331d65dda52e Mon Sep 17 00:00:00 2001 From: shinra-electric <50119606+shinra-electric@users.noreply.github.com> Date: Mon, 12 May 2025 19:12:40 +0100 Subject: [PATCH 265/299] CI: Bump MoltenVK to v1.3.0 (#1559) --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6ae4b892..c7cbc202 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -202,7 +202,7 @@ jobs: - name: "Install molten-vk" run: | - curl -L -O https://github.com/KhronosGroup/MoltenVK/releases/download/v1.2.9/MoltenVK-macos.tar + curl -L -O https://github.com/KhronosGroup/MoltenVK/releases/download/v1.3.0/MoltenVK-macos.tar tar xf MoltenVK-macos.tar sudo mkdir -p /usr/local/lib sudo cp MoltenVK/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib /usr/local/lib From bed5fdb1951814633a3eaea0445bcddb62b63df9 Mon Sep 17 00:00:00 2001 From: Issa <32466950+gamersalpha@users.noreply.github.com> Date: Tue, 13 May 2025 03:38:11 +0200 Subject: [PATCH 266/299] UI: Disable Ctrl+Q shortcut on non-macOS platforms to prevent accidental exits during gameplay (#1565) --- src/gui/MainWindow.cpp | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index f2a5a2fa..2f63b460 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -1448,15 +1448,23 @@ void MainWindow::OnKeyUp(wxKeyEvent& event) void MainWindow::OnKeyDown(wxKeyEvent& event) { - if ((event.AltDown() && event.GetKeyCode() == WXK_F4) || - (event.CmdDown() && event.GetKeyCode() == 'Q')) - { - Close(true); - } - else - { - event.Skip(); - } +#if defined(__APPLE__) + // On macOS, allow Cmd+Q to quit the application + if (event.CmdDown() && event.GetKeyCode() == 'Q') + { + Close(true); + } +#else + // On Windows/Linux, only Alt+F4 is allowed for quitting + if (event.AltDown() && event.GetKeyCode() == WXK_F4) + { + Close(true); + } +#endif + else + { + event.Skip(); + } } void MainWindow::OnChar(wxKeyEvent& event) From 111637a9fd7a18ed7948399244be9dcdbb7ce284 Mon Sep 17 00:00:00 2001 From: gamerbross <55158797+gamerbross@users.noreply.github.com> Date: Thu, 15 May 2025 19:58:43 +0200 Subject: [PATCH 267/299] nsyshid: Skylander 360 Portal small optimization and code formatting (#1568) --- src/Cafe/OS/libs/nsyshid/SkylanderXbox360.cpp | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Cafe/OS/libs/nsyshid/SkylanderXbox360.cpp b/src/Cafe/OS/libs/nsyshid/SkylanderXbox360.cpp index c8755982..eba8ff9e 100644 --- a/src/Cafe/OS/libs/nsyshid/SkylanderXbox360.cpp +++ b/src/Cafe/OS/libs/nsyshid/SkylanderXbox360.cpp @@ -3,7 +3,7 @@ namespace nsyshid { SkylanderXbox360PortalLibusb::SkylanderXbox360PortalLibusb(std::shared_ptr<Device> usbPortal) - : Device(0x1430, 0x0150, 1, 2, 0) + : Device(0x1430, 0x0150, 1, 2, 0) { m_IsOpened = false; m_usbPortal = std::static_pointer_cast<backend::libusb::DeviceLibusb>(usbPortal); @@ -26,7 +26,7 @@ namespace nsyshid Device::ReadResult SkylanderXbox360PortalLibusb::Read(ReadMessage* message) { - std::vector<uint8> xboxData(std::min<uint32>(32, message->length + sizeof(XBOX_DATA_HEADER)), 0); + std::vector<uint8> xboxData(std::min<uint32>(32, message->length + sizeof(XBOX_DATA_HEADER))); memcpy(xboxData.data(), XBOX_DATA_HEADER, sizeof(XBOX_DATA_HEADER)); memcpy(xboxData.data() + sizeof(XBOX_DATA_HEADER), message->data, message->length - sizeof(XBOX_DATA_HEADER)); @@ -45,7 +45,7 @@ namespace nsyshid if (message->data[0] == 'M' && message->data[1] == 0x01) // Enables Speaker g72x_init_state(&m_state); - std::vector<uint8> xboxData(message->length + sizeof(XBOX_DATA_HEADER), 0); + std::vector<uint8> xboxData(message->length + sizeof(XBOX_DATA_HEADER)); memcpy(xboxData.data(), XBOX_DATA_HEADER, sizeof(XBOX_DATA_HEADER)); memcpy(xboxData.data() + sizeof(XBOX_DATA_HEADER), message->data, message->length); @@ -61,7 +61,7 @@ namespace nsyshid { std::vector<uint8> audioData(message->data, message->data + message->length); - std::vector<uint8_t> xboxAudioData; + std::vector<uint8_t> xboxAudioData(audioData.size() / 4); for (size_t i = 0; i < audioData.size(); i += 4) { int16_t sample1 = (static_cast<int16_t>(audioData[i + 1]) << 8) | audioData[i]; @@ -70,10 +70,10 @@ namespace nsyshid uint8_t encoded1 = g721_encoder(sample1, &m_state) & 0x0F; uint8_t encoded2 = g721_encoder(sample2, &m_state) & 0x0F; - xboxAudioData.push_back((encoded2 << 4) | encoded1); + xboxAudioData[i / 4] = ((encoded2 << 4) | encoded1); } - std::vector<uint8> xboxData(xboxAudioData.size() + sizeof(XBOX_AUDIO_DATA_HEADER), 0); + std::vector<uint8> xboxData(xboxAudioData.size() + sizeof(XBOX_AUDIO_DATA_HEADER)); memcpy(xboxData.data(), XBOX_AUDIO_DATA_HEADER, sizeof(XBOX_AUDIO_DATA_HEADER)); memcpy(xboxData.data() + sizeof(XBOX_AUDIO_DATA_HEADER), xboxAudioData.data(), xboxAudioData.size()); @@ -123,32 +123,32 @@ namespace nsyshid *(uint16be*)(currentWritePtr + 7) = 0x001D; // wDescriptorLength currentWritePtr = currentWritePtr + 9; // endpoint descriptor 1 - *(uint8*)(currentWritePtr + 0) = 7; // bLength - *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType - *(uint8*)(currentWritePtr + 2) = 0x81; // bEndpointAddress - *(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes + *(uint8*)(currentWritePtr + 0) = 7; // bLength + *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType + *(uint8*)(currentWritePtr + 2) = 0x81; // bEndpointAddress + *(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes *(uint16be*)(currentWritePtr + 4) = 0x0040; // wMaxPacketSize - *(uint8*)(currentWritePtr + 6) = 0x01; // bInterval + *(uint8*)(currentWritePtr + 6) = 0x01; // bInterval currentWritePtr = currentWritePtr + 7; // endpoint descriptor 2 - *(uint8*)(currentWritePtr + 0) = 7; // bLength - *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType - *(uint8*)(currentWritePtr + 2) = 0x02; // bEndpointAddress - *(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes + *(uint8*)(currentWritePtr + 0) = 7; // bLength + *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType + *(uint8*)(currentWritePtr + 2) = 0x02; // bEndpointAddress + *(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes *(uint16be*)(currentWritePtr + 4) = 0x0040; // wMaxPacketSize - *(uint8*)(currentWritePtr + 6) = 0x01; // bInterval + *(uint8*)(currentWritePtr + 6) = 0x01; // bInterval currentWritePtr = currentWritePtr + 7; cemu_assert_debug((currentWritePtr - configurationDescriptor) == 0x29); memcpy(output, configurationDescriptor, - std::min<uint32>(outputMaxLength, sizeof(configurationDescriptor))); + std::min<uint32>(outputMaxLength, sizeof(configurationDescriptor))); return true; } bool SkylanderXbox360PortalLibusb::SetIdle(uint8 ifIndex, - uint8 reportId, - uint8 duration) + uint8 reportId, + uint8 duration) { return true; } @@ -157,4 +157,4 @@ namespace nsyshid { return true; } -} \ No newline at end of file +} // namespace nsyshid \ No newline at end of file From 96765e4ac6f4efddc912da54f5c43209e994185c Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 10 May 2025 13:58:43 +0200 Subject: [PATCH 268/299] Latte: Refactor clear code --- src/Cafe/OS/libs/gx2/GX2_Blit.cpp | 175 ++++++++++++++---------------- 1 file changed, 83 insertions(+), 92 deletions(-) diff --git a/src/Cafe/OS/libs/gx2/GX2_Blit.cpp b/src/Cafe/OS/libs/gx2/GX2_Blit.cpp index c8080f38..db21c9af 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Blit.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_Blit.cpp @@ -82,35 +82,89 @@ namespace GX2 } } - void GX2ClearColor(GX2ColorBuffer* colorBuffer, float r, float g, float b, float a) + void SubmitHLEClear(GX2ColorBuffer* colorBuffer, float colorRGBA[4], GX2DepthBuffer* depthBuffer, float depthClearValue, uint8 stencilClearValue, bool clearColor, bool clearDepth, bool clearStencil) { GX2ReserveCmdSpace(50); + uint32 hleClearFlags = 0; + if (clearColor) + hleClearFlags |= 1; + if (clearDepth) + hleClearFlags |= 2; + if (clearStencil) + hleClearFlags |= 4; + // color buffer + MPTR colorPhysAddr = MPTR_NULL; + uint32 colorFormat = 0; + uint32 colorTileMode = 0; + uint32 colorWidth = 0; + uint32 colorHeight = 0; + uint32 colorPitch = 0; + uint32 colorFirstSlice = 0; + uint32 colorNumSlices = 0; + if (colorBuffer != nullptr) + { + colorPhysAddr = memory_virtualToPhysical(colorBuffer->surface.imagePtr); + colorFormat = (uint32)colorBuffer->surface.format.value(); + colorTileMode = (uint32)colorBuffer->surface.tileMode.value(); + colorWidth = colorBuffer->surface.width; + colorHeight = colorBuffer->surface.height; + colorPitch = colorBuffer->surface.pitch; + colorFirstSlice = _swapEndianU32(colorBuffer->viewFirstSlice); + colorNumSlices = _swapEndianU32(colorBuffer->viewNumSlices); + } + // depth buffer + MPTR depthPhysAddr = MPTR_NULL; + uint32 depthFormat = 0; + uint32 depthTileMode = 0; + uint32 depthWidth = 0; + uint32 depthHeight = 0; + uint32 depthPitch = 0; + uint32 depthFirstSlice = 0; + uint32 depthNumSlices = 0; + if (depthBuffer != nullptr) + { + depthPhysAddr = memory_virtualToPhysical(depthBuffer->surface.imagePtr); + depthFormat = (uint32)depthBuffer->surface.format.value(); + depthTileMode = (uint32)depthBuffer->surface.tileMode.value(); + depthWidth = depthBuffer->surface.width; + depthHeight = depthBuffer->surface.height; + depthPitch = depthBuffer->surface.pitch; + depthFirstSlice = _swapEndianU32(depthBuffer->viewFirstSlice); + depthNumSlices = _swapEndianU32(depthBuffer->viewNumSlices); + } + + gx2WriteGather_submit(pm4HeaderType3(IT_HLE_CLEAR_COLOR_DEPTH_STENCIL, 23), + hleClearFlags, + colorPhysAddr, + colorFormat, + colorTileMode, + colorWidth, + colorHeight, + colorPitch, + colorFirstSlice, + colorNumSlices, + depthPhysAddr, + depthFormat, + depthTileMode, + depthWidth, + depthHeight, + depthPitch, + depthFirstSlice, + depthNumSlices, + (uint32)(colorRGBA[0] * 255.0f), + (uint32)(colorRGBA[1] * 255.0f), + (uint32)(colorRGBA[2] * 255.0f), + (uint32)(colorRGBA[3] * 255.0f), + *(uint32*)&depthClearValue, + stencilClearValue&0xFF); + } + + void GX2ClearColor(GX2ColorBuffer* colorBuffer, float r, float g, float b, float a) + { if ((colorBuffer->surface.resFlag & GX2_RESFLAG_USAGE_COLOR_BUFFER) != 0) { - gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_CLEAR_COLOR_DEPTH_STENCIL, 23)); - gx2WriteGather_submitU32AsBE(1); // color (1) - gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(colorBuffer->surface.imagePtr)); - gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.format.value()); - gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.tileMode.value()); - gx2WriteGather_submitU32AsBE(colorBuffer->surface.width); - gx2WriteGather_submitU32AsBE(colorBuffer->surface.height); - gx2WriteGather_submitU32AsBE(colorBuffer->surface.pitch); - gx2WriteGather_submitU32AsBE(_swapEndianU32(colorBuffer->viewFirstSlice)); - gx2WriteGather_submitU32AsBE(_swapEndianU32(colorBuffer->viewNumSlices)); - gx2WriteGather_submitU32AsBE(MPTR_NULL); - gx2WriteGather_submitU32AsBE(0); // depth buffer format - gx2WriteGather_submitU32AsBE(0); // tilemode for depth buffer - gx2WriteGather_submitU32AsBE(0); - gx2WriteGather_submitU32AsBE(0); - gx2WriteGather_submitU32AsBE(0); - gx2WriteGather_submitU32AsBE(0); - gx2WriteGather_submitU32AsBE(0); - gx2WriteGather_submitU32AsBE((uint32)(r * 255.0f)); - gx2WriteGather_submitU32AsBE((uint32)(g * 255.0f)); - gx2WriteGather_submitU32AsBE((uint32)(b * 255.0f)); - gx2WriteGather_submitU32AsBE((uint32)(a * 255.0f)); - gx2WriteGather_submitU32AsBE(0); // clear depth - gx2WriteGather_submitU32AsBE(0); // clear stencil + float colorRGBA[4] = { r, g, b, a }; + SubmitHLEClear(colorBuffer, colorRGBA, nullptr, 0.0f, 0, true, false, false); } else { @@ -120,7 +174,6 @@ namespace GX2 void GX2ClearBuffersEx(GX2ColorBuffer* colorBuffer, GX2DepthBuffer* depthBuffer, float r, float g, float b, float a, float depthClearValue, uint8 stencilClearValue, GX2ClearFlags clearFlags) { - GX2ReserveCmdSpace(50); _updateDepthStencilClearRegs(depthClearValue, stencilClearValue, clearFlags); uint32 hleClearFlags = 0; @@ -130,42 +183,13 @@ namespace GX2 hleClearFlags |= 4; hleClearFlags |= 1; - // send command to clear color, depth and stencil - if (_swapEndianU32(colorBuffer->viewFirstSlice) != 0) - debugBreakpoint(); - gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_CLEAR_COLOR_DEPTH_STENCIL, 23)); - gx2WriteGather_submitU32AsBE(hleClearFlags); // color (1), depth (2), stencil (4) - gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(colorBuffer->surface.imagePtr)); - gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.format.value()); - gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.tileMode.value()); - gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.width); - gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.height); - gx2WriteGather_submitU32AsBE((uint32)colorBuffer->surface.pitch); - gx2WriteGather_submitU32AsBE(_swapEndianU32(colorBuffer->viewFirstSlice)); - gx2WriteGather_submitU32AsBE(_swapEndianU32(colorBuffer->viewNumSlices)); - gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(depthBuffer->surface.imagePtr)); - gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.format.value()); - gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.tileMode.value()); - gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.width); - gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.height); - gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.pitch); - gx2WriteGather_submitU32AsBE(_swapEndianU32(depthBuffer->viewFirstSlice)); - gx2WriteGather_submitU32AsBE(_swapEndianU32(depthBuffer->viewNumSlices)); - - gx2WriteGather_submitU32AsBE((uint32)(r * 255.0f)); - gx2WriteGather_submitU32AsBE((uint32)(g * 255.0f)); - gx2WriteGather_submitU32AsBE((uint32)(b * 255.0f)); - gx2WriteGather_submitU32AsBE((uint32)(a * 255.0f)); - - gx2WriteGather_submitU32AsBE(*(uint32*)&depthClearValue); // clear depth - gx2WriteGather_submitU32AsBE(stencilClearValue&0xFF); // clear stencil + float colorRGBA[4] = { r, g, b, a }; + SubmitHLEClear(colorBuffer, colorRGBA, depthBuffer, depthClearValue, stencilClearValue, true, (clearFlags & GX2ClearFlags::CLEAR_DEPTH) != 0, (clearFlags & GX2ClearFlags::CLEAR_STENCIL) != 0); } // always uses passed depthClearValue/stencilClearValue for clearing, even if clear flags dont specify value updates void GX2ClearDepthStencilEx(GX2DepthBuffer* depthBuffer, float depthClearValue, uint8 stencilClearValue, GX2ClearFlags clearFlags) { - GX2ReserveCmdSpace(50); - if (!depthBuffer && (depthBuffer->surface.width == 0 || depthBuffer->surface.height == 0)) { // Super Smash Bros tries to clear an uninitialized depth surface? @@ -175,41 +199,8 @@ namespace GX2 _updateDepthStencilClearRegs(depthClearValue, stencilClearValue, clearFlags); - uint32 hleClearFlags = 0; - if ((clearFlags & GX2ClearFlags::CLEAR_DEPTH) != 0) - hleClearFlags |= 2; - if ((clearFlags & GX2ClearFlags::CLEAR_STENCIL) != 0) - hleClearFlags |= 4; - - // send command to clear color, depth and stencil - if (hleClearFlags != 0) - { - gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_CLEAR_COLOR_DEPTH_STENCIL, 23)); - gx2WriteGather_submitU32AsBE(hleClearFlags); // color (1), depth (2), stencil (4) - gx2WriteGather_submitU32AsBE(MPTR_NULL); - gx2WriteGather_submitU32AsBE(0); // format for color buffer - gx2WriteGather_submitU32AsBE(0); // tilemode for color buffer - gx2WriteGather_submitU32AsBE(0); - gx2WriteGather_submitU32AsBE(0); - gx2WriteGather_submitU32AsBE(0); - gx2WriteGather_submitU32AsBE(0); - gx2WriteGather_submitU32AsBE(0); - gx2WriteGather_submitU32AsBE(memory_virtualToPhysical(depthBuffer->surface.imagePtr)); - gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.format.value()); - gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.tileMode.value()); - gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.width); - gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.height); - gx2WriteGather_submitU32AsBE((uint32)depthBuffer->surface.pitch); - gx2WriteGather_submitU32AsBE(_swapEndianU32(depthBuffer->viewFirstSlice)); - gx2WriteGather_submitU32AsBE(_swapEndianU32(depthBuffer->viewNumSlices)); - gx2WriteGather_submitU32AsBE(0); - gx2WriteGather_submitU32AsBE(0); - gx2WriteGather_submitU32AsBE(0); - gx2WriteGather_submitU32AsBE(0); - - gx2WriteGather_submitU32AsBE(*(uint32*)&depthClearValue); // clear depth - gx2WriteGather_submitU32AsBE(stencilClearValue & 0xFF); // clear stencil - } + float colorRGBA[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + SubmitHLEClear(nullptr, colorRGBA, depthBuffer, depthClearValue, stencilClearValue, false, (clearFlags & GX2ClearFlags::CLEAR_DEPTH) != 0, (clearFlags & GX2ClearFlags::CLEAR_STENCIL) != 0); } void GX2BlitInit() From 28ea70b6d863e82bd592bddc5e031030d9a716fa Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Wed, 14 May 2025 18:59:50 +0200 Subject: [PATCH 269/299] GX2+TCL: Reimplement command buffer submission - GX2 utilizes TCL(.rpl) API for command submission instead of directly writing to an internal GPU fifo - Submission & retire timestamps are correctly implemented as incremental counters - Command buffering behaviour matches console - Fixes race conditions on aarch64 --- src/Cafe/HW/Latte/Core/Latte.h | 4 - .../HW/Latte/Core/LatteCommandProcessor.cpp | 208 +++----- src/Cafe/HW/Latte/Core/LattePM4.h | 3 +- src/Cafe/HW/Latte/Core/LatteThread.cpp | 1 - src/Cafe/OS/libs/TCL/TCL.cpp | 149 +++++- src/Cafe/OS/libs/TCL/TCL.h | 23 +- src/Cafe/OS/libs/gx2/GX2.cpp | 91 +--- src/Cafe/OS/libs/gx2/GX2.h | 8 +- src/Cafe/OS/libs/gx2/GX2_Blit.cpp | 1 - src/Cafe/OS/libs/gx2/GX2_Command.cpp | 464 +++++++++++++----- src/Cafe/OS/libs/gx2/GX2_Command.h | 44 +- src/Cafe/OS/libs/gx2/GX2_ContextState.cpp | 10 +- src/Cafe/OS/libs/gx2/GX2_Draw.cpp | 4 - src/Cafe/OS/libs/gx2/GX2_Event.cpp | 46 +- src/Cafe/OS/libs/gx2/GX2_Misc.cpp | 69 ++- src/Cafe/OS/libs/gx2/GX2_RenderTarget.cpp | 10 +- src/Cafe/OS/libs/gx2/GX2_Shader.cpp | 25 +- src/Cafe/OS/libs/gx2/GX2_State.cpp | 1 - src/Cafe/OS/libs/gx2/GX2_Surface_Copy.cpp | 6 +- src/Cafe/OS/libs/gx2/GX2_shader_legacy.cpp | 34 +- src/Common/precompiled.h | 32 ++ 21 files changed, 761 insertions(+), 472 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/Latte.h b/src/Cafe/HW/Latte/Core/Latte.h index e5e9dd5c..2636467b 100644 --- a/src/Cafe/HW/Latte/Core/Latte.h +++ b/src/Cafe/HW/Latte/Core/Latte.h @@ -47,8 +47,6 @@ struct LatteGPUState_t gx2GPUSharedArea_t* sharedArea; // quick reference to shared area MPTR sharedAreaAddr; // other - // todo: Currently we have the command buffer logic implemented as a FIFO ringbuffer. On real HW it's handled as a series of command buffers that are pushed individually. - std::atomic<uint64> lastSubmittedCommandBufferTimestamp; uint32 gx2InitCalled; // incremented every time GX2Init() is called // OpenGL control uint32 glVendor; // GLVENDOR_* @@ -75,8 +73,6 @@ struct LatteGPUState_t extern LatteGPUState_t LatteGPUState; -extern uint8* gxRingBufferReadPtr; // currently active read pointer (gx2 ring buffer or display list) - // texture #include "Cafe/HW/Latte/Core/LatteTexture.h" diff --git a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp index f592cc9e..4385cf49 100644 --- a/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp +++ b/src/Cafe/HW/Latte/Core/LatteCommandProcessor.cpp @@ -13,6 +13,7 @@ #include "Cafe/HW/Latte/Core/LattePM4.h" #include "Cafe/OS/libs/coreinit/coreinit_Time.h" +#include "Cafe/OS/libs/TCL/TCL.h" // TCL currently handles the GPU command ringbuffer #include "Cafe/CafeSystem.h" @@ -28,11 +29,6 @@ typedef uint32be* LatteCMDPtr; #define LatteReadCMD() ((uint32)*(cmd++)) #define LatteSkipCMD(_nWords) cmd += (_nWords) -uint8* gxRingBufferReadPtr; // currently active read pointer (gx2 ring buffer or display list) -uint8* gx2CPParserDisplayListPtr; -uint8* gx2CPParserDisplayListStart; // used for debugging -uint8* gx2CPParserDisplayListEnd; - void LatteThread_HandleOSScreen(); void LatteThread_Exit(); @@ -155,16 +151,12 @@ void LatteCP_signalEnterWait() */ uint32 LatteCP_readU32Deprc() { - uint32 v; - uint8* gxRingBufferWritePtr; - sint32 readDistance; // no display list active while (true) { - gxRingBufferWritePtr = gx2WriteGatherPipe.writeGatherPtrGxBuffer[GX2::sGX2MainCoreIndex]; - readDistance = (sint32)(gxRingBufferWritePtr - gxRingBufferReadPtr); - if (readDistance != 0) - break; + uint32 cmdWord; + if ( TCL::TCLGPUReadRBWord(cmdWord) ) + return cmdWord; g_renderer->NotifyLatteCommandProcessorIdle(); // let the renderer know in case it wants to flush any commands performanceMonitor.gpuTime_idleTime.beginMeasuring(); @@ -175,56 +167,8 @@ uint32 LatteCP_readU32Deprc() } LatteThread_HandleOSScreen(); // check if new frame was presented via OSScreen API - readDistance = (sint32)(gxRingBufferWritePtr - gxRingBufferReadPtr); - if (readDistance != 0) - break; - if (Latte_GetStopSignal()) - LatteThread_Exit(); - - // still no command data available, do some other tasks - LatteTiming_HandleTimedVsync(); - LatteAsyncCommands_checkAndExecute(); - std::this_thread::yield(); - performanceMonitor.gpuTime_idleTime.endMeasuring(); - } - v = *(uint32*)gxRingBufferReadPtr; - gxRingBufferReadPtr += 4; -#ifdef CEMU_DEBUG_ASSERT - if (v == 0xcdcdcdcd) - assert_dbg(); -#endif - v = _swapEndianU32(v); - return v; -} - -void LatteCP_waitForNWords(uint32 numWords) -{ - uint8* gxRingBufferWritePtr; - sint32 readDistance; - bool isFlushed = false; - sint32 waitDistance = numWords * sizeof(uint32be); - // no display list active - while (true) - { - gxRingBufferWritePtr = gx2WriteGatherPipe.writeGatherPtrGxBuffer[GX2::sGX2MainCoreIndex]; - readDistance = (sint32)(gxRingBufferWritePtr - gxRingBufferReadPtr); - if (readDistance < 0) - return; // wrap around means there is at least one full command queued after this - if (readDistance >= waitDistance) - break; - g_renderer->NotifyLatteCommandProcessorIdle(); // let the renderer know in case it wants to flush any commands - performanceMonitor.gpuTime_idleTime.beginMeasuring(); - // no command data available, spin in a busy loop for a while then check again - for (sint32 busy = 0; busy < 80; busy++) - { - _mm_pause(); - } - readDistance = (sint32)(gxRingBufferWritePtr - gxRingBufferReadPtr); - if (readDistance < 0) - return; // wrap around means there is at least one full command queued after this - if (readDistance >= waitDistance) - break; - + if ( TCL::TCLGPUReadRBWord(cmdWord) ) + return cmdWord; if (Latte_GetStopSignal()) LatteThread_Exit(); @@ -234,6 +178,7 @@ void LatteCP_waitForNWords(uint32 numWords) std::this_thread::yield(); performanceMonitor.gpuTime_idleTime.endMeasuring(); } + UNREACHABLE; } template<uint32 readU32()> @@ -270,21 +215,23 @@ void LatteCP_itIndirectBufferDepr(LatteCMDPtr cmd, uint32 nWords) cemu_assert_debug(nWords == 3); uint32 physicalAddress = LatteReadCMD(); uint32 physicalAddressHigh = LatteReadCMD(); // unused - uint32 sizeInDWords = LatteReadCMD(); - uint32 displayListSize = sizeInDWords * 4; - DrawPassContext drawPassCtx; + uint32 sizeInU32s = LatteReadCMD(); #ifdef LATTE_CP_LOGGING if (GetAsyncKeyState('A')) LatteCP_DebugPrintCmdBuffer(MEMPTR<uint32be>(physicalAddress), displayListSize); #endif - uint32be* buf = MEMPTR<uint32be>(physicalAddress).GetPtr(); - drawPassCtx.PushCurrentCommandQueuePos(buf, buf, buf + sizeInDWords); + if (sizeInU32s > 0) + { + DrawPassContext drawPassCtx; + uint32be* buf = MEMPTR<uint32be>(physicalAddress).GetPtr(); + drawPassCtx.PushCurrentCommandQueuePos(buf, buf, buf + sizeInU32s); - LatteCP_processCommandBuffer(drawPassCtx); - if (drawPassCtx.isWithinDrawPass()) - drawPassCtx.endDrawPass(); + LatteCP_processCommandBuffer(drawPassCtx); + if (drawPassCtx.isWithinDrawPass()) + drawPassCtx.endDrawPass(); + } } // pushes the command buffer to the stack @@ -294,11 +241,12 @@ void LatteCP_itIndirectBuffer(LatteCMDPtr cmd, uint32 nWords, DrawPassContext& d uint32 physicalAddress = LatteReadCMD(); uint32 physicalAddressHigh = LatteReadCMD(); // unused uint32 sizeInDWords = LatteReadCMD(); - uint32 displayListSize = sizeInDWords * 4; - cemu_assert_debug(displayListSize >= 4); - - uint32be* buf = MEMPTR<uint32be>(physicalAddress).GetPtr(); - drawPassCtx.PushCurrentCommandQueuePos(buf, buf, buf + sizeInDWords); + if (sizeInDWords > 0) + { + uint32 displayListSize = sizeInDWords * 4; + uint32be* buf = MEMPTR<uint32be>(physicalAddress).GetPtr(); + drawPassCtx.PushCurrentCommandQueuePos(buf, buf, buf + sizeInDWords); + } } LatteCMDPtr LatteCP_itStreamoutBufferUpdate(LatteCMDPtr cmd, uint32 nWords) @@ -565,26 +513,55 @@ LatteCMDPtr LatteCP_itMemWrite(LatteCMDPtr cmd, uint32 nWords) if (word1 == 0x40000) { // write U32 - *memPtr = word2; + stdx::atomic_ref<uint32be> atomicRef(*memPtr); + atomicRef.store(word2); } else if (word1 == 0x00000) { - // write U64 (as two U32) - // note: The U32s are swapped - memPtr[0] = word2; - memPtr[1] = word3; + // write U64 + // note: The U32s are swapped here, but needs verification. Also, it seems like the two U32 halves are written independently and the U64 as a whole is not atomic -> investiagte + stdx::atomic_ref<uint64be> atomicRef(*(uint64be*)memPtr); + atomicRef.store(((uint64le)word2 << 32) | word3); } else if (word1 == 0x20000) { // write U64 (little endian) - memPtr[0] = _swapEndianU32(word2); - memPtr[1] = _swapEndianU32(word3); + stdx::atomic_ref<uint64le> atomicRef(*(uint64le*)memPtr); + atomicRef.store(((uint64le)word3 << 32) | word2); } else cemu_assert_unimplemented(); return cmd; } +LatteCMDPtr LatteCP_itEventWriteEOP(LatteCMDPtr cmd, uint32 nWords) +{ + cemu_assert_debug(nWords == 5); + uint32 word0 = LatteReadCMD(); + uint32 word1 = LatteReadCMD(); + uint32 word2 = LatteReadCMD(); + uint32 word3 = LatteReadCMD(); // value low bits + uint32 word4 = LatteReadCMD(); // value high bits + + cemu_assert_debug(word2 == 0x40000000 || word2 == 0x42000000); + + if (word0 == 0x504 && (word2&0x40000000)) // todo - figure out the flags + { + stdx::atomic_ref<uint64be> atomicRef(*(uint64be*)memory_getPointerFromPhysicalOffset(word1)); + uint64 val = ((uint64)word4 << 32) | word3; + atomicRef.store(val); + } + else + { cemu_assert_unimplemented(); + } + bool triggerInterrupt = (word2 & 0x2000000) != 0; + if (triggerInterrupt) + { + // todo - timestamp interrupt + } + TCL::TCLGPUNotifyNewRetirementTimestamp(); + return cmd; +} LatteCMDPtr LatteCP_itMemSemaphore(LatteCMDPtr cmd, uint32 nWords) { @@ -783,16 +760,6 @@ LatteCMDPtr LatteCP_itDrawImmediate(LatteCMDPtr cmd, uint32 nWords, DrawPassCont drawPassCtx.executeDraw(count, false, _tempIndexArrayMPTR); return cmd; - -} - -LatteCMDPtr LatteCP_itHLEFifoWrapAround(LatteCMDPtr cmd, uint32 nWords) -{ - cemu_assert_debug(nWords == 1); - uint32 unused = LatteReadCMD(); - gxRingBufferReadPtr = gx2WriteGatherPipe.gxRingBuffer; - cmd = (LatteCMDPtr)gxRingBufferReadPtr; - return cmd; } LatteCMDPtr LatteCP_itHLESampleTimer(LatteCMDPtr cmd, uint32 nWords) @@ -819,16 +786,6 @@ LatteCMDPtr LatteCP_itHLESpecialState(LatteCMDPtr cmd, uint32 nWords) return cmd; } -LatteCMDPtr LatteCP_itHLESetRetirementTimestamp(LatteCMDPtr cmd, uint32 nWords) -{ - cemu_assert_debug(nWords == 2); - uint32 timestampHigh = (uint32)LatteReadCMD(); - uint32 timestampLow = (uint32)LatteReadCMD(); - uint64 timestamp = ((uint64)timestampHigh << 32ULL) | (uint64)timestampLow; - GX2::__GX2NotifyNewRetirementTimestamp(timestamp); - return cmd; -} - LatteCMDPtr LatteCP_itHLEBeginOcclusionQuery(LatteCMDPtr cmd, uint32 nWords) { cemu_assert_debug(nWords == 1); @@ -1145,9 +1102,10 @@ void LatteCP_processCommandBuffer(DrawPassContext& drawPassCtx) LatteCMDPtr cmd, cmdStart, cmdEnd; if (!drawPassCtx.PopCurrentCommandQueuePos(cmd, cmdStart, cmdEnd)) break; + uint32 itHeader; while (cmd < cmdEnd) { - uint32 itHeader = LatteReadCMD(); + itHeader = LatteReadCMD(); uint32 itHeaderType = (itHeader >> 30) & 3; if (itHeaderType == 3) { @@ -1361,11 +1319,6 @@ void LatteCP_processCommandBuffer(DrawPassContext& drawPassCtx) LatteCP_itHLEEndOcclusionQuery(cmdData, nWords); break; } - case IT_HLE_SET_CB_RETIREMENT_TIMESTAMP: - { - LatteCP_itHLESetRetirementTimestamp(cmdData, nWords); - break; - } case IT_HLE_BOTTOM_OF_PIPE_CB: { LatteCP_itHLEBottomOfPipeCB(cmdData, nWords); @@ -1421,6 +1374,7 @@ void LatteCP_processCommandBuffer(DrawPassContext& drawPassCtx) void LatteCP_ProcessRingbuffer() { sint32 timerRecheck = 0; // estimates how much CP processing time has elapsed based on the executed commands, if the value exceeds CP_TIMER_RECHECK then _handleTimers() is called + uint32be tmpBuffer[128]; while (true) { uint32 itHeader = LatteCP_readU32Deprc(); @@ -1429,10 +1383,13 @@ void LatteCP_ProcessRingbuffer() { uint32 itCode = (itHeader >> 8) & 0xFF; uint32 nWords = ((itHeader >> 16) & 0x3FFF) + 1; - LatteCP_waitForNWords(nWords); - LatteCMDPtr cmd = (LatteCMDPtr)gxRingBufferReadPtr; - uint8* cmdEnd = gxRingBufferReadPtr + nWords * 4; - gxRingBufferReadPtr = cmdEnd; + cemu_assert(nWords < 128); + for (sint32 i=0; i<nWords; i++) + { + uint32 word = LatteCP_readU32Deprc(); + tmpBuffer[i] = word; + } + LatteCMDPtr cmd = (LatteCMDPtr)tmpBuffer; switch (itCode) { case IT_SURFACE_SYNC: @@ -1599,6 +1556,11 @@ void LatteCP_ProcessRingbuffer() timerRecheck += CP_TIMER_RECHECK / 512; break; } + case IT_EVENT_WRITE_EOP: + { + LatteCP_itEventWriteEOP(cmd, nWords); + break; + } case IT_HLE_COPY_COLORBUFFER_TO_SCANBUFFER: { LatteCP_itHLECopyColorBufferToScanBuffer(cmd, nWords); @@ -1637,12 +1599,6 @@ void LatteCP_ProcessRingbuffer() timerRecheck += CP_TIMER_RECHECK / 128; break; } - case IT_HLE_FIFO_WRAP_AROUND: - { - LatteCP_itHLEFifoWrapAround(cmd, nWords); - timerRecheck += CP_TIMER_RECHECK / 512; - break; - } case IT_HLE_SAMPLE_TIMER: { LatteCP_itHLESampleTimer(cmd, nWords); @@ -1667,12 +1623,6 @@ void LatteCP_ProcessRingbuffer() timerRecheck += CP_TIMER_RECHECK / 512; break; } - case IT_HLE_SET_CB_RETIREMENT_TIMESTAMP: - { - LatteCP_itHLESetRetirementTimestamp(cmd, nWords); - timerRecheck += CP_TIMER_RECHECK / 512; - break; - } case IT_HLE_BOTTOM_OF_PIPE_CB: { LatteCP_itHLEBottomOfPipeCB(cmd, nWords); @@ -1933,11 +1883,6 @@ void LatteCP_DebugPrintCmdBuffer(uint32be* bufferPtr, uint32 size) cemuLog_log(LogType::Force, "{} IT_HLE_COPY_SURFACE_NEW", strPrefix); break; } - case IT_HLE_FIFO_WRAP_AROUND: - { - cemuLog_log(LogType::Force, "{} IT_HLE_FIFO_WRAP_AROUND", strPrefix); - break; - } case IT_HLE_SAMPLE_TIMER: { cemuLog_log(LogType::Force, "{} IT_HLE_SAMPLE_TIMER", strPrefix); @@ -1958,11 +1903,6 @@ void LatteCP_DebugPrintCmdBuffer(uint32be* bufferPtr, uint32 size) cemuLog_log(LogType::Force, "{} IT_HLE_END_OCCLUSION_QUERY", strPrefix); break; } - case IT_HLE_SET_CB_RETIREMENT_TIMESTAMP: - { - cemuLog_log(LogType::Force, "{} IT_HLE_SET_CB_RETIREMENT_TIMESTAMP", strPrefix); - break; - } case IT_HLE_BOTTOM_OF_PIPE_CB: { cemuLog_log(LogType::Force, "{} IT_HLE_BOTTOM_OF_PIPE_CB", strPrefix); diff --git a/src/Cafe/HW/Latte/Core/LattePM4.h b/src/Cafe/HW/Latte/Core/LattePM4.h index 8079a89c..1f5d2129 100644 --- a/src/Cafe/HW/Latte/Core/LattePM4.h +++ b/src/Cafe/HW/Latte/Core/LattePM4.h @@ -14,6 +14,7 @@ #define IT_MEM_WRITE 0x3D #define IT_SURFACE_SYNC 0x43 #define IT_EVENT_WRITE 0x46 +#define IT_EVENT_WRITE_EOP 0x47 // end of pipe #define IT_LOAD_CONFIG_REG 0x60 #define IT_LOAD_CONTEXT_REG 0x61 @@ -47,14 +48,12 @@ #define IT_HLE_WAIT_FOR_FLIP 0xF1 #define IT_HLE_BOTTOM_OF_PIPE_CB 0xF2 #define IT_HLE_COPY_COLORBUFFER_TO_SCANBUFFER 0xF3 -#define IT_HLE_FIFO_WRAP_AROUND 0xF4 #define IT_HLE_CLEAR_COLOR_DEPTH_STENCIL 0xF5 #define IT_HLE_SAMPLE_TIMER 0xF7 #define IT_HLE_TRIGGER_SCANBUFFER_SWAP 0xF8 #define IT_HLE_SPECIAL_STATE 0xF9 #define IT_HLE_BEGIN_OCCLUSION_QUERY 0xFA #define IT_HLE_END_OCCLUSION_QUERY 0xFB -#define IT_HLE_SET_CB_RETIREMENT_TIMESTAMP 0xFD #define pm4HeaderType3(__itCode, __dataDWordCount) (0xC0000000|((uint32)(__itCode)<<8)|((uint32)((__dataDWordCount)-1)<<16)) #define pm4HeaderType2Filler() (0x80000000) diff --git a/src/Cafe/HW/Latte/Core/LatteThread.cpp b/src/Cafe/HW/Latte/Core/LatteThread.cpp index 426f8e48..e42dce5f 100644 --- a/src/Cafe/HW/Latte/Core/LatteThread.cpp +++ b/src/Cafe/HW/Latte/Core/LatteThread.cpp @@ -207,7 +207,6 @@ int Latte_ThreadEntry() if (Latte_GetStopSignal()) LatteThread_Exit(); } - gxRingBufferReadPtr = gx2WriteGatherPipe.gxRingBuffer; LatteCP_ProcessRingbuffer(); cemu_assert_debug(false); // should never reach return 0; diff --git a/src/Cafe/OS/libs/TCL/TCL.cpp b/src/Cafe/OS/libs/TCL/TCL.cpp index d0c29d44..8b345b5e 100644 --- a/src/Cafe/OS/libs/TCL/TCL.cpp +++ b/src/Cafe/OS/libs/TCL/TCL.cpp @@ -1,28 +1,161 @@ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/OS/libs/TCL/TCL.h" +#include "HW/Latte/Core/LattePM4.h" + namespace TCL { + SysAllocator<coreinit::OSEvent> s_updateRetirementEvent; + uint64 s_currentRetireMarker = 0; - enum class TCL_SUBMISSION_FLAG : uint32 + struct TCLStatePPC // mapped into PPC space { - SURFACE_SYNC = 0x400000, // submit surface sync packet before cmd - TRIGGER_INTERRUPT = 0x200000, // probably - UKN_20000000 = 0x20000000, + uint64be gpuRetireMarker; // written by GPU }; - int TCLSubmitToRing(uint32be* cmd, uint32 cmdLen, uint32be* controlFlags, uint64* submissionTimestamp) + SysAllocator<TCLStatePPC> s_tclStatePPC; + + // called from GPU for timestamp EOP event + void TCLGPUNotifyNewRetirementTimestamp() { - // todo - figure out all the bits of *controlFlags - // if submissionTimestamp != nullptr then set it to the timestamp of the submission. Note: We should make sure that uint64's are written atomically by the GPU command processor + // gpuRetireMarker is updated via event eop command + __OSLockScheduler(); + coreinit::OSSignalEventAllInternal(s_updateRetirementEvent.GetPtr()); + __OSUnlockScheduler(); + } - cemu_assert_debug(false); + int TCLTimestamp(TCLTimestampId id, uint64be* timestampOut) + { + if (id == TCLTimestampId::TIMESTAMP_LAST_BUFFER_RETIRED) + { + MEMPTR<uint32> b; + // this is the timestamp of the last buffer that was retired by the GPU + stdx::atomic_ref<uint64be> retireTimestamp(s_tclStatePPC->gpuRetireMarker); + *timestampOut = retireTimestamp.load(); + return 0; + } + else + { + cemuLog_log(LogType::Force, "TCLTimestamp(): Unsupported timestamp ID {}", (uint32)id); + *timestampOut = 0; + return 0; + } + } + int TCLWaitTimestamp(TCLTimestampId id, uint64 waitTs, uint64 timeout) + { + if (id == TCLTimestampId::TIMESTAMP_LAST_BUFFER_RETIRED) + { + while ( true ) + { + stdx::atomic_ref<uint64be> retireTimestamp(s_tclStatePPC->gpuRetireMarker); + uint64 currentTimestamp = retireTimestamp.load(); + if (currentTimestamp >= waitTs) + return 0; + coreinit::OSWaitEvent(s_updateRetirementEvent.GetPtr()); + } + } + else + { + cemuLog_log(LogType::Force, "TCLWaitTimestamp(): Unsupported timestamp ID {}", (uint32)id); + } + return 0; + } + + static constexpr uint32 TCL_RING_BUFFER_SIZE = 4096; // in U32s + + std::atomic<uint32> tclRingBufferA[TCL_RING_BUFFER_SIZE]; + std::atomic<uint32> tclRingBufferA_readIndex{0}; + uint32 tclRingBufferA_writeIndex{0}; + + // GPU code calls this to grab the next command word + bool TCLGPUReadRBWord(uint32& cmdWord) + { + if (tclRingBufferA_readIndex == tclRingBufferA_writeIndex) + return false; + cmdWord = tclRingBufferA[tclRingBufferA_readIndex]; + tclRingBufferA_readIndex = (tclRingBufferA_readIndex+1) % TCL_RING_BUFFER_SIZE; + return true; + } + + void TCLWaitForRBSpace(uint32be numU32s) + { + while ( true ) + { + uint32 distance = (tclRingBufferA_readIndex + TCL_RING_BUFFER_SIZE - tclRingBufferA_writeIndex) & (TCL_RING_BUFFER_SIZE - 1); + if (tclRingBufferA_writeIndex == tclRingBufferA_readIndex) // buffer completely empty + distance = TCL_RING_BUFFER_SIZE; + if (distance >= numU32s+1) // assume distance minus one, because we are never allowed to completely wrap around + break; + _mm_pause(); + } + } + + // this function assumes that TCLWaitForRBSpace was called and that there is enough space + void TCLWriteCmd(uint32be* cmd, uint32 cmdLen) + { + while (cmdLen > 0) + { + tclRingBufferA[tclRingBufferA_writeIndex] = *cmd; + tclRingBufferA_writeIndex++; + tclRingBufferA_writeIndex &= (TCL_RING_BUFFER_SIZE - 1); + cmd++; + cmdLen--; + } + } + + #define EVENT_TYPE_TS 5 + + void TCLSubmitRetireMarker(bool triggerEventInterrupt) + { + s_currentRetireMarker++; + uint32be cmd[6]; + cmd[0] = pm4HeaderType3(IT_EVENT_WRITE_EOP, 5); + cmd[1] = (4 | (EVENT_TYPE_TS << 8)); // event type (bits 8-15) and event index (bits 0-7). + cmd[2] = MEMPTR<void>(&s_tclStatePPC->gpuRetireMarker).GetMPTR(); // address lower 32bits + data sel bits + cmd[3] = 0x40000000; // select 64bit write, lower 16 bits are the upper bits of the address + if (triggerEventInterrupt) + cmd[3] |= 0x2000000; // trigger interrupt after value has been written + cmd[4] = (uint32)s_currentRetireMarker; // data lower 32 bits + cmd[5] = (uint32)(s_currentRetireMarker>>32); // data higher 32 bits + TCLWriteCmd(cmd, 6); + } + + int TCLSubmitToRing(uint32be* cmd, uint32 cmdLen, betype<TCLSubmissionFlag>* controlFlags, uint64be* timestampValueOut) + { + TCLSubmissionFlag flags = *controlFlags; + cemu_assert_debug(timestampValueOut); // handle case where this is null + + // make sure there is enough space to submit all commands at one + uint32 totalCommandLength = cmdLen; + totalCommandLength += 6; // space needed for TCLSubmitRetireMarker + + TCLWaitForRBSpace(totalCommandLength); + + // submit command buffer + TCLWriteCmd(cmd, cmdLen); + + // create new marker timestamp and tell GPU to write it to our variable after its done processing the command + if ((HAS_FLAG(flags, TCLSubmissionFlag::USE_RETIRED_MARKER))) + { + TCLSubmitRetireMarker(!HAS_FLAG(flags, TCLSubmissionFlag::NO_MARKER_INTERRUPT)); + *timestampValueOut = s_currentRetireMarker; // incremented before each submit + } + else + { + cemu_assert_unimplemented(); + } return 0; } void Initialize() { cafeExportRegister("TCL", TCLSubmitToRing, LogType::Placeholder); + cafeExportRegister("TCL", TCLTimestamp, LogType::Placeholder); + cafeExportRegister("TCL", TCLWaitTimestamp, LogType::Placeholder); + + s_currentRetireMarker = 0; + s_tclStatePPC->gpuRetireMarker = 0; + coreinit::OSInitEvent(s_updateRetirementEvent.GetPtr(), coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); } } diff --git a/src/Cafe/OS/libs/TCL/TCL.h b/src/Cafe/OS/libs/TCL/TCL.h index ab5358b0..35f0a6bf 100644 --- a/src/Cafe/OS/libs/TCL/TCL.h +++ b/src/Cafe/OS/libs/TCL/TCL.h @@ -1,4 +1,25 @@ namespace TCL { + enum class TCLTimestampId + { + TIMESTAMP_LAST_BUFFER_RETIRED = 1, + }; + + enum class TCLSubmissionFlag : uint32 + { + SURFACE_SYNC = 0x400000, // submit surface sync packet before cmd + NO_MARKER_INTERRUPT = 0x200000, + USE_RETIRED_MARKER = 0x20000000, // Controls whether the timer is updated before or after (retired) the cmd. Also controls which timestamp is returned for the submission. Before and after using separate counters + }; + + int TCLTimestamp(TCLTimestampId id, uint64be* timestampOut); + int TCLWaitTimestamp(TCLTimestampId id, uint64 waitTs, uint64 timeout); + int TCLSubmitToRing(uint32be* cmd, uint32 cmdLen, betype<TCLSubmissionFlag>* controlFlags, uint64be* timestampValueOut); + + // called from Latte code + bool TCLGPUReadRBWord(uint32& cmdWord); + void TCLGPUNotifyNewRetirementTimestamp(); + void Initialize(); -} \ No newline at end of file +} +ENABLE_BITMASK_OPERATORS(TCL::TCLSubmissionFlag); diff --git a/src/Cafe/OS/libs/gx2/GX2.cpp b/src/Cafe/OS/libs/gx2/GX2.cpp index 593d31fb..1c3a8dcc 100644 --- a/src/Cafe/OS/libs/gx2/GX2.cpp +++ b/src/Cafe/OS/libs/gx2/GX2.cpp @@ -59,7 +59,7 @@ void gx2Export_GX2SwapScanBuffers(PPCInterpreter_t* hCPU) if (isPokken) GX2::GX2DrawDone(); - GX2ReserveCmdSpace(5+2); + GX2::GX2ReserveCmdSpace(5+2); uint64 tick64 = PPCInterpreter_getMainCoreCycleCounter() / 20ULL; lastSwapTime = tick64; @@ -86,24 +86,16 @@ void gx2Export_GX2SwapScanBuffers(PPCInterpreter_t* hCPU) GX2::GX2WaitForFlip(); } - GX2::GX2WriteGather_checkAndInsertWrapAroundMark(); osLib_returnFromFunction(hCPU, 0); } void gx2Export_GX2CopyColorBufferToScanBuffer(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2CopyColorBufferToScanBuffer(0x{:08x},{})", hCPU->gpr[3], hCPU->gpr[4]); - GX2ReserveCmdSpace(5); + GX2::GX2ReserveCmdSpace(10); // todo: proper implementation - // hack: Avoid running to far ahead of GPU. Normally this would be guaranteed by the circular buffer model, which we currently dont fully emulate - if(GX2::GX2WriteGather_getReadWriteDistance() > 32*1024*1024 ) - { - debug_printf("Waiting for GPU to catch up...\n"); - PPCInterpreter_relinquishTimeslice(); // release current thread - return; - } GX2ColorBuffer* colorBuffer = (GX2ColorBuffer*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_COPY_COLORBUFFER_TO_SCANBUFFER, 9)); @@ -309,81 +301,6 @@ void gx2Export_GX2SetSemaphore(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, 0); } -void gx2Export_GX2Flush(PPCInterpreter_t* hCPU) -{ - cemuLog_log(LogType::GX2, "GX2Flush()"); - _GX2SubmitToTCL(); - osLib_returnFromFunction(hCPU, 0); -} - -uint8* _GX2LastFlushPtr[PPC_CORE_COUNT] = {NULL}; - -uint64 _prevReturnedGPUTime = 0; - -uint64 Latte_GetTime() -{ - uint64 gpuTime = coreinit::OSGetSystemTime(); - gpuTime *= 20000ULL; - if (gpuTime <= _prevReturnedGPUTime) - gpuTime = _prevReturnedGPUTime + 1; // avoid ever returning identical timestamps - _prevReturnedGPUTime = gpuTime; - return gpuTime; -} - -void _GX2SubmitToTCL() -{ - uint32 coreIndex = PPCInterpreter_getCoreIndex(PPCInterpreter_getCurrentInstance()); - // do nothing if called from non-main GX2 core - if (GX2::sGX2MainCoreIndex != coreIndex) - { - cemuLog_logDebug(LogType::Force, "_GX2SubmitToTCL() called on non-main GX2 core"); - return; - } - if( gx2WriteGatherPipe.displayListStart[coreIndex] != MPTR_NULL ) - return; // quit if in display list - _GX2LastFlushPtr[coreIndex] = (gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex]); - // update last submitted CB timestamp - uint64 commandBufferTimestamp = Latte_GetTime(); - LatteGPUState.lastSubmittedCommandBufferTimestamp.store(commandBufferTimestamp); - cemuLog_log(LogType::GX2, "Submitting GX2 command buffer with timestamp {:016x}", commandBufferTimestamp); - // submit HLE packet to write retirement timestamp - gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_SET_CB_RETIREMENT_TIMESTAMP, 2)); - gx2WriteGather_submitU32AsBE((uint32)(commandBufferTimestamp>>32ULL)); - gx2WriteGather_submitU32AsBE((uint32)(commandBufferTimestamp&0xFFFFFFFFULL)); -} - -uint32 _GX2GetUnflushedBytes(uint32 coreIndex) -{ - uint32 unflushedBytes = 0; - if (_GX2LastFlushPtr[coreIndex] != NULL) - { - if (_GX2LastFlushPtr[coreIndex] > gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex]) - unflushedBytes = (uint32)(gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex] - gx2WriteGatherPipe.gxRingBuffer + 4); // this isn't 100% correct since we ignore the bytes between the last flush address and the start of the wrap around - else - unflushedBytes = (uint32)(gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex] - _GX2LastFlushPtr[coreIndex]); - } - else - unflushedBytes = (uint32)(gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex] - gx2WriteGatherPipe.gxRingBuffer); - return unflushedBytes; -} - -/* - * Guarantees that the requested amount of space is available on the current command buffer - * If the space is not available, the current command buffer is pushed to the GPU and a new one is allocated - */ -void GX2ReserveCmdSpace(uint32 reservedFreeSpaceInU32) -{ - uint32 coreIndex = coreinit::OSGetCoreId(); - // if we are in a display list then do nothing - if( gx2WriteGatherPipe.displayListStart[coreIndex] != MPTR_NULL ) - return; - uint32 unflushedBytes = _GX2GetUnflushedBytes(coreIndex); - if( unflushedBytes >= 0x1000 ) - { - _GX2SubmitToTCL(); - } -} - void gx2_load() { osLib_addFunction("gx2", "GX2GetContextStateDisplayList", gx2Export_GX2GetContextStateDisplayList); @@ -445,10 +362,6 @@ void gx2_load() // semaphore osLib_addFunction("gx2", "GX2SetSemaphore", gx2Export_GX2SetSemaphore); - // command buffer - osLib_addFunction("gx2", "GX2Flush", gx2Export_GX2Flush); - - GX2::GX2Init_writeGather(); GX2::GX2MemInit(); GX2::GX2ResourceInit(); GX2::GX2CommandInit(); diff --git a/src/Cafe/OS/libs/gx2/GX2.h b/src/Cafe/OS/libs/gx2/GX2.h index a22719f4..92452864 100644 --- a/src/Cafe/OS/libs/gx2/GX2.h +++ b/src/Cafe/OS/libs/gx2/GX2.h @@ -67,10 +67,4 @@ void gx2Export_GX2MarkScanBufferCopied(PPCInterpreter_t* hCPU); void gx2Export_GX2SetDefaultState(PPCInterpreter_t* hCPU); void gx2Export_GX2SetupContextStateEx(PPCInterpreter_t* hCPU); -void gx2Export_GX2SetContextState(PPCInterpreter_t* hCPU); - -// command buffer - -uint32 _GX2GetUnflushedBytes(uint32 coreIndex); -void _GX2SubmitToTCL(); -void GX2ReserveCmdSpace(uint32 reservedFreeSpaceInU32); \ No newline at end of file +void gx2Export_GX2SetContextState(PPCInterpreter_t* hCPU); \ No newline at end of file diff --git a/src/Cafe/OS/libs/gx2/GX2_Blit.cpp b/src/Cafe/OS/libs/gx2/GX2_Blit.cpp index db21c9af..6e0db6aa 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Blit.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_Blit.cpp @@ -132,7 +132,6 @@ namespace GX2 depthFirstSlice = _swapEndianU32(depthBuffer->viewFirstSlice); depthNumSlices = _swapEndianU32(depthBuffer->viewNumSlices); } - gx2WriteGather_submit(pm4HeaderType3(IT_HLE_CLEAR_COLOR_DEPTH_STENCIL, 23), hleClearFlags, colorPhysAddr, diff --git a/src/Cafe/OS/libs/gx2/GX2_Command.cpp b/src/Cafe/OS/libs/gx2/GX2_Command.cpp index ec96a4ff..6699e1e1 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Command.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_Command.cpp @@ -4,178 +4,397 @@ #include "Cafe/HW/Latte/Core/LattePM4.h" #include "Cafe/OS/libs/coreinit/coreinit.h" #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" +#include "Cafe/OS/libs/TCL/TCL.h" #include "Cafe/HW/Latte/ISA/RegDefines.h" #include "GX2.h" #include "GX2_Command.h" #include "GX2_Shader.h" #include "GX2_Misc.h" +#include "OS/libs/coreinit/coreinit_MEM.h" -extern uint8* gxRingBufferReadPtr; - -GX2WriteGatherPipeState gx2WriteGatherPipe = { 0 }; +namespace GX2 +{ + GX2PerCoreCBState s_perCoreCBState[Espresso::CORE_COUNT]; +} void gx2WriteGather_submitU32AsBE(uint32 v) { uint32 coreIndex = PPCInterpreter_getCoreIndex(PPCInterpreter_getCurrentInstance()); - if (gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex] == NULL) + if (GX2::s_perCoreCBState[coreIndex].currentWritePtr == nullptr) return; - *(uint32*)(*gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex]) = _swapEndianU32(v); - (*gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex]) += 4; + *(uint32*)(GX2::s_perCoreCBState[coreIndex].currentWritePtr) = _swapEndianU32(v); + GX2::s_perCoreCBState[coreIndex].currentWritePtr++; + cemu_assert_debug(GX2::s_perCoreCBState[coreIndex].currentWritePtr <= (GX2::s_perCoreCBState[coreIndex].bufferPtr + GX2::s_perCoreCBState[coreIndex].bufferSizeInU32s)); } void gx2WriteGather_submitU32AsLE(uint32 v) { uint32 coreIndex = PPCInterpreter_getCoreIndex(PPCInterpreter_getCurrentInstance()); - if (gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex] == NULL) + if (GX2::s_perCoreCBState[coreIndex].currentWritePtr == nullptr) return; - *(uint32*)(*gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex]) = v; - (*gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex]) += 4; + *(uint32*)(GX2::s_perCoreCBState[coreIndex].currentWritePtr) = v; + GX2::s_perCoreCBState[coreIndex].currentWritePtr++; + cemu_assert_debug(GX2::s_perCoreCBState[coreIndex].currentWritePtr <= (GX2::s_perCoreCBState[coreIndex].bufferPtr + GX2::s_perCoreCBState[coreIndex].bufferSizeInU32s)); } void gx2WriteGather_submitU32AsLEArray(uint32* v, uint32 numValues) { uint32 coreIndex = PPCInterpreter_getCoreIndex(PPCInterpreter_getCurrentInstance()); - if (gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex] == NULL) + if (GX2::s_perCoreCBState[coreIndex].currentWritePtr == nullptr) return; - memcpy_dwords((*gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex]), v, numValues); - (*gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex]) += 4 * numValues; + memcpy_dwords(GX2::s_perCoreCBState[coreIndex].currentWritePtr, v, numValues); + GX2::s_perCoreCBState[coreIndex].currentWritePtr += numValues; + cemu_assert_debug(GX2::s_perCoreCBState[coreIndex].currentWritePtr <= (GX2::s_perCoreCBState[coreIndex].bufferPtr + GX2::s_perCoreCBState[coreIndex].bufferSizeInU32s)); } namespace GX2 { - sint32 gx2WriteGatherCurrentMainCoreIndex = -1; - bool gx2WriteGatherInited = false; - void GX2WriteGather_ResetToDefaultState() + struct GX2CommandState // mapped to PPC space since the GPU writes here { - gx2WriteGatherCurrentMainCoreIndex = -1; - gx2WriteGatherInited = false; - } + // command pool + MEMPTR<uint32be> commandPoolBase; + uint32 commandPoolSizeInU32s; + MEMPTR<uint32be> gpuCommandReadPtr; + // timestamp + uint64be lastSubmissionTime; + }; - void GX2Init_writeGather() // init write gather, make current core + SysAllocator<GX2CommandState> s_commandState; + GX2PerCoreCBState s_mainCoreLastCommandState; + bool s_cbBufferIsInternallyAllocated; + + void GX2Command_StartNewCommandBuffer(uint32 numU32s); + + // called from GX2Init. Allocates a 4MB memory chunk from which command buffers are suballocated from + void GX2Init_commandBufferPool(void* bufferBase, uint32 bufferSize) { - if (gx2WriteGatherPipe.gxRingBuffer == NULL) - gx2WriteGatherPipe.gxRingBuffer = (uint8*)malloc(GX2_COMMAND_RING_BUFFER_SIZE); - if (gx2WriteGatherCurrentMainCoreIndex == sGX2MainCoreIndex) - return; // write gather already configured for same core - for (sint32 i = 0; i < PPC_CORE_COUNT; i++) + cemu_assert_debug(!s_commandState->commandPoolBase); // should not be allocated already + // setup command buffer pool. If not provided allocate a 4MB or custom size buffer + uint32 poolSize = bufferSize ? bufferSize : 0x400000; // 4MB (can be overwritten by custom GX2Init parameters?) + if (bufferBase) { - if (i == sGX2MainCoreIndex) + s_commandState->commandPoolBase = (uint32be*)bufferBase; + s_cbBufferIsInternallyAllocated = false; + } + else + { + s_commandState->commandPoolBase = (uint32be*)coreinit::_weak_MEMAllocFromDefaultHeapEx(poolSize, 0x100); + s_cbBufferIsInternallyAllocated = true; + } + if (!s_commandState->commandPoolBase) + { + cemuLog_log(LogType::Force, "GX2: Failed to allocate command buffer pool"); + } + s_commandState->commandPoolSizeInU32s = poolSize / sizeof(uint32be); + s_commandState->gpuCommandReadPtr = s_commandState->commandPoolBase; + // init per-core command buffer state + for (uint32 i = 0; i < Espresso::CORE_COUNT; i++) + { + s_perCoreCBState[i].bufferPtr = nullptr; + s_perCoreCBState[i].bufferSizeInU32s = 0; + s_perCoreCBState[i].currentWritePtr = nullptr; + } + // start first command buffer for main core + GX2Command_StartNewCommandBuffer(0x100); + } + + void GX2Shutdown_commandBufferPool() + { + if (!s_commandState->commandPoolBase) + return; + if (s_cbBufferIsInternallyAllocated) + coreinit::_weak_MEMFreeToDefaultHeap(s_commandState->commandPoolBase.GetPtr()); + s_cbBufferIsInternallyAllocated = false; + s_commandState->commandPoolBase = nullptr; + s_commandState->commandPoolSizeInU32s = 0; + s_commandState->gpuCommandReadPtr = nullptr; + } + + // current position of where the GPU is reading from. Updated via a memory write command submitted to the GPU + uint32 GX2Command_GetPoolGPUReadIndex() + { + stdx::atomic_ref<MEMPTR<uint32be>> _readPtr(s_commandState->gpuCommandReadPtr); + MEMPTR<uint32be> currentReadPtr = _readPtr.load(); + cemu_assert_debug(currentReadPtr); + return (uint32)(currentReadPtr.GetPtr() - s_commandState->commandPoolBase.GetPtr()); + } + + void GX2Command_WaitForNextBufferRetired() + { + uint64 retiredTimeStamp = GX2GetRetiredTimeStamp(); + retiredTimeStamp += 1; + // but cant be higher than the submission timestamp + stdx::atomic_ref<uint64be> _lastSubmissionTime(s_commandState->lastSubmissionTime); + uint64 submissionTimeStamp = _lastSubmissionTime.load(); + if (retiredTimeStamp > submissionTimeStamp) + retiredTimeStamp = submissionTimeStamp; + GX2WaitTimeStamp(retiredTimeStamp); + } + + void GX2Command_SetupCoreCommandBuffer(uint32be* buffer, uint32 sizeInU32s, bool isDisplayList) + { + uint32 coreIndex = coreinit::OSGetCoreId(); + auto& coreCBState = s_perCoreCBState[coreIndex]; + coreCBState.bufferPtr = buffer; + coreCBState.bufferSizeInU32s = sizeInU32s; + coreCBState.currentWritePtr = buffer; + coreCBState.isDisplayList = isDisplayList; + } + + void GX2Command_StartNewCommandBuffer(uint32 numU32s) + { + uint32 coreIndex = coreinit::OSGetCoreId(); + auto& coreCBState = s_perCoreCBState[coreIndex]; + numU32s = std::max<uint32>(numU32s, 0x100); + // grab space from command buffer pool and if necessary wait for it + uint32be* bufferPtr = nullptr; + uint32 bufferSizeInU32s = 0; + uint32 readIndex; + while (true) + { + // try to grab buffer data from first available spot: + // 1. At the current write location up to the end of the buffer (avoiding an overlap with the read location) + // 2. From the start of the buffer up to the read location + readIndex = GX2Command_GetPoolGPUReadIndex(); + uint32be* nextWritePos = coreCBState.bufferPtr ? coreCBState.bufferPtr + coreCBState.bufferSizeInU32s : s_commandState->commandPoolBase.GetPtr(); + uint32 writeIndex = nextWritePos - s_commandState->commandPoolBase; + uint32 poolSizeInU32s = s_commandState->commandPoolSizeInU32s; + // readIndex == writeIndex can mean either buffer full or buffer empty + // we could use GX2GetRetiredTimeStamp() == GX2GetLastSubmittedTimeStamp() to determine if the buffer is truly empty + // but this can have false negatives since the last submission timestamp is updated independently of the read index + // so instead we just avoid ever filling the buffer completely + cemu_assert_debug(readIndex < poolSizeInU32s); + cemu_assert_debug(writeIndex < poolSizeInU32s); + if (writeIndex < readIndex) { - gx2WriteGatherPipe.writeGatherPtrGxBuffer[i] = gx2WriteGatherPipe.gxRingBuffer; - gx2WriteGatherPipe.writeGatherPtrWrite[i] = &gx2WriteGatherPipe.writeGatherPtrGxBuffer[i]; + // writeIndex has wrapped around + uint32 wordsAvailable = readIndex - writeIndex; + if (wordsAvailable > 0) + wordsAvailable--; // avoid writeIndex becoming equal to readIndex + if (wordsAvailable >= numU32s) + { + bufferPtr = s_commandState->commandPoolBase + writeIndex; + bufferSizeInU32s = wordsAvailable; + break; + } } else { - gx2WriteGatherPipe.writeGatherPtrGxBuffer[i] = NULL; - gx2WriteGatherPipe.writeGatherPtrWrite[i] = NULL; + uint32 wordsAvailable = poolSizeInU32s - writeIndex; + if (wordsAvailable > 0) + wordsAvailable--; // avoid writeIndex becoming equal to readIndex + if (wordsAvailable >= numU32s) + { + bufferPtr = nextWritePos; + bufferSizeInU32s = wordsAvailable; + break; + } + // not enough space at end of buffer, try to grab from the beginning of the buffer + wordsAvailable = readIndex; + if (wordsAvailable > 0) + wordsAvailable--; // avoid writeIndex becoming equal to readIndex + if (wordsAvailable >= numU32s) + { + bufferPtr = s_commandState->commandPoolBase; + bufferSizeInU32s = wordsAvailable; + break; + } } - gx2WriteGatherPipe.displayListStart[i] = MPTR_NULL; - gx2WriteGatherPipe.writeGatherPtrDisplayList[i] = NULL; - gx2WriteGatherPipe.displayListMaxSize[i] = 0; + GX2Command_WaitForNextBufferRetired(); + } + cemu_assert_debug(bufferPtr); + bufferSizeInU32s = std::min<uint32>(numU32s, 0x20000); // size cap +#ifdef CEMU_DEBUG_ASSERT + uint32 newWriteIndex = ((bufferPtr - s_commandState->commandPoolBase) + bufferSizeInU32s) % s_commandState->commandPoolSizeInU32s; + cemu_assert_debug(newWriteIndex != readIndex); +#endif + // setup buffer and make it the current write gather target + cemu_assert_debug(bufferPtr >= s_commandState->commandPoolBase && (bufferPtr + bufferSizeInU32s) <= s_commandState->commandPoolBase + s_commandState->commandPoolSizeInU32s); + GX2Command_SetupCoreCommandBuffer(bufferPtr, bufferSizeInU32s, false); + } + + void GX2Command_SubmitCommandBuffer(uint32be* buffer, uint32 sizeInU32s, MEMPTR<uint32be>* completionGPUReadPointer, bool triggerMarkerInterrupt) + { + uint32be cmd[10]; + uint32 cmdLen = 4; + cmd[0] = pm4HeaderType3(IT_INDIRECT_BUFFER_PRIV, 3); + cmd[1] = memory_virtualToPhysical(MEMPTR<void>(buffer).GetMPTR()); + cmd[2] = 0x00000000; // address high bits + cmd[3] = sizeInU32s; + if (completionGPUReadPointer) + { + // append command to update completionGPUReadPointer after the GPU is done with the command buffer + cmd[4] = pm4HeaderType3(IT_MEM_WRITE, 4); + cmd[5] = memory_virtualToPhysical(MEMPTR<void>(completionGPUReadPointer).GetMPTR()) | 2; + cmd[6] = 0x40000; + cmd[7] = MEMPTR<void>(buffer + sizeInU32s).GetMPTR(); // value to write + cmd[8] = 0x00000000; + cmdLen = 9; + } + + betype<TCL::TCLSubmissionFlag> submissionFlags{}; + if (!triggerMarkerInterrupt) + submissionFlags |= TCL::TCLSubmissionFlag::NO_MARKER_INTERRUPT; + submissionFlags |= TCL::TCLSubmissionFlag::USE_RETIRED_MARKER; + + TCL::TCLSubmitToRing(cmd, cmdLen, &submissionFlags, &s_commandState->lastSubmissionTime); + } + + void GX2Command_PadCurrentBuffer() + { + uint32 coreIndex = coreinit::OSGetCoreId(); + auto& coreCBState = s_perCoreCBState[coreIndex]; + if (!coreCBState.currentWritePtr) + return; + uint32 writeDistance = (uint32)(coreCBState.currentWritePtr - coreCBState.bufferPtr); + if ((writeDistance&7) != 0) + { + uint32 distanceToPad = 0x8 - (writeDistance & 0x7); + while (distanceToPad) + { + *coreCBState.currentWritePtr = pm4HeaderType2Filler(); + coreCBState.currentWritePtr++; + distanceToPad--; + } + } + } + + void GX2Command_Flush(uint32 numU32sForNextBuffer, bool triggerMarkerInterrupt) + { + uint32 coreIndex = coreinit::OSGetCoreId(); + auto& coreCBState = s_perCoreCBState[coreIndex]; + if (coreCBState.isDisplayList) + { + // display list + cemu_assert_debug((uint32)(coreCBState.currentWritePtr - coreCBState.bufferPtr) < coreCBState.bufferSizeInU32s); + cemuLog_logDebugOnce(LogType::Force, "GX2 flush called on display list"); + } + else + { + // command buffer + if (coreCBState.currentWritePtr != coreCBState.bufferPtr) + { + // pad the command buffer to 32 byte alignment + GX2Command_PadCurrentBuffer(); + // submit it to the GPU + uint32 bufferLength = (uint32)(coreCBState.currentWritePtr - coreCBState.bufferPtr); + cemu_assert_debug(bufferLength <= coreCBState.bufferSizeInU32s); + GX2Command_SubmitCommandBuffer(coreCBState.bufferPtr, bufferLength, &s_commandState->gpuCommandReadPtr, triggerMarkerInterrupt); + GX2Command_StartNewCommandBuffer(numU32sForNextBuffer); + } + else + { + // current buffer is empty so we dont need to queue it + if (numU32sForNextBuffer > s_commandState->commandPoolSizeInU32s) + GX2Command_StartNewCommandBuffer(numU32sForNextBuffer); + } + } + } + + void GX2Flush() + { + GX2Command_Flush(256, true); + } + + uint64 GX2GetLastSubmittedTimeStamp() + { + stdx::atomic_ref<uint64be> _lastSubmissionTime(s_commandState->lastSubmissionTime); + return _lastSubmissionTime.load(); + } + + uint64 GX2GetRetiredTimeStamp() + { + uint64be ts = 0; + TCL::TCLTimestamp(TCL::TCLTimestampId::TIMESTAMP_LAST_BUFFER_RETIRED, &ts); + return ts; + } + + bool GX2WaitTimeStamp(uint64 tsWait) + { + // handle GPU timeout here? But for now we timeout after 60 seconds + TCL::TCLWaitTimestamp(TCL::TCLTimestampId::TIMESTAMP_LAST_BUFFER_RETIRED, tsWait, Espresso::TIMER_CLOCK * 60); + return true; + } + + /* + * Guarantees that the requested amount of space is available on the current command buffer + * If the space is not available, the current command buffer is pushed to the GPU and a new one is allocated + */ + void GX2ReserveCmdSpace(uint32 reservedFreeSpaceInU32) + { + uint32 coreIndex = coreinit::OSGetCoreId(); + auto& coreCBState = s_perCoreCBState[coreIndex]; + if (coreCBState.currentWritePtr == nullptr) + return; + uint32 writeDistance = (uint32)(coreCBState.currentWritePtr - coreCBState.bufferPtr); + if (writeDistance + reservedFreeSpaceInU32 > coreCBState.bufferSizeInU32s) + { + GX2Command_Flush(reservedFreeSpaceInU32, true); } - gx2WriteGatherCurrentMainCoreIndex = sGX2MainCoreIndex; - gx2WriteGatherInited = true; } void GX2WriteGather_beginDisplayList(PPCInterpreter_t* hCPU, MPTR buffer, uint32 maxSize) { uint32 coreIndex = PPCInterpreter_getCoreIndex(hCPU); - gx2WriteGatherPipe.displayListStart[coreIndex] = buffer; - gx2WriteGatherPipe.displayListMaxSize[coreIndex] = maxSize; - // set new write gather ptr - gx2WriteGatherPipe.writeGatherPtrDisplayList[coreIndex] = memory_getPointerFromVirtualOffset(gx2WriteGatherPipe.displayListStart[coreIndex]); - gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex] = &gx2WriteGatherPipe.writeGatherPtrDisplayList[coreIndex]; + if (coreIndex == sGX2MainCoreIndex) + { + GX2Command_PadCurrentBuffer(); + cemu_assert_debug(!s_perCoreCBState[coreIndex].isDisplayList); + s_mainCoreLastCommandState = s_perCoreCBState[coreIndex]; + } + GX2Command_SetupCoreCommandBuffer(MEMPTR<uint32be>(buffer), maxSize/4, true); } uint32 GX2WriteGather_getDisplayListWriteDistance(sint32 coreIndex) { - return (uint32)(*gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex] - memory_getPointerFromVirtualOffset(gx2WriteGatherPipe.displayListStart[coreIndex])); - } - - uint32 GX2WriteGather_getFifoWriteDistance(uint32 coreIndex) - { - uint32 writeDistance = (uint32)(gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex] - gx2WriteGatherPipe.gxRingBuffer); - return writeDistance; + auto& coreCBState = s_perCoreCBState[coreIndex]; + cemu_assert_debug(coreCBState.isDisplayList); + if (coreCBState.currentWritePtr == nullptr) + return 0; + return (uint32)(coreCBState.currentWritePtr - coreCBState.bufferPtr) * 4; } uint32 GX2WriteGather_endDisplayList(PPCInterpreter_t* hCPU, MPTR buffer) { - uint32 coreIndex = PPCInterpreter_getCoreIndex(hCPU); - if (gx2WriteGatherPipe.displayListStart[coreIndex] != MPTR_NULL) + uint32 coreIndex = coreinit::OSGetCoreId(); + auto& coreCBState = s_perCoreCBState[coreIndex]; + GX2Command_PadCurrentBuffer(); + uint32 finalWriteIndex = coreCBState.currentWritePtr - coreCBState.bufferPtr; + cemu_assert_debug(finalWriteIndex <= coreCBState.bufferSizeInU32s); + // if we are on the main GX2 core then restore the GPU command buffer + if (coreIndex == sGX2MainCoreIndex) { - uint32 currentWriteSize = GX2WriteGather_getDisplayListWriteDistance(coreIndex); - // pad to 32 byte - if (gx2WriteGatherPipe.displayListMaxSize[coreIndex] >= ((gx2WriteGatherPipe.displayListMaxSize[coreIndex] + 0x1F) & ~0x1F)) - { - while ((currentWriteSize & 0x1F) != 0) - { - gx2WriteGather_submitU32AsBE(pm4HeaderType2Filler()); - currentWriteSize += 4; - } - } - // get size of written data - currentWriteSize = GX2WriteGather_getDisplayListWriteDistance(coreIndex); - // disable current display list and restore write gather ptr - gx2WriteGatherPipe.displayListStart[coreIndex] = MPTR_NULL; - if (sGX2MainCoreIndex == coreIndex) - gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex] = &gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex]; - else - gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex] = NULL; - // return size of (written) display list - return currentWriteSize; + coreCBState = s_mainCoreLastCommandState; } else { - // no active display list - // return a size of 0 - return 0; + coreCBState.bufferPtr = nullptr; + coreCBState.currentWritePtr = nullptr; + coreCBState.bufferSizeInU32s = 0; + coreCBState.isDisplayList = false; } + return finalWriteIndex * 4; } - bool GX2GetCurrentDisplayList(betype<MPTR>* displayListAddr, uint32be* displayListSize) + bool GX2GetCurrentDisplayList(MEMPTR<uint32be>* displayListAddr, uint32be* displayListSize) { uint32 coreIndex = coreinit::OSGetCoreId(); - if (gx2WriteGatherPipe.displayListStart[coreIndex] == MPTR_NULL) + auto& coreCBState = s_perCoreCBState[coreIndex]; + if (!coreCBState.isDisplayList) return false; - if (displayListAddr) - *displayListAddr = gx2WriteGatherPipe.displayListStart[coreIndex]; + *displayListAddr = coreCBState.bufferPtr; if (displayListSize) - *displayListSize = gx2WriteGatherPipe.displayListMaxSize[coreIndex]; - + *displayListSize = coreCBState.bufferSizeInU32s * sizeof(uint32be); return true; } + // returns true if we are writing to a display list bool GX2GetDisplayListWriteStatus() { - // returns true if we are writing to a display list uint32 coreIndex = coreinit::OSGetCoreId(); - return gx2WriteGatherPipe.displayListStart[coreIndex] != MPTR_NULL; - } - - uint32 GX2WriteGather_getReadWriteDistance() - { - uint32 coreIndex = sGX2MainCoreIndex; - uint32 writeDistance = (uint32)(gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex] + GX2_COMMAND_RING_BUFFER_SIZE - gxRingBufferReadPtr); - writeDistance %= GX2_COMMAND_RING_BUFFER_SIZE; - return writeDistance; - } - - void GX2WriteGather_checkAndInsertWrapAroundMark() - { - uint32 coreIndex = coreinit::OSGetCoreId(); - if (coreIndex != sGX2MainCoreIndex) // only if main gx2 core - return; - if (gx2WriteGatherPipe.displayListStart[coreIndex] != MPTR_NULL) - return; - uint32 writeDistance = GX2WriteGather_getFifoWriteDistance(coreIndex); - if (writeDistance >= (GX2_COMMAND_RING_BUFFER_SIZE * 3 / 5)) - { - gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_HLE_FIFO_WRAP_AROUND, 1)); - gx2WriteGather_submitU32AsBE(0); // empty word since we can't send commands with zero data words - gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex] = gx2WriteGatherPipe.gxRingBuffer; - } + return s_perCoreCBState[coreIndex].isDisplayList; } void GX2BeginDisplayList(MEMPTR<void> displayListAddr, uint32 size) @@ -204,28 +423,23 @@ namespace GX2 memory_virtualToPhysical(addr), 0, // high address bits size / 4); - GX2::GX2WriteGather_checkAndInsertWrapAroundMark(); } void GX2DirectCallDisplayList(void* addr, uint32 size) { // this API submits to TCL directly and bypasses write-gatherer // its basically a way to manually submit a command buffer to the GPU - // as such it also affects the submission and retire timestamps - - uint32 coreIndex = PPCInterpreter_getCoreIndex(PPCInterpreter_getCurrentInstance()); - cemu_assert_debug(coreIndex == sGX2MainCoreIndex); - coreIndex = sGX2MainCoreIndex; // always submit to main queue which is owned by GX2 main core (TCLSubmitToRing does not need this workaround) - - uint32be* cmdStream = (uint32be*)(gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex]); - cmdStream[0] = pm4HeaderType3(IT_INDIRECT_BUFFER_PRIV, 3); - cmdStream[1] = memory_virtualToPhysical(MEMPTR<void>(addr).GetMPTR()); - cmdStream[2] = 0; - cmdStream[3] = size / 4; - gx2WriteGatherPipe.writeGatherPtrGxBuffer[coreIndex] += 16; - - // update submission timestamp and retired timestamp - _GX2SubmitToTCL(); + uint32 coreIndex = coreinit::OSGetCoreId(); + if (coreIndex != sGX2MainCoreIndex) + { + cemuLog_logDebugOnce(LogType::Force, "GX2DirectCallDisplayList() called on non-main GX2 core"); + } + if (!s_perCoreCBState[coreIndex].isDisplayList) + { + // make sure any preceeding commands are submitted first + GX2Command_Flush(0x100, false); + } + GX2Command_SubmitCommandBuffer(static_cast<uint32be*>(addr), size / 4, nullptr, false); } void GX2CopyDisplayList(MEMPTR<uint32be*> addr, uint32 size) @@ -288,6 +502,12 @@ namespace GX2 void GX2CommandInit() { + cafeExportRegister("gx2", GX2Flush, LogType::GX2); + + cafeExportRegister("gx2", GX2GetLastSubmittedTimeStamp, LogType::GX2); + cafeExportRegister("gx2", GX2GetRetiredTimeStamp, LogType::GX2); + cafeExportRegister("gx2", GX2WaitTimeStamp, LogType::GX2); + cafeExportRegister("gx2", GX2BeginDisplayList, LogType::GX2); cafeExportRegister("gx2", GX2BeginDisplayListEx, LogType::GX2); cafeExportRegister("gx2", GX2EndDisplayList, LogType::GX2); @@ -295,7 +515,6 @@ namespace GX2 cafeExportRegister("gx2", GX2GetCurrentDisplayList, LogType::GX2); cafeExportRegister("gx2", GX2GetDisplayListWriteStatus, LogType::GX2); - cafeExportRegister("gx2", GX2CallDisplayList, LogType::GX2); cafeExportRegister("gx2", GX2DirectCallDisplayList, LogType::GX2); cafeExportRegister("gx2", GX2CopyDisplayList, LogType::GX2); @@ -305,7 +524,10 @@ namespace GX2 void GX2CommandResetToDefaultState() { - GX2WriteGather_ResetToDefaultState(); + s_commandState->commandPoolBase = nullptr; + s_commandState->commandPoolSizeInU32s = 0; + s_commandState->gpuCommandReadPtr = nullptr; + s_cbBufferIsInternallyAllocated = false; } } diff --git a/src/Cafe/OS/libs/gx2/GX2_Command.h b/src/Cafe/OS/libs/gx2/GX2_Command.h index 51c04928..00f5d427 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Command.h +++ b/src/Cafe/OS/libs/gx2/GX2_Command.h @@ -2,21 +2,19 @@ #include "Cafe/HW/Latte/ISA/LatteReg.h" #include "Cafe/HW/Espresso/Const.h" -struct GX2WriteGatherPipeState +namespace GX2 { - uint8* gxRingBuffer; - // each core has it's own write gatherer and display list state (writing) - uint8* writeGatherPtrGxBuffer[Espresso::CORE_COUNT]; - uint8** writeGatherPtrWrite[Espresso::CORE_COUNT]; - uint8* writeGatherPtrDisplayList[Espresso::CORE_COUNT]; - MPTR displayListStart[Espresso::CORE_COUNT]; - uint32 displayListMaxSize[Espresso::CORE_COUNT]; + struct GX2PerCoreCBState + { + uint32be* bufferPtr; + uint32 bufferSizeInU32s; + uint32be* currentWritePtr; + bool isDisplayList; + }; + + extern GX2PerCoreCBState s_perCoreCBState[Espresso::CORE_COUNT]; }; -extern GX2WriteGatherPipeState gx2WriteGatherPipe; - -void GX2ReserveCmdSpace(uint32 reservedFreeSpaceInU32); // move to GX2 namespace eventually - void gx2WriteGather_submitU32AsBE(uint32 v); void gx2WriteGather_submitU32AsLE(uint32 v); void gx2WriteGather_submitU32AsLEArray(uint32* v, uint32 numValues); @@ -27,7 +25,8 @@ uint32 PPCInterpreter_getCurrentCoreIndex(); template <typename ...Targs> inline void gx2WriteGather_submit_(uint32 coreIndex, uint32be* writePtr) { - (*gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex]) = (uint8*)writePtr; + GX2::s_perCoreCBState[coreIndex].currentWritePtr = writePtr; + cemu_assert_debug(GX2::s_perCoreCBState[coreIndex].currentWritePtr <= (GX2::s_perCoreCBState[coreIndex].bufferPtr + GX2::s_perCoreCBState[coreIndex].bufferSizeInU32s)); } template <typename T, typename ...Targs> @@ -75,17 +74,23 @@ template <typename ...Targs> inline void gx2WriteGather_submit(Targs... args) { uint32 coreIndex = PPCInterpreter_getCurrentCoreIndex(); - if (gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex] == nullptr) + if (GX2::s_perCoreCBState[coreIndex].currentWritePtr == nullptr) + { + cemu_assert_suspicious(); // writing to command buffer without valid write pointer? return; - - uint32be* writePtr = (uint32be*)(*gx2WriteGatherPipe.writeGatherPtrWrite[coreIndex]); + } + uint32be* writePtr = GX2::s_perCoreCBState[coreIndex].currentWritePtr; gx2WriteGather_submit_(coreIndex, writePtr, std::forward<Targs>(args)...); } namespace GX2 { - uint32 GX2WriteGather_getReadWriteDistance(); - void GX2WriteGather_checkAndInsertWrapAroundMark(); + void GX2Command_Flush(uint32 numU32sForNextBuffer, bool triggerMarkerInterrupt = true); + void GX2ReserveCmdSpace(uint32 reservedFreeSpaceInU32); + + uint64 GX2GetLastSubmittedTimeStamp(); + uint64 GX2GetRetiredTimeStamp(); + bool GX2WaitTimeStamp(uint64 tsWait); void GX2BeginDisplayList(MEMPTR<void> displayListAddr, uint32 size); void GX2BeginDisplayListEx(MEMPTR<void> displayListAddr, uint32 size, bool profiling); @@ -96,7 +101,8 @@ namespace GX2 bool GX2GetDisplayListWriteStatus(); - void GX2Init_writeGather(); void GX2CommandInit(); + void GX2Init_commandBufferPool(void* bufferBase, uint32 bufferSize); + void GX2Shutdown_commandBufferPool(); void GX2CommandResetToDefaultState(); } \ No newline at end of file diff --git a/src/Cafe/OS/libs/gx2/GX2_ContextState.cpp b/src/Cafe/OS/libs/gx2/GX2_ContextState.cpp index cf150b47..fb631a11 100644 --- a/src/Cafe/OS/libs/gx2/GX2_ContextState.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_ContextState.cpp @@ -168,7 +168,7 @@ uint32 _GX2Context_CalcStateSize() void _GX2Context_CreateLoadDL() { - GX2ReserveCmdSpace(3); + GX2::GX2ReserveCmdSpace(3); gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_CONTEXT_CONTROL, 2)); gx2WriteGather_submitU32AsBE(0x80000077); gx2WriteGather_submitU32AsBE(0x80000077); @@ -176,7 +176,7 @@ void _GX2Context_CreateLoadDL() void _GX2Context_WriteCmdDisableStateShadowing() { - GX2ReserveCmdSpace(3); + GX2::GX2ReserveCmdSpace(3); gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_CONTEXT_CONTROL, 2)); gx2WriteGather_submitU32AsBE(0x80000000); gx2WriteGather_submitU32AsBE(0x80000000); @@ -184,7 +184,7 @@ void _GX2Context_WriteCmdDisableStateShadowing() void _GX2Context_cmdLoad(void* gx2ukn, uint32 pm4Header, MPTR physAddrRegArea, uint32 waitForIdle, uint32 numRegOffsetEntries, GX2RegLoadPktEntry_t* regOffsetEntries) { - GX2ReserveCmdSpace(3 + numRegOffsetEntries*2); + GX2::GX2ReserveCmdSpace(3 + numRegOffsetEntries*2); gx2WriteGather_submitU32AsBE(pm4Header); gx2WriteGather_submitU32AsBE(physAddrRegArea); gx2WriteGather_submitU32AsBE(waitForIdle); @@ -199,7 +199,6 @@ void _GX2Context_cmdLoad(void* gx2ukn, uint32 pm4Header, MPTR physAddrRegArea, u void _GX2Context_WriteCmdRestoreState(GX2ContextState_t* gx2ContextState, uint32 ukn) { - GX2::GX2WriteGather_checkAndInsertWrapAroundMark(); MPTR physAddrContextState = memory_virtualToPhysical(memory_getVirtualOffsetFromPointer(gx2ContextState)); _GX2Context_CreateLoadDL(); __cmdStateLoad(NULL, IT_LOAD_CONFIG_REG, gx2ContextState->hwContext.areaConfigReg, 0x80000000, configReg_loadPktEntries); @@ -212,7 +211,7 @@ void _GX2Context_WriteCmdRestoreState(GX2ContextState_t* gx2ContextState, uint32 void GX2SetDefaultState() { - GX2ReserveCmdSpace(0x100); + GX2::GX2ReserveCmdSpace(0x100); Latte::LATTE_PA_CL_VTE_CNTL reg{}; reg.set_VPORT_X_OFFSET_ENA(true).set_VPORT_X_SCALE_ENA(true); @@ -376,7 +375,6 @@ void gx2Export_GX2SetContextState(PPCInterpreter_t* hCPU) osLib_returnFromFunction(hCPU, 0); } - void gx2Export_GX2GetContextStateDisplayList(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2GetContextStateDisplayList(0x{:08x}, 0x{:08x}, 0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); diff --git a/src/Cafe/OS/libs/gx2/GX2_Draw.cpp b/src/Cafe/OS/libs/gx2/GX2_Draw.cpp index 053b787b..958978e1 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Draw.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_Draw.cpp @@ -52,7 +52,6 @@ namespace GX2 0, count, 0); - GX2::GX2WriteGather_checkAndInsertWrapAroundMark(); } void GX2DrawIndexedEx2(GX2PrimitiveMode2 primitiveMode, uint32 count, GX2IndexType indexType, void* indexData, uint32 baseVertex, uint32 numInstances, uint32 baseInstance) @@ -85,7 +84,6 @@ namespace GX2 pm4HeaderType3(IT_SET_CTL_CONST, 2), 1, 0 // baseInstance ); - GX2::GX2WriteGather_checkAndInsertWrapAroundMark(); } void GX2DrawEx(GX2PrimitiveMode2 primitiveMode, uint32 count, uint32 baseVertex, uint32 numInstances) @@ -109,7 +107,6 @@ namespace GX2 count, 0 // DRAW_INITIATOR ); - GX2::GX2WriteGather_checkAndInsertWrapAroundMark(); } void GX2DrawIndexedImmediateEx(GX2PrimitiveMode2 primitiveMode, uint32 count, GX2IndexType indexType, void* indexData, uint32 baseVertex, uint32 numInstances) @@ -177,7 +174,6 @@ namespace GX2 } } - GX2::GX2WriteGather_checkAndInsertWrapAroundMark(); } struct GX2DispatchComputeParam diff --git a/src/Cafe/OS/libs/gx2/GX2_Event.cpp b/src/Cafe/OS/libs/gx2/GX2_Event.cpp index 9748e20b..645f0a79 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Event.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_Event.cpp @@ -16,18 +16,6 @@ namespace GX2 SysAllocator<coreinit::OSThreadQueue> g_vsyncThreadQueue; SysAllocator<coreinit::OSThreadQueue> g_flipThreadQueue; - SysAllocator<coreinit::OSEvent> s_updateRetirementEvent; - std::atomic<uint64> s_lastRetirementTimestamp = 0; - - // called from GPU code when a command buffer is retired - void __GX2NotifyNewRetirementTimestamp(uint64 tsRetire) - { - __OSLockScheduler(); - s_lastRetirementTimestamp = tsRetire; - coreinit::OSSignalEventAllInternal(s_updateRetirementEvent.GetPtr()); - __OSUnlockScheduler(); - } - void GX2SetGPUFence(uint32be* fencePtr, uint32 mask, uint32 compareOp, uint32 compareValue) { GX2ReserveCmdSpace(7); @@ -210,16 +198,6 @@ namespace GX2 osLib_returnFromFunction(hCPU, 0); } - uint64 GX2GetLastSubmittedTimeStamp() - { - return LatteGPUState.lastSubmittedCommandBufferTimestamp.load(); - } - - uint64 GX2GetRetiredTimeStamp() - { - return s_lastRetirementTimestamp; - } - void GX2WaitForVsync() { __OSLockScheduler(); @@ -236,19 +214,6 @@ namespace GX2 __OSUnlockScheduler(); } - bool GX2WaitTimeStamp(uint64 tsWait) - { - __OSLockScheduler(); - while (tsWait > s_lastRetirementTimestamp) - { - // GPU hasn't caught up yet - coreinit::OSWaitEventInternal(s_updateRetirementEvent.GetPtr()); - } - __OSUnlockScheduler(); - // return true to indicate no timeout - return true; - } - void GX2DrawDone() { // optional force full sync (texture readback and occlusion queries) @@ -263,13 +228,10 @@ namespace GX2 gx2WriteGather_submitU32AsBE(0x00000000); // unused } // flush pipeline - if (_GX2GetUnflushedBytes(coreinit::OSGetCoreId()) > 0) - _GX2SubmitToTCL(); + GX2Command_Flush(0x100, true); uint64 ts = GX2GetLastSubmittedTimeStamp(); GX2WaitTimeStamp(ts); - - GX2::GX2WriteGather_checkAndInsertWrapAroundMark(); } void GX2Init_event() @@ -294,25 +256,19 @@ namespace GX2 cafeExportRegister("gx2", GX2SetEventCallback, LogType::GX2); cafeExportRegister("gx2", GX2GetEventCallback, LogType::GX2); - cafeExportRegister("gx2", GX2GetLastSubmittedTimeStamp, LogType::GX2); - cafeExportRegister("gx2", GX2GetRetiredTimeStamp, LogType::GX2); - cafeExportRegister("gx2", GX2WaitForVsync, LogType::GX2); cafeExportRegister("gx2", GX2WaitForFlip, LogType::GX2); - cafeExportRegister("gx2", GX2WaitTimeStamp, LogType::GX2); cafeExportRegister("gx2", GX2DrawDone, LogType::GX2); coreinit::OSInitThreadQueue(g_vsyncThreadQueue.GetPtr()); coreinit::OSInitThreadQueue(g_flipThreadQueue.GetPtr()); - coreinit::OSInitEvent(s_updateRetirementEvent, coreinit::OSEvent::EVENT_STATE::STATE_NOT_SIGNALED, coreinit::OSEvent::EVENT_MODE::MODE_AUTO); coreinit::OSInitSemaphore(s_eventCbQueueSemaphore, 0); } void GX2EventResetToDefaultState() { s_callbackThreadLaunched = false; - s_lastRetirementTimestamp = 0; for(auto& it : s_eventCallback) { it.callbackFuncPtr = nullptr; diff --git a/src/Cafe/OS/libs/gx2/GX2_Misc.cpp b/src/Cafe/OS/libs/gx2/GX2_Misc.cpp index 3c7ea3f9..e7830cd8 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Misc.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_Misc.cpp @@ -81,19 +81,68 @@ namespace GX2 void _test_AddrLib(); - void GX2Init(void* initSettings) + using GX2InitArg = uint32; + enum class GX2InitArgId : GX2InitArg + { + EndOfArgs = 0, + CommandPoolBase = 1, + CommandPoolSize = 2, + UknArg7 = 7, + UknArg8 = 8, + UknArg9 = 9, + UknArg11 = 11, + }; + + void GX2Init(betype<GX2InitArg>* initArgStream) { if (LatteGPUState.gx2InitCalled) { cemuLog_logDebug(LogType::Force, "GX2Init() called while already initialized"); return; } + // parse init params from the stream + MEMPTR<void> commandPoolBase = nullptr; + uint32 commandPoolSize = 0; + if (initArgStream) + { + while (true) + { + GX2InitArgId paramId = static_cast<GX2InitArgId>((GX2InitArg)*initArgStream); + initArgStream++; + if (paramId == GX2InitArgId::EndOfArgs) + { + break; + } + else if (paramId == GX2InitArgId::CommandPoolBase) + { + commandPoolBase = MEMPTR<void>(*initArgStream); + initArgStream++; + } + else if (paramId == GX2InitArgId::CommandPoolSize) + { + commandPoolSize = *initArgStream; + initArgStream++; + } + else if (paramId == GX2InitArgId::UknArg7 || + paramId == GX2InitArgId::UknArg8 || + paramId == GX2InitArgId::UknArg9 || + paramId == GX2InitArgId::UknArg11) + { + initArgStream++; + } + else + { + cemuLog_log(LogType::Force, "GX2Init: Unsupported init arg {}", (uint32)paramId); + } + } + } + // init main core uint32 coreIndex = coreinit::OSGetCoreId(); cemuLog_log(LogType::GX2, "GX2Init() on core {} by thread 0x{:08x}", coreIndex, MEMPTR<OSThread_t>(coreinit::OSGetCurrentThread()).GetMPTR()); sGX2MainCoreIndex = coreIndex; // init submodules GX2::GX2Init_event(); - GX2::GX2Init_writeGather(); + GX2::GX2Init_commandBufferPool(commandPoolBase, commandPoolSize); // init shared area if (LatteGPUState.sharedAreaAddr == MPTR_NULL) { @@ -112,6 +161,21 @@ namespace GX2 _test_AddrLib(); } + void GX2Shutdown() + { + if (!LatteGPUState.gx2InitCalled) + { + cemuLog_logDebug(LogType::Force, "GX2Shutdown() called while not initialized"); + return; + } + LatteGPUState.gx2InitCalled--; + if (LatteGPUState.gx2InitCalled != 0) + return; + GX2DrawDone(); + GX2Shutdown_commandBufferPool(); + cemuLog_log(LogType::Force, "GX2 shutdown"); + } + void _GX2DriverReset() { LatteGPUState.gx2InitCalled = 0; @@ -237,6 +301,7 @@ namespace GX2 void GX2MiscInit() { cafeExportRegister("gx2", GX2Init, LogType::GX2); + cafeExportRegister("gx2", GX2Shutdown, LogType::GX2); cafeExportRegister("gx2", GX2GetMainCoreId, LogType::GX2); cafeExportRegister("gx2", GX2ResetGPU, LogType::GX2); diff --git a/src/Cafe/OS/libs/gx2/GX2_RenderTarget.cpp b/src/Cafe/OS/libs/gx2/GX2_RenderTarget.cpp index 2a257a67..8abc3613 100644 --- a/src/Cafe/OS/libs/gx2/GX2_RenderTarget.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_RenderTarget.cpp @@ -135,7 +135,7 @@ void gx2Export_GX2InitDepthBufferRegs(PPCInterpreter_t* hCPU) void gx2Export_GX2SetColorBuffer(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2SetColorBuffer(0x{:08x}, {})", hCPU->gpr[3], hCPU->gpr[4]); - GX2ReserveCmdSpace(20); + GX2::GX2ReserveCmdSpace(20); GX2ColorBuffer* colorBufferBE = (GX2ColorBuffer*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); @@ -198,15 +198,13 @@ void gx2Export_GX2SetColorBuffer(PPCInterpreter_t* hCPU) mmCB_COLOR0_INFO - 0xA000 + hCPU->gpr[4], colorBufferBE->reg_info); - GX2::GX2WriteGather_checkAndInsertWrapAroundMark(); - osLib_returnFromFunction(hCPU, 0); } void gx2Export_GX2SetDepthBuffer(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2SetDepthBuffer(0x{:08x})", hCPU->gpr[3]); - GX2ReserveCmdSpace(20); + GX2::GX2ReserveCmdSpace(20); GX2DepthBuffer* depthBufferBE = (GX2DepthBuffer*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); @@ -264,8 +262,6 @@ void gx2Export_GX2SetDepthBuffer(PPCInterpreter_t* hCPU) gx2WriteGather_submitU32AsBE(mmDB_DEPTH_VIEW - 0xA000); gx2WriteGather_submitU32AsBE(db_view); - GX2::GX2WriteGather_checkAndInsertWrapAroundMark(); - osLib_returnFromFunction(hCPU, 0); } @@ -281,7 +277,7 @@ void gx2Export_GX2MarkScanBufferCopied(PPCInterpreter_t* hCPU) uint32 scanTarget = hCPU->gpr[3]; if( scanTarget == GX2_SCAN_TARGET_TV ) { - GX2ReserveCmdSpace(10); + GX2::GX2ReserveCmdSpace(10); uint32 physAddr = (MEMORY_TILINGAPERTURE_AREA_ADDR+0x200000); diff --git a/src/Cafe/OS/libs/gx2/GX2_Shader.cpp b/src/Cafe/OS/libs/gx2/GX2_Shader.cpp index 7a153737..20a773e0 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Shader.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_Shader.cpp @@ -303,7 +303,27 @@ namespace GX2 void GX2SetVertexShader(GX2VertexShader* vertexShader) { - GX2ReserveCmdSpace(100); + uint32 numOutputIds = vertexShader->regs.vsOutIdTableSize; + numOutputIds = std::min<uint32>(numOutputIds, 0xA); + uint32 vsSemanticTableSize = vertexShader->regs.semanticTableSize; + + uint32 reserveSize = 31; + if (vertexShader->shaderMode == GX2_SHADER_MODE::GEOMETRY_SHADER) + { + reserveSize += 7; + } + else + { + reserveSize += 18; + reserveSize += numOutputIds; + if (vertexShader->usesStreamOut != 0) + reserveSize += 2+12; + } + if (vsSemanticTableSize > 0) + { + reserveSize += 5 + vsSemanticTableSize; + } + GX2ReserveCmdSpace(reserveSize); MPTR shaderProgramAddr; uint32 shaderProgramSize; @@ -361,8 +381,6 @@ namespace GX2 cemu_assert_debug(vertexShader->regs.SPI_VS_OUT_CONFIG.value().get_VS_PER_COMPONENT() == false); // not handled on the GPU side - uint32 numOutputIds = vertexShader->regs.vsOutIdTableSize; - numOutputIds = std::min<uint32>(numOutputIds, 0xA); gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1+numOutputIds)); gx2WriteGather_submitU32AsBE(Latte::REGADDR::SPI_VS_OUT_ID_0-0xA000); for(uint32 i=0; i<numOutputIds; i++) @@ -392,7 +410,6 @@ namespace GX2 } } // update semantic table - uint32 vsSemanticTableSize = vertexShader->regs.semanticTableSize; if (vsSemanticTableSize > 0) { gx2WriteGather_submit( diff --git a/src/Cafe/OS/libs/gx2/GX2_State.cpp b/src/Cafe/OS/libs/gx2/GX2_State.cpp index d9c0420f..795ff527 100644 --- a/src/Cafe/OS/libs/gx2/GX2_State.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_State.cpp @@ -213,7 +213,6 @@ namespace GX2 void GX2SetViewportReg(GX2ViewportReg* viewportReg) { - GX2::GX2WriteGather_checkAndInsertWrapAroundMark(); GX2ReserveCmdSpace(2 + 6); gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 1 + 6), diff --git a/src/Cafe/OS/libs/gx2/GX2_Surface_Copy.cpp b/src/Cafe/OS/libs/gx2/GX2_Surface_Copy.cpp index fe785d61..ce85048e 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Surface_Copy.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_Surface_Copy.cpp @@ -264,7 +264,7 @@ void gx2Surface_GX2CopySurface(GX2Surface* srcSurface, uint32 srcMip, uint32 src // send copy command to GPU if( srcHwTileMode > 0 && srcHwTileMode < 16 && dstHwTileMode > 0 && dstHwTileMode < 16 || requestGPURAMCopy ) { - GX2ReserveCmdSpace(1+13*2); + GX2::GX2ReserveCmdSpace(1+13*2); gx2WriteGather_submit(pm4HeaderType3(IT_HLE_COPY_SURFACE_NEW, 13*2), // src @@ -540,7 +540,7 @@ void gx2Export_GX2ResolveAAColorBuffer(PPCInterpreter_t* hCPU) uint32 dstDepth = std::max<uint32>(surfOutDst.depth, 1); // send copy command to GPU - GX2ReserveCmdSpace(1 + 13 * 2); + GX2::GX2ReserveCmdSpace(1 + 13 * 2); gx2WriteGather_submit(pm4HeaderType3(IT_HLE_COPY_SURFACE_NEW, 13 * 2), // src (uint32)srcSurface->imagePtr, @@ -619,7 +619,7 @@ void gx2Export_GX2ConvertDepthBufferToTextureSurface(PPCInterpreter_t* hCPU) sint32 srcMip = 0; uint32 numSlices = std::max<uint32>(_swapEndianU32(depthBuffer->viewNumSlices), 1); - GX2ReserveCmdSpace((1 + 13 * 2) * numSlices); + GX2::GX2ReserveCmdSpace((1 + 13 * 2) * numSlices); for (uint32 subSliceIndex = 0; subSliceIndex < numSlices; subSliceIndex++) { // send copy command to GPU diff --git a/src/Cafe/OS/libs/gx2/GX2_shader_legacy.cpp b/src/Cafe/OS/libs/gx2/GX2_shader_legacy.cpp index b0a5d2fa..d91a8529 100644 --- a/src/Cafe/OS/libs/gx2/GX2_shader_legacy.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_shader_legacy.cpp @@ -11,9 +11,14 @@ void gx2Export_GX2SetPixelShader(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2SetPixelShader(0x{:08x})", hCPU->gpr[3]); - GX2ReserveCmdSpace(100); - GX2PixelShader_t* pixelShader = (GX2PixelShader_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + + uint32 numInputs = _swapEndianU32(pixelShader->regs[4]); + if( numInputs > 0x20 ) + numInputs = 0x20; + + GX2::GX2ReserveCmdSpace(26 + numInputs); + MPTR shaderProgramAddr; uint32 shaderProgramSize; @@ -44,9 +49,6 @@ void gx2Export_GX2SetPixelShader(PPCInterpreter_t* hCPU) _swapEndianU32(pixelShader->regs[2]), _swapEndianU32(pixelShader->regs[3])); // setup pixel shader extended inputs control - uint32 numInputs = _swapEndianU32(pixelShader->regs[4]); - if( numInputs > 0x20 ) - numInputs = 0x20; gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1+numInputs)); gx2WriteGather_submitU32AsBE(mmSPI_PS_INPUT_CNTL_0-0xA000); for(uint32 i=0; i<numInputs; i++) @@ -79,9 +81,17 @@ void gx2Export_GX2SetPixelShader(PPCInterpreter_t* hCPU) void gx2Export_GX2SetGeometryShader(PPCInterpreter_t* hCPU) { cemuLog_log(LogType::GX2, "GX2SetGeometryShader(0x{:08x})", hCPU->gpr[3]); - GX2ReserveCmdSpace(100); GX2GeometryShader_t* geometryShader = (GX2GeometryShader_t*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); + uint32 numOutputIds = _swapEndianU32(geometryShader->regs[7]); + numOutputIds = std::min<uint32>(numOutputIds, 0xA); + uint32 reserveSize = 38; // 38 fixed parameters + if (numOutputIds != 0) + reserveSize += 2 + numOutputIds; + if( _swapEndianU32(geometryShader->useStreamout) != 0 ) + reserveSize += 2 + 12; + + GX2::GX2ReserveCmdSpace(reserveSize); MPTR shaderProgramAddr; uint32 shaderProgramSize; @@ -128,6 +138,7 @@ void gx2Export_GX2SetGeometryShader(PPCInterpreter_t* hCPU) if( _swapEndianU32(geometryShader->useStreamout) != 0 ) { + // todo - IT_EVENT_WRITE packet here // stride 0 gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 2)); gx2WriteGather_submitU32AsBE(mmVGT_STRMOUT_VTX_STRIDE_0-0xA000); @@ -180,8 +191,6 @@ void gx2Export_GX2SetGeometryShader(PPCInterpreter_t* hCPU) gx2WriteGather_submitU32AsBE(_swapEndianU32(geometryShader->regs[3])); // GS outputs - uint32 numOutputIds = _swapEndianU32(geometryShader->regs[7]); - numOutputIds = std::min<uint32>(numOutputIds, 0xA); if( numOutputIds != 0 ) { gx2WriteGather_submitU32AsBE(pm4HeaderType3(IT_SET_CONTEXT_REG, 1+numOutputIds)); @@ -254,8 +263,7 @@ void gx2Export_GX2SetComputeShader(PPCInterpreter_t* hCPU) shaderPtr = computeShader->rBuffer.GetVirtualAddr(); shaderSize = computeShader->rBuffer.GetSize(); } - - GX2ReserveCmdSpace(0x11); + GX2::GX2ReserveCmdSpace(0x11); gx2WriteGather_submit(pm4HeaderType3(IT_SET_CONTEXT_REG, 6), mmSQ_PGM_START_ES-0xA000, @@ -272,7 +280,7 @@ void gx2Export_GX2SetComputeShader(PPCInterpreter_t* hCPU) void _GX2SubmitUniformBlock(uint32 registerBase, uint32 index, MPTR virtualAddress, uint32 size) { - GX2ReserveCmdSpace(9); + GX2::GX2ReserveCmdSpace(9); gx2WriteGather_submit(pm4HeaderType3(IT_SET_RESOURCE, 8), registerBase + index * 7, memory_virtualToPhysical(virtualAddress), @@ -307,7 +315,7 @@ void gx2Export_GX2SetGeometryUniformBlock(PPCInterpreter_t* hCPU) void gx2Export_GX2RSetVertexUniformBlock(PPCInterpreter_t* hCPU) { - GX2ReserveCmdSpace(9); + GX2::GX2ReserveCmdSpace(9); GX2RBuffer* bufferPtr = (GX2RBuffer*)memory_getPointerFromVirtualOffset(hCPU->gpr[3]); uint32 index = hCPU->gpr[4]; @@ -320,7 +328,7 @@ void gx2Export_GX2RSetVertexUniformBlock(PPCInterpreter_t* hCPU) void gx2Export_GX2SetShaderModeEx(PPCInterpreter_t* hCPU) { - GX2ReserveCmdSpace(8+4); + GX2::GX2ReserveCmdSpace(8+4); uint32 mode = hCPU->gpr[3]; uint32 sqConfig = hCPU->gpr[3] == 0 ? 4 : 0; diff --git a/src/Common/precompiled.h b/src/Common/precompiled.h index bda75cef..9e5c60f5 100644 --- a/src/Common/precompiled.h +++ b/src/Common/precompiled.h @@ -616,4 +616,36 @@ namespace stdx scope_exit& operator=(scope_exit) = delete; void release() { m_released = true;} }; + + // Xcode 16 doesn't have std::atomic_ref support and we provide a minimalist reimplementation as fallback +#ifdef __cpp_lib_atomic_ref + #include <atomic> + template<typename T> + using atomic_ref = std::atomic_ref<T>; +#else + template<typename T> + class atomic_ref + { + static_assert(std::is_trivially_copyable<T>::value, "atomic_ref requires trivially copyable types"); + public: + using value_type = T; + + explicit atomic_ref(T& obj) noexcept : ptr_(std::addressof(obj)) {} + + T load(std::memory_order order = std::memory_order_seq_cst) const noexcept + { + auto aptr = reinterpret_cast<std::atomic<T>*>(ptr_); + return aptr->load(order); + } + + void store(T desired, std::memory_order order = std::memory_order_seq_cst) const noexcept + { + auto aptr = reinterpret_cast<std::atomic<T>*>(ptr_); + aptr->store(desired, order); + } + + private: + T* ptr_; + }; +#endif } From 783d88a892204d0570d0720d3749d01b685bdc31 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 17 May 2025 20:45:58 +0200 Subject: [PATCH 270/299] coreinit: Fix race condition in __FSAIoctlResponseCallback --- src/Cafe/OS/libs/coreinit/coreinit_FS.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp b/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp index 0fc8912f..12ddb8df 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_FS.cpp @@ -742,7 +742,8 @@ namespace coreinit } __FSCmdSubmitResult(cmd, fsStatus); - __FSUpdateQueue(&cmd->fsClientBody->fsCmdQueue); + // dont read from cmd after this point, since the game could already have modified it + __FSUpdateQueue(&client->fsCmdQueue); osLib_returnFromFunction(hCPU, 0); } From 7168d20cdebdce08173cdd1568c605400d7967ef Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Tue, 27 May 2025 16:51:46 +0200 Subject: [PATCH 271/299] FST: Refactor IV handling --- src/Cafe/Filesystem/FST/FST.cpp | 28 +++++++++++++++------------- src/Cafe/Filesystem/FST/FST.h | 3 +-- src/Cemu/ncrypto/ncrypto.h | 11 +++++++++-- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/Cafe/Filesystem/FST/FST.cpp b/src/Cafe/Filesystem/FST/FST.cpp index f1255778..ec112b9a 100644 --- a/src/Cafe/Filesystem/FST/FST.cpp +++ b/src/Cafe/Filesystem/FST/FST.cpp @@ -13,6 +13,8 @@ #define SET_FST_ERROR(__code) if (errorCodeOut) *errorCodeOut = ErrorCode::__code +static_assert(sizeof(NCrypto::AesIv) == 16); // make sure IV is actually 16 bytes + class FSTDataSource { public: @@ -868,7 +870,7 @@ static_assert(sizeof(FSTHashedBlock) == BLOCK_SIZE); struct FSTCachedRawBlock { FSTRawBlock blockData; - uint8 ivForNextBlock[16]; + NCrypto::AesIv ivForNextBlock; uint64 lastAccess; }; @@ -919,13 +921,13 @@ void FSTVolume::TrimCacheIfRequired(FSTCachedRawBlock** droppedRawBlock, FSTCach } } -void FSTVolume::DetermineUnhashedBlockIV(uint32 clusterIndex, uint32 blockIndex, uint8 ivOut[16]) +void FSTVolume::DetermineUnhashedBlockIV(uint32 clusterIndex, uint32 blockIndex, NCrypto::AesIv& ivOut) { - memset(ivOut, 0, sizeof(ivOut)); + ivOut = {}; if(blockIndex == 0) { - ivOut[0] = (uint8)(clusterIndex >> 8); - ivOut[1] = (uint8)(clusterIndex >> 0); + ivOut.iv[0] = (uint8)(clusterIndex >> 8); + ivOut.iv[1] = (uint8)(clusterIndex >> 0); } else { @@ -936,20 +938,20 @@ void FSTVolume::DetermineUnhashedBlockIV(uint32 clusterIndex, uint32 blockIndex, auto itr = m_cacheDecryptedRawBlocks.find(cacheBlockId); if (itr != m_cacheDecryptedRawBlocks.end()) { - memcpy(ivOut, itr->second->ivForNextBlock, 16); + ivOut = itr->second->ivForNextBlock; } else { - cemu_assert(m_sectorSize >= 16); + cemu_assert(m_sectorSize >= NCrypto::AesIv::SIZE); uint64 clusterOffset = (uint64)m_cluster[clusterIndex].offset * m_sectorSize; - uint8 prevIV[16]; - if (m_dataSource->readData(clusterIndex, clusterOffset, blockIndex * m_sectorSize - 16, prevIV, 16) != 16) + NCrypto::AesIv prevIV{}; + if (m_dataSource->readData(clusterIndex, clusterOffset, blockIndex * m_sectorSize - NCrypto::AesIv::SIZE, prevIV.iv, NCrypto::AesIv::SIZE) != NCrypto::AesIv::SIZE) { cemuLog_log(LogType::Force, "Failed to read IV for raw FST block"); m_detectedCorruption = true; return; } - memcpy(ivOut, prevIV, 16); + ivOut = prevIV; } } } @@ -984,10 +986,10 @@ FSTCachedRawBlock* FSTVolume::GetDecryptedRawBlock(uint32 clusterIndex, uint32 b return nullptr; } // decrypt hash data - uint8 iv[16]{}; + NCrypto::AesIv iv{}; DetermineUnhashedBlockIV(clusterIndex, blockIndex, iv); - memcpy(block->ivForNextBlock, block->blockData.rawData.data() + m_sectorSize - 16, 16); - AES128_CBC_decrypt(block->blockData.rawData.data(), block->blockData.rawData.data(), m_sectorSize, m_partitionTitlekey.b, iv); + std::copy(block->blockData.rawData.data() + m_sectorSize - NCrypto::AesIv::SIZE, block->blockData.rawData.data() + m_sectorSize, block->ivForNextBlock.iv); + AES128_CBC_decrypt(block->blockData.rawData.data(), block->blockData.rawData.data(), m_sectorSize, m_partitionTitlekey.b, iv.iv); // if this is the next block, then hash it if(cluster.hasContentHash) { diff --git a/src/Cafe/Filesystem/FST/FST.h b/src/Cafe/Filesystem/FST/FST.h index 601799ce..26201c32 100644 --- a/src/Cafe/Filesystem/FST/FST.h +++ b/src/Cafe/Filesystem/FST/FST.h @@ -83,7 +83,6 @@ public: } private: - /* FST data (in memory) */ enum class ClusterHashMode : uint8 { @@ -193,7 +192,7 @@ private: std::unordered_map<uint64, struct FSTCachedHashedBlock*> m_cacheDecryptedHashedBlocks; uint64 m_cacheAccessCounter{}; - void DetermineUnhashedBlockIV(uint32 clusterIndex, uint32 blockIndex, uint8 ivOut[16]); + void DetermineUnhashedBlockIV(uint32 clusterIndex, uint32 blockIndex, NCrypto::AesIv& ivOut); struct FSTCachedRawBlock* GetDecryptedRawBlock(uint32 clusterIndex, uint32 blockIndex); struct FSTCachedHashedBlock* GetDecryptedHashedBlock(uint32 clusterIndex, uint32 blockIndex); diff --git a/src/Cemu/ncrypto/ncrypto.h b/src/Cemu/ncrypto/ncrypto.h index 5f399ad7..1ed7e91b 100644 --- a/src/Cemu/ncrypto/ncrypto.h +++ b/src/Cemu/ncrypto/ncrypto.h @@ -13,10 +13,17 @@ namespace NCrypto std::string base64Encode(const void* inputMem, size_t inputLen); std::vector<uint8> base64Decode(std::string_view inputStr); - /* key helper struct */ + /* key and iv helper struct */ struct AesKey { - uint8 b[16]; + static constexpr size_t SIZE = 16; + uint8 b[SIZE]; + }; + + struct AesIv + { + static constexpr size_t SIZE = 16; + uint8 iv[SIZE]; }; /* ECC Certificate */ From 02616bf6bebe90d73004953174b0cfc541db5a18 Mon Sep 17 00:00:00 2001 From: capitalistspz <keipitalists@proton.me> Date: Wed, 28 May 2025 14:18:01 +0100 Subject: [PATCH 272/299] build: Allow Linux builds to be made without Bluez (#1579) --- src/gui/input/PairingDialog.cpp | 2 +- src/input/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/input/PairingDialog.cpp b/src/gui/input/PairingDialog.cpp index 350fce81..ecbfc110 100644 --- a/src/gui/input/PairingDialog.cpp +++ b/src/gui/input/PairingDialog.cpp @@ -225,7 +225,7 @@ void PairingDialog::WorkerThread() BluetoothFindDeviceClose(deviceFind); } } -#elif BOOST_OS_LINUX +#elif defined(HAS_BLUEZ) void PairingDialog::WorkerThread() { constexpr static uint8_t LIAC_LAP[] = {0x00, 0x8b, 0x9e}; diff --git a/src/input/CMakeLists.txt b/src/input/CMakeLists.txt index 004dc2ba..62fa8d85 100644 --- a/src/input/CMakeLists.txt +++ b/src/input/CMakeLists.txt @@ -104,6 +104,6 @@ if (ENABLE_WXWIDGETS) endif() -if (UNIX AND NOT APPLE) +if (ENABLE_BLUEZ) target_link_libraries(CemuInput PRIVATE bluez::bluez) endif () \ No newline at end of file From 152b790242e20333ef1a1c9b551a43c787993af1 Mon Sep 17 00:00:00 2001 From: oltolm <oleg.tolmatcev@gmail.com> Date: Fri, 30 May 2025 01:39:02 +0200 Subject: [PATCH 273/299] UI: Use wxID_ANY and wxNOT_FOUND instead of hardcoding -1 (#1581) --- src/gui/MainWindow.cpp | 74 +++++++++---------- src/gui/MemorySearcherTool.cpp | 6 +- src/gui/debugger/BreakpointWindow.cpp | 2 +- src/gui/debugger/ModuleWindow.cpp | 2 +- src/gui/debugger/SymbolCtrl.cpp | 6 +- .../DebugPPCThreadsWindow.cpp | 2 +- src/gui/wxcomponents/checkedlistctrl.h | 4 +- 7 files changed, 48 insertions(+), 48 deletions(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 2f63b460..b032a357 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -286,7 +286,7 @@ private: }; MainWindow::MainWindow() - : wxFrame(nullptr, -1, GetInitialWindowTitle(), wxDefaultPosition, wxSize(1280, 720), wxMINIMIZE_BOX | wxMAXIMIZE_BOX | wxSYSTEM_MENU | wxCAPTION | wxCLOSE_BOX | wxCLIP_CHILDREN | wxRESIZE_BORDER) + : wxFrame(nullptr, wxID_ANY, GetInitialWindowTitle(), wxDefaultPosition, wxSize(1280, 720), wxMINIMIZE_BOX | wxMAXIMIZE_BOX | wxSYSTEM_MENU | wxCAPTION | wxCLOSE_BOX | wxCLIP_CHILDREN | wxRESIZE_BORDER) { gui_initHandleContextFromWxWidgetsWindow(g_window_info.window_main, this); g_mainFrame = this; @@ -1859,7 +1859,7 @@ public: auto versionString = formatWxString(_("Cemu\nVersion {0}\nCompiled on {1}\nOriginal authors: {2}"), BUILD_VERSION_STRING, BUILD_DATE, "Exzap, Petergov"); sizer->Add(new wxStaticText(parent, wxID_ANY, versionString), wxSizerFlags().Border(wxALL, 3).Border(wxTOP, 10)); - sizer->Add(new wxHyperlinkCtrl(parent, -1, "https://cemu.info", "https://cemu.info"), wxSizerFlags().Expand().Border(wxTOP | wxBOTTOM, 3)); + sizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "https://cemu.info", "https://cemu.info"), wxSizerFlags().Expand().Border(wxTOP | wxBOTTOM, 3)); sizer->AddSpacer(3); sizer->Add(new wxStaticLine(parent), wxSizerFlags().Expand().Border(wxRIGHT, 4)); @@ -1879,95 +1879,95 @@ public: // zLib { wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); - lineSizer->Add(new wxStaticText(parent, -1, "zLib ("), 0); - lineSizer->Add(new wxHyperlinkCtrl(parent, -1, "https://www.zlib.net", "https://www.zlib.net"), 0); - lineSizer->Add(new wxStaticText(parent, -1, ")"), 0); + lineSizer->Add(new wxStaticText(parent, wxID_ANY, "zLib ("), 0); + lineSizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "https://www.zlib.net", "https://www.zlib.net"), 0); + lineSizer->Add(new wxStaticText(parent, wxID_ANY, ")"), 0); sizer->Add(lineSizer); } // wxWidgets { wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); - lineSizer->Add(new wxStaticText(parent, -1, "wxWidgets ("), 0); - lineSizer->Add(new wxHyperlinkCtrl(parent, -1, "https://www.wxwidgets.org/", "https://www.wxwidgets.org/"), 0); - lineSizer->Add(new wxStaticText(parent, -1, ")"), 0); + lineSizer->Add(new wxStaticText(parent, wxID_ANY, "wxWidgets ("), 0); + lineSizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "https://www.wxwidgets.org/", "https://www.wxwidgets.org/"), 0); + lineSizer->Add(new wxStaticText(parent, wxID_ANY, ")"), 0); sizer->Add(lineSizer); } // OpenSSL { wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); - lineSizer->Add(new wxStaticText(parent, -1, "OpenSSL ("), 0); - lineSizer->Add(new wxHyperlinkCtrl(parent, -1, "https://www.openssl.org/", "https://www.openssl.org/"), 0); - lineSizer->Add(new wxStaticText(parent, -1, ")"), 0); + lineSizer->Add(new wxStaticText(parent, wxID_ANY, "OpenSSL ("), 0); + lineSizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "https://www.openssl.org/", "https://www.openssl.org/"), 0); + lineSizer->Add(new wxStaticText(parent, wxID_ANY, ")"), 0); sizer->Add(lineSizer); } // libcurl { wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); - lineSizer->Add(new wxStaticText(parent, -1, "libcurl ("), 0); - lineSizer->Add(new wxHyperlinkCtrl(parent, -1, "https://curl.haxx.se/libcurl/", "https://curl.haxx.se/libcurl/"), 0); - lineSizer->Add(new wxStaticText(parent, -1, ")"), 0); + lineSizer->Add(new wxStaticText(parent, wxID_ANY, "libcurl ("), 0); + lineSizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "https://curl.haxx.se/libcurl/", "https://curl.haxx.se/libcurl/"), 0); + lineSizer->Add(new wxStaticText(parent, wxID_ANY, ")"), 0); sizer->Add(lineSizer); } // imgui { wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); - lineSizer->Add(new wxStaticText(parent, -1, "imgui ("), 0); - lineSizer->Add(new wxHyperlinkCtrl(parent, -1, "https://github.com/ocornut/imgui", "https://github.com/ocornut/imgui"), 0); - lineSizer->Add(new wxStaticText(parent, -1, ")"), 0); + lineSizer->Add(new wxStaticText(parent, wxID_ANY, "imgui ("), 0); + lineSizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "https://github.com/ocornut/imgui", "https://github.com/ocornut/imgui"), 0); + lineSizer->Add(new wxStaticText(parent, wxID_ANY, ")"), 0); sizer->Add(lineSizer); } // fontawesome { wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); - lineSizer->Add(new wxStaticText(parent, -1, "fontawesome ("), 0); - lineSizer->Add(new wxHyperlinkCtrl(parent, -1, "https://github.com/FortAwesome/Font-Awesome", "https://github.com/FortAwesome/Font-Awesome"), 0); - lineSizer->Add(new wxStaticText(parent, -1, ")"), 0); + lineSizer->Add(new wxStaticText(parent, wxID_ANY, "fontawesome ("), 0); + lineSizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "https://github.com/FortAwesome/Font-Awesome", "https://github.com/FortAwesome/Font-Awesome"), 0); + lineSizer->Add(new wxStaticText(parent, wxID_ANY, ")"), 0); sizer->Add(lineSizer); } // boost { wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); - lineSizer->Add(new wxStaticText(parent, -1, "boost ("), 0); - lineSizer->Add(new wxHyperlinkCtrl(parent, -1, "https://www.boost.org", "https://www.boost.org"), 0); - lineSizer->Add(new wxStaticText(parent, -1, ")"), 0); + lineSizer->Add(new wxStaticText(parent, wxID_ANY, "boost ("), 0); + lineSizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "https://www.boost.org", "https://www.boost.org"), 0); + lineSizer->Add(new wxStaticText(parent, wxID_ANY, ")"), 0); sizer->Add(lineSizer); } // libusb { wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); - lineSizer->Add(new wxStaticText(parent, -1, "libusb ("), 0); - lineSizer->Add(new wxHyperlinkCtrl(parent, -1, "https://libusb.info", "https://libusb.info"), 0); - lineSizer->Add(new wxStaticText(parent, -1, ")"), 0); + lineSizer->Add(new wxStaticText(parent, wxID_ANY, "libusb ("), 0); + lineSizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "https://libusb.info", "https://libusb.info"), 0); + lineSizer->Add(new wxStaticText(parent, wxID_ANY, ")"), 0); sizer->Add(lineSizer); } // icons { wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); - lineSizer->Add(new wxStaticText(parent, -1, "icons from "), 0); - lineSizer->Add(new wxHyperlinkCtrl(parent, -1, "https://icons8.com", "https://icons8.com"), 0); + lineSizer->Add(new wxStaticText(parent, wxID_ANY, "icons from "), 0); + lineSizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "https://icons8.com", "https://icons8.com"), 0); sizer->Add(lineSizer); } // Lato font (are we still using it?) { wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); - lineSizer->Add(new wxStaticText(parent, -1, "\"Lato\" font by tyPoland Lukasz Dziedzic (OFL, V1.1)"), 0); + lineSizer->Add(new wxStaticText(parent, wxID_ANY, "\"Lato\" font by tyPoland Lukasz Dziedzic (OFL, V1.1)"), 0); sizer->Add(lineSizer); } // SDL { wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); - lineSizer->Add(new wxStaticText(parent, -1, "SDL ("), 0); - lineSizer->Add(new wxHyperlinkCtrl(parent, -1, "https://github.com/libsdl-org/SDL", "https://github.com/libsdl-org/SDL"), 0); - lineSizer->Add(new wxStaticText(parent, -1, ")"), 0); + lineSizer->Add(new wxStaticText(parent, wxID_ANY, "SDL ("), 0); + lineSizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "https://github.com/libsdl-org/SDL", "https://github.com/libsdl-org/SDL"), 0); + lineSizer->Add(new wxStaticText(parent, wxID_ANY, ")"), 0); sizer->Add(lineSizer); } // IH264 { wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); - lineSizer->Add(new wxStaticText(parent, -1, "Modified ih264 from Android project ("), 0); - lineSizer->Add(new wxHyperlinkCtrl(parent, -1, "Source", "https://cemu.info/oss/ih264d.zip"), 0); - lineSizer->Add(new wxStaticText(parent, -1, " "), 0); - wxHyperlinkCtrl* noticeLink = new wxHyperlinkCtrl(parent, -1, "NOTICE", ""); + lineSizer->Add(new wxStaticText(parent, wxID_ANY, "Modified ih264 from Android project ("), 0); + lineSizer->Add(new wxHyperlinkCtrl(parent, wxID_ANY, "Source", "https://cemu.info/oss/ih264d.zip"), 0); + lineSizer->Add(new wxStaticText(parent, wxID_ANY, " "), 0); + wxHyperlinkCtrl* noticeLink = new wxHyperlinkCtrl(parent, wxID_ANY, "NOTICE", ""); noticeLink->Bind(wxEVT_LEFT_DOWN, [](wxMouseEvent& event) { fs::path tempPath = fs::temp_directory_path(); @@ -2001,7 +2001,7 @@ public: wxLaunchDefaultBrowser(wxHelper::FromUtf8(fmt::format("file:{}", _pathToUtf8(tempPath)))); }); lineSizer->Add(noticeLink, 0); - lineSizer->Add(new wxStaticText(parent, -1, ")"), 0); + lineSizer->Add(new wxStaticText(parent, wxID_ANY, ")"), 0); sizer->Add(lineSizer); } } diff --git a/src/gui/MemorySearcherTool.cpp b/src/gui/MemorySearcherTool.cpp index fadebc44..2fd60da1 100644 --- a/src/gui/MemorySearcherTool.cpp +++ b/src/gui/MemorySearcherTool.cpp @@ -393,7 +393,7 @@ void MemorySearcherTool::OnResultListClick(wxMouseEvent& event) while (true) { selectedIndex = m_listResults->GetNextItem(selectedIndex, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); - if (selectedIndex == -1) + if (selectedIndex == wxNOT_FOUND) break; long address = m_listResults->GetItemData(selectedIndex); @@ -684,7 +684,7 @@ void MemorySearcherTool::OnPopupClick(wxCommandEvent& event) if (event.GetId() == LIST_ENTRY_REMOVE) { const int row = m_listEntryTable->GetSelectedRow(); - if (row == -1) + if (row == wxNOT_FOUND) return; m_listEntryTable->DeleteItem(row); @@ -700,7 +700,7 @@ void MemorySearcherTool::OnItemEdited(wxDataViewEvent& event) else if (column == 3) { auto row = m_listEntryTable->GetSelectedRow(); - if (row == -1) + if (row == wxNOT_FOUND) return; auto addressText = std::string(m_listEntryTable->GetTextValue(row, 1).mbc_str()); diff --git a/src/gui/debugger/BreakpointWindow.cpp b/src/gui/debugger/BreakpointWindow.cpp index 658a51ad..141e4603 100644 --- a/src/gui/debugger/BreakpointWindow.cpp +++ b/src/gui/debugger/BreakpointWindow.cpp @@ -246,7 +246,7 @@ void BreakpointWindow::OnContextMenuClickSelected(wxCommandEvent& evt) if (evt.GetId() == MENU_ID_DELETE_BP) { long sel = m_breakpoints->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); - if (sel != -1) + if (sel != wxNOT_FOUND) { if (sel >= debuggerState.breakpoints.size()) return; diff --git a/src/gui/debugger/ModuleWindow.cpp b/src/gui/debugger/ModuleWindow.cpp index c24517bd..8183c40c 100644 --- a/src/gui/debugger/ModuleWindow.cpp +++ b/src/gui/debugger/ModuleWindow.cpp @@ -126,7 +126,7 @@ void ModuleWindow::OnGameLoaded() void ModuleWindow::OnLeftDClick(wxMouseEvent& event) { long selected = m_modules->GetFirstSelected(); - if (selected == -1) + if (selected == wxNOT_FOUND) return; const auto text = m_modules->GetItemText(selected, ColumnAddress); const auto address = std::stoul(text.ToStdString(), nullptr, 16); diff --git a/src/gui/debugger/SymbolCtrl.cpp b/src/gui/debugger/SymbolCtrl.cpp index aa862987..fd079d7b 100644 --- a/src/gui/debugger/SymbolCtrl.cpp +++ b/src/gui/debugger/SymbolCtrl.cpp @@ -107,7 +107,7 @@ wxString SymbolListCtrl::OnGetItemText(long item, long column) const void SymbolListCtrl::OnLeftDClick(wxListEvent& event) { long selected = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); - if (selected == -1) + if (selected == wxNOT_FOUND) return; const auto text = GetItemText(selected, ColumnAddress); const auto address = std::stoul(text.ToStdString(), nullptr, 16); @@ -120,7 +120,7 @@ void SymbolListCtrl::OnLeftDClick(wxListEvent& event) void SymbolListCtrl::OnRightClick(wxListEvent& event) { long selected = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); - if (selected == -1) + if (selected == wxNOT_FOUND) return; auto text = GetItemText(selected, ColumnAddress); text = "0x" + text; @@ -162,4 +162,4 @@ void SymbolListCtrl::ChangeListFilter(std::string filter) SetItemCount(visible_entries); if (visible_entries > 0) RefreshItems(GetTopItem(), std::min<long>(visible_entries - 1, GetTopItem() + GetCountPerPage() + 1)); -} \ No newline at end of file +} diff --git a/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp b/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp index f4e5b7af..6b69b0f0 100644 --- a/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp +++ b/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp @@ -439,7 +439,7 @@ void DebugPPCThreadsWindow::OnThreadListRightClick(wxMouseEvent& event) 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) + if (sel != wxNOT_FOUND) 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 diff --git a/src/gui/wxcomponents/checkedlistctrl.h b/src/gui/wxcomponents/checkedlistctrl.h index 215c0004..926c2ba7 100644 --- a/src/gui/wxcomponents/checkedlistctrl.h +++ b/src/gui/wxcomponents/checkedlistctrl.h @@ -87,7 +87,7 @@ public: wxCheckedListCtrl() : wxListCtrl(), m_imageList(16, 16, TRUE) {} - wxCheckedListCtrl(wxWindow *parent, wxWindowID id = -1, + wxCheckedListCtrl(wxWindow *parent, wxWindowID id = wxID_ANY, const wxPoint& pt = wxDefaultPosition, const wxSize& sz = wxDefaultSize, long style = wxCLC_CHECK_WHEN_SELECTING, @@ -96,7 +96,7 @@ public: : wxListCtrl(), m_imageList(16, 16, TRUE) { Create(parent, id, pt, sz, style, validator, name); } - bool Create(wxWindow *parent, wxWindowID id = -1, + bool Create(wxWindow *parent, wxWindowID id = wxID_ANY, const wxPoint& pt = wxDefaultPosition, const wxSize& sz = wxDefaultSize, long style = wxCLC_CHECK_WHEN_SELECTING, From 6df3e1742e660e188e8943afdd2c44ccd7851c1f Mon Sep 17 00:00:00 2001 From: oltolm <oleg.tolmatcev@gmail.com> Date: Sat, 31 May 2025 16:29:07 +0200 Subject: [PATCH 274/299] UI: Fix wxWidgets assert in InfinityPage (#1582) --- src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp index d40e5e5e..2186f8ea 100644 --- a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp +++ b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp @@ -90,9 +90,9 @@ wxPanel* EmulatedUSBDeviceFrame::AddSkylanderPage(wxNotebook* notebook) wxPanel* EmulatedUSBDeviceFrame::AddInfinityPage(wxNotebook* notebook) { auto* panel = new wxPanel(notebook); - auto* panelSizer = new wxBoxSizer(wxBOTH); + auto* panelSizer = new wxBoxSizer(wxVERTICAL); auto* box = new wxStaticBox(panel, wxID_ANY, _("Infinity Manager")); - auto* boxSizer = new wxStaticBoxSizer(box, wxBOTH); + auto* boxSizer = new wxStaticBoxSizer(box, wxVERTICAL); auto* row = new wxBoxSizer(wxHORIZONTAL); @@ -831,4 +831,4 @@ uint8 MoveDimensionFigureDialog::GetNewIndex() const std::array<std::optional<uint32>, 7> EmulatedUSBDeviceFrame::GetCurrentMinifigs() { return m_dimSlots; -} \ No newline at end of file +} From c8045f7f04d8558930dd2c144b75b6a46185ceee Mon Sep 17 00:00:00 2001 From: Colin Kinloch <colin@kinlo.ch> Date: Sun, 1 Jun 2025 02:57:33 +0100 Subject: [PATCH 275/299] UI: wxCAPTION flag on input API dialog to fix kwin (#1586) --- src/gui/input/InputAPIAddWindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/input/InputAPIAddWindow.cpp b/src/gui/input/InputAPIAddWindow.cpp index 688ee14e..6bfb589b 100644 --- a/src/gui/input/InputAPIAddWindow.cpp +++ b/src/gui/input/InputAPIAddWindow.cpp @@ -23,7 +23,7 @@ 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) + : wxDialog(parent, wxID_ANY, "Add input API", position, wxDefaultSize, wxCAPTION), m_controllers(controllers) { this->SetSizeHints(wxDefaultSize, wxDefaultSize); From 162fdabb9d9e1957ffa0d193e11383ff90650afb Mon Sep 17 00:00:00 2001 From: Colin Kinloch <colin@kinlo.ch> Date: Mon, 2 Jun 2025 00:38:21 +0100 Subject: [PATCH 276/299] debug: "verbose" command line argument to log to stdout (#1587) --- src/Cemu/Logging/CemuLogging.cpp | 4 ++++ src/config/LaunchSettings.cpp | 6 ++++++ src/config/LaunchSettings.h | 4 ++++ 3 files changed, 14 insertions(+) diff --git a/src/Cemu/Logging/CemuLogging.cpp b/src/Cemu/Logging/CemuLogging.cpp index 5cde2a7f..f3575cc9 100644 --- a/src/Cemu/Logging/CemuLogging.cpp +++ b/src/Cemu/Logging/CemuLogging.cpp @@ -3,6 +3,7 @@ #include "util/helpers/helpers.h" #include "config/CemuConfig.h" #include "config/ActiveSettings.h" +#include "config/LaunchSettings.h" #include <mutex> #include <condition_variable> @@ -144,6 +145,9 @@ bool cemuLog_log(LogType type, std::string_view text) if (!cemuLog_isLoggingEnabled(type)) return false; + if (LaunchSettings::Verbose()) + std::cout << text << std::endl; + cemuLog_writeLineToLog(text); const auto it = std::find_if(g_logging_window_mapping.cbegin(), g_logging_window_mapping.cend(), diff --git a/src/config/LaunchSettings.cpp b/src/config/LaunchSettings.cpp index b2b0c08a..fde20539 100644 --- a/src/config/LaunchSettings.cpp +++ b/src/config/LaunchSettings.cpp @@ -59,6 +59,9 @@ bool LaunchSettings::HandleCommandline(const std::vector<std::wstring>& args) desc.add_options() ("help,h", "This help screen") ("version,v", "Displays the version of Cemu") +#if !BOOST_OS_WINDOWS + ("verbose", "Log to stdout") +#endif ("game,g", po::wvalue<std::wstring>(), "Path of game to launch") ("title-id,t", po::value<std::string>(), "Title ID of the title to be launched (overridden by --game)") @@ -125,6 +128,9 @@ bool LaunchSettings::HandleCommandline(const std::vector<std::wstring>& args) return false; // exit in main } + if (vm.count("verbose")) + s_verbose = true; + if (vm.count("game")) { std::wstring tmp = vm["game"].as<std::wstring>(); diff --git a/src/config/LaunchSettings.h b/src/config/LaunchSettings.h index d1bed9e1..13665cb7 100644 --- a/src/config/LaunchSettings.h +++ b/src/config/LaunchSettings.h @@ -22,6 +22,8 @@ public: static std::optional<bool> RenderUpsideDownEnabled() { return s_render_upside_down; } static std::optional<bool> FullscreenEnabled() { return s_fullscreen; } + static bool Verbose() { return s_verbose; } + static bool GDBStubEnabled() { return s_enable_gdbstub; } static bool NSightModeEnabled() { return s_nsight_mode; } @@ -40,6 +42,8 @@ private: inline static std::optional<bool> s_render_upside_down{}; inline static std::optional<bool> s_fullscreen{}; + + inline static bool s_verbose = false; inline static bool s_enable_gdbstub = false; inline static bool s_nsight_mode = false; From a184a04e5624ef078476a5722bb340a8fcef6d1b Mon Sep 17 00:00:00 2001 From: neebyA <126654084+neebyA@users.noreply.github.com> Date: Sat, 7 Jun 2025 13:42:49 -0700 Subject: [PATCH 277/299] macOS: Minor UI improvements (#1575) --- src/gui/MainWindow.cpp | 21 ++++ src/gui/components/wxGameList.cpp | 183 +++++++++++++++++++++++++++++- src/gui/components/wxGameList.h | 2 - 3 files changed, 200 insertions(+), 6 deletions(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index b032a357..882c6eab 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -91,6 +91,7 @@ enum MAINFRAME_MENU_ID_OPTIONS_GENERAL2, MAINFRAME_MENU_ID_OPTIONS_AUDIO, MAINFRAME_MENU_ID_OPTIONS_INPUT, + MAINFRAME_MENU_ID_OPTIONS_MAC_SETTINGS, // options -> account MAINFRAME_MENU_ID_OPTIONS_ACCOUNT_1 = 20350, MAINFRAME_MENU_ID_OPTIONS_ACCOUNT_12 = 20350 + 11, @@ -187,6 +188,7 @@ EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_GENERAL, MainWindow::OnOptionsInput) EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_GENERAL2, MainWindow::OnOptionsInput) EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_AUDIO, MainWindow::OnOptionsInput) EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_INPUT, MainWindow::OnOptionsInput) +EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_MAC_SETTINGS, MainWindow::OnOptionsInput) // tools menu EVT_MENU(MAINFRAME_MENU_ID_TOOLS_MEMORY_SEARCHER, MainWindow::OnToolsInput) EVT_MENU(MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER, MainWindow::OnToolsInput) @@ -288,6 +290,11 @@ private: MainWindow::MainWindow() : wxFrame(nullptr, wxID_ANY, GetInitialWindowTitle(), wxDefaultPosition, wxSize(1280, 720), wxMINIMIZE_BOX | wxMAXIMIZE_BOX | wxSYSTEM_MENU | wxCAPTION | wxCLOSE_BOX | wxCLIP_CHILDREN | wxRESIZE_BORDER) { +#ifdef __WXMAC__ + // Not necessary to set wxApp::s_macExitMenuItemId as automatically handled + wxApp::s_macAboutMenuItemId = MAINFRAME_MENU_ID_HELP_ABOUT; + wxApp::s_macPreferencesMenuItemId = MAINFRAME_MENU_ID_OPTIONS_MAC_SETTINGS; +#endif gui_initHandleContextFromWxWidgetsWindow(g_window_info.window_main, this); g_mainFrame = this; CafeSystem::SetImplementation(this); @@ -911,6 +918,7 @@ void MainWindow::OnOptionsInput(wxCommandEvent& event) break; } + case MAINFRAME_MENU_ID_OPTIONS_MAC_SETTINGS: case MAINFRAME_MENU_ID_OPTIONS_GENERAL2: { OpenSettings(); @@ -1940,6 +1948,16 @@ public: lineSizer->Add(new wxStaticText(parent, wxID_ANY, ")"), 0); sizer->Add(lineSizer); } +#if BOOST_OS_MACOS + // MoltenVK + { + wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); + lineSizer->Add(new wxStaticText(parent, -1, "MoltenVK ("), 0); + lineSizer->Add(new wxHyperlinkCtrl(parent, -1, "https://github.com/KhronosGroup/MoltenVK", "https://github.com/KhronosGroup/MoltenVK"), 0); + lineSizer->Add(new wxStaticText(parent, -1, ")"), 0); + sizer->Add(lineSizer); + } +#endif // icons { wxSizer* lineSizer = new wxBoxSizer(wxHORIZONTAL); @@ -2165,6 +2183,9 @@ void MainWindow::RecreateMenu() m_padViewMenuItem = optionsMenu->AppendCheckItem(MAINFRAME_MENU_ID_OPTIONS_SECOND_WINDOW_PADVIEW, _("&Separate GamePad view"), wxEmptyString); m_padViewMenuItem->Check(GetConfig().pad_open); optionsMenu->AppendSeparator(); + #if BOOST_OS_MACOS + optionsMenu->Append(MAINFRAME_MENU_ID_OPTIONS_MAC_SETTINGS, _("&Settings..." "\tCtrl-,")); + #endif optionsMenu->Append(MAINFRAME_MENU_ID_OPTIONS_GENERAL2, _("&General settings")); optionsMenu->Append(MAINFRAME_MENU_ID_OPTIONS_INPUT, _("&Input settings")); diff --git a/src/gui/components/wxGameList.cpp b/src/gui/components/wxGameList.cpp index e418ca0a..95711fef 100644 --- a/src/gui/components/wxGameList.cpp +++ b/src/gui/components/wxGameList.cpp @@ -82,6 +82,56 @@ std::list<fs::path> _getCachesPaths(const TitleId& titleId) return cachePaths; } +// Convert PNG to Apple icon image format +bool writeICNS(const fs::path& pngPath, const fs::path& icnsPath) { + // Read PNG file + std::ifstream pngFile(pngPath, std::ios::binary); + if (!pngFile) + return false; + + // Get PNG size + pngFile.seekg(0, std::ios::end); + uint32 pngSize = static_cast<uint32>(pngFile.tellg()); + pngFile.seekg(0, std::ios::beg); + + // Calculate total file size (header + size + type + data) + uint32 totalSize = 8 + 8 + pngSize; + + // Create output file + std::ofstream icnsFile(icnsPath, std::ios::binary); + if (!icnsFile) + return false; + + // Write ICNS header + icnsFile.put(0x69); // 'i' + icnsFile.put(0x63); // 'c' + icnsFile.put(0x6e); // 'n' + icnsFile.put(0x73); // 's' + + // Write total file size (big endian) + icnsFile.put((totalSize >> 24) & 0xFF); + icnsFile.put((totalSize >> 16) & 0xFF); + icnsFile.put((totalSize >> 8) & 0xFF); + icnsFile.put(totalSize & 0xFF); + + // Write icon type (ic07 = 128x128 PNG) + icnsFile.put(0x69); // 'i' + icnsFile.put(0x63); // 'c' + icnsFile.put(0x30); // '0' + icnsFile.put(0x37); // '7' + + // Write PNG size (big endian) + icnsFile.put((pngSize >> 24) & 0xFF); + icnsFile.put((pngSize >> 16) & 0xFF); + icnsFile.put((pngSize >> 8) & 0xFF); + icnsFile.put(pngSize & 0xFF); + + // Copy PNG data + icnsFile << pngFile.rdbuf(); + + return true; +} + wxGameList::wxGameList(wxWindow* parent, wxWindowID id) : wxListCtrl(parent, id, wxDefaultPosition, wxDefaultSize, GetStyleFlags(Style::kList)), m_style(Style::kList) { @@ -596,9 +646,7 @@ void wxGameList::OnContextMenu(wxContextMenuEvent& event) menu.Append(kContextMenuEditGameProfile, _("&Edit game profile")); menu.AppendSeparator(); -#if BOOST_OS_LINUX || BOOST_OS_WINDOWS menu.Append(kContextMenuCreateShortcut, _("&Create shortcut")); -#endif menu.AppendSeparator(); menu.Append(kContextMenuCopyTitleName, _("&Copy Title Name")); menu.Append(kContextMenuCopyTitleId, _("&Copy Title ID")); @@ -724,9 +772,7 @@ void wxGameList::OnContextMenuSelected(wxCommandEvent& event) } case kContextMenuCreateShortcut: { -#if BOOST_OS_LINUX || BOOST_OS_WINDOWS CreateShortcut(gameInfo); -#endif break; } case kContextMenuCopyTitleName: @@ -1372,6 +1418,135 @@ void wxGameList::CreateShortcut(GameInfo2& gameInfo) } outputStream << desktopEntryString; } +#elif BOOST_OS_MACOS +void wxGameList::CreateShortcut(GameInfo2& gameInfo) +{ + const auto titleId = gameInfo.GetBaseTitleId(); + const auto titleName = wxString::FromUTF8(gameInfo.GetTitleName()); + auto exePath = ActiveSettings::GetExecutablePath(); + + const wxString appName = wxString::Format("%s.app", titleName); + wxFileDialog entryDialog(this, _("Choose shortcut location"), "~/Applications", appName, + "Application (*.app)|*.app", wxFD_SAVE | wxFD_CHANGE_DIR | wxFD_OVERWRITE_PROMPT); + const auto result = entryDialog.ShowModal(); + if (result == wxID_CANCEL) + return; + const auto output_path = entryDialog.GetPath(); + // Create .app folder + const fs::path appPath = output_path.utf8_string(); + if (!fs::create_directories(appPath)) + { + cemuLog_log(LogType::Force, "Failed to create app directory"); + return; + } + const fs::path infoPath = appPath / "Contents/Info.plist"; + const fs::path scriptPath = appPath / "Contents/MacOS/run.sh"; + const fs::path icnsPath = appPath / "Contents/Resources/shortcut.icns"; + if (!(fs::create_directories(scriptPath.parent_path()) && fs::create_directories(icnsPath.parent_path()))) + { + cemuLog_log(LogType::Force, "Failed to create app shortcut directories"); + return; + } + + std::optional<fs::path> iconPath; + // Obtain and convert icon + [&]() + { + int iconIndex, smallIconIndex; + + if (!QueryIconForTitle(titleId, iconIndex, smallIconIndex)) + { + cemuLog_log(LogType::Force, "Icon hasn't loaded"); + return; + } + const fs::path outIconDir = fs::temp_directory_path(); + + if (!fs::exists(outIconDir) && !fs::create_directories(outIconDir)) + { + cemuLog_log(LogType::Force, "Failed to create icon directory"); + return; + } + + iconPath = outIconDir / fmt::format("{:016x}.png", gameInfo.GetBaseTitleId()); + wxFileOutputStream pngFileStream(_pathToUtf8(iconPath.value())); + + auto image = m_image_list->GetIcon(iconIndex).ConvertToImage(); + wxPNGHandler pngHandler; + if (!pngHandler.SaveFile(&image, pngFileStream, false)) + { + iconPath = std::nullopt; + cemuLog_log(LogType::Force, "Icon failed to save"); + } + }(); + + std::string runCommand = fmt::format("#!/bin/zsh\n\n{0:?} --title-id {1:016x}", _pathToUtf8(exePath), titleId); + const std::string infoPlist = fmt::format( + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" + "<plist version=\"1.0\">\n" + "<dict>\n" + " <key>CFBundleDisplayName</key>\n" + " <string>{0}</string>\n" + " <key>CFBundleExecutable</key>\n" + " <string>run.sh</string>\n" + " <key>CFBundleIconFile</key>\n" + " <string>shortcut.icns</string>\n" + " <key>CFBundleName</key>\n" + " <string>{0}</string>\n" + " <key>CFBundlePackageType</key>\n" + " <string>APPL</string>\n" + " <key>CFBundleSignature</key>\n" + " <string>\?\?\?\?</string>\n" + " <key>LSApplicationCategoryType</key>\n" + " <string>public.app-category.games</string>\n" + " <key>CFBundleShortVersionString</key>\n" + " <string>{1}</string>\n" + " <key>CFBundleVersion</key>\n" + " <string>{1}</string>\n" + "</dict>\n" + "</plist>\n", + gameInfo.GetTitleName(), + std::to_string(gameInfo.GetVersion()) + ); + // write Info.plist to infoPath + std::ofstream infoStream(infoPath); + std::ofstream scriptStream(scriptPath); + if (!infoStream.good() || !scriptStream.good()) + { + auto errorMsg = formatWxString(_("Failed to save app shortcut to {}"), output_path.utf8_string()); + wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); + return; + } + infoStream << infoPlist; + scriptStream << runCommand; + scriptStream.close(); + + // Set execute permissions for script + fs::permissions( + scriptPath, + fs::perms::owner_exec | fs::perms::group_exec | fs::perms::others_exec, + fs::perm_options::add + ); + + // Return if iconPath is empty + if (!iconPath) + { + cemuLog_log(LogType::Force, "Icon not found"); + return; + } + + // Convert icon to icns, only works for 128x128 PNG + // Alternatively, can run the command "sips -s format icns {iconPath} --out '{icnsPath}'" + // using std::system() to handle images of any size + if (!writeICNS(*iconPath, icnsPath)) + { + cemuLog_log(LogType::Force, "Failed to convert icon to icns"); + return; + } + + // Remove temp file + fs::remove(*iconPath); +} #elif BOOST_OS_WINDOWS void wxGameList::CreateShortcut(GameInfo2& gameInfo) { diff --git a/src/gui/components/wxGameList.h b/src/gui/components/wxGameList.h index b285d259..a6bfa7f7 100644 --- a/src/gui/components/wxGameList.h +++ b/src/gui/components/wxGameList.h @@ -53,9 +53,7 @@ public: void ReloadGameEntries(bool cached = false); void DeleteCachedStrings(); -#if BOOST_OS_LINUX || BOOST_OS_WINDOWS void CreateShortcut(GameInfo2& gameInfo); -#endif long FindListItemByTitleId(uint64 title_id) const; void OnClose(wxCloseEvent& event); From d427b59019773a7a0af09d5a20e799c2f4fcf3bb Mon Sep 17 00:00:00 2001 From: Wiichele <pappalardosystem@gmail.com> Date: Sun, 8 Jun 2025 07:16:09 +0200 Subject: [PATCH 278/299] boss: Use HTTP/1.1 instead of default (#1593) --- src/Cafe/IOSU/legacy/iosu_boss.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Cafe/IOSU/legacy/iosu_boss.cpp b/src/Cafe/IOSU/legacy/iosu_boss.cpp index 7ab25f68..212d42a0 100644 --- a/src/Cafe/IOSU/legacy/iosu_boss.cpp +++ b/src/Cafe/IOSU/legacy/iosu_boss.cpp @@ -502,6 +502,7 @@ namespace iosu curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, task_header_callback); curl_easy_setopt(curl, CURLOPT_HEADERDATA, &(*it)); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 0x3C); + curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); if (IsNetworkServiceSSLDisabled(ActiveSettings::GetNetworkService())) { curl_easy_setopt(curl,CURLOPT_SSL_VERIFYPEER,0L); From 3eff2d4a603019f69e40860294ab4eefcbdc8582 Mon Sep 17 00:00:00 2001 From: Luminyx <79218624+Luminyx1@users.noreply.github.com> Date: Tue, 10 Jun 2025 02:03:18 -0400 Subject: [PATCH 279/299] GraphicPack: Allow overlay for code folder (#1574) --- src/Cafe/GraphicPack/GraphicPack2.cpp | 19 +++++++++++++++---- src/Cafe/GraphicPack/GraphicPack2.h | 2 +- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/Cafe/GraphicPack/GraphicPack2.cpp b/src/Cafe/GraphicPack/GraphicPack2.cpp index f21bb89d..6ae05c5b 100644 --- a/src/Cafe/GraphicPack/GraphicPack2.cpp +++ b/src/Cafe/GraphicPack/GraphicPack2.cpp @@ -821,7 +821,7 @@ void GraphicPack2::AddConstantsForCurrentPreset(ExpressionParser& ep) } } -void GraphicPack2::_iterateReplacedFiles(const fs::path& currentPath, bool isAOC) +void GraphicPack2::_iterateReplacedFiles(const fs::path& currentPath, bool isAOC, const char* virtualMountBase) { uint64 currentTitleId = CafeSystem::GetForegroundTitleId(); uint64 aocTitleId = (currentTitleId & 0xFFFFFFFFull) | 0x0005000c00000000ull; @@ -836,7 +836,7 @@ void GraphicPack2::_iterateReplacedFiles(const fs::path& currentPath, bool isAOC } else { - virtualMountPath = fs::path("vol/content/") / virtualMountPath; + virtualMountPath = fs::path(virtualMountBase) / virtualMountPath; } fscDeviceRedirect_add(virtualMountPath.generic_string(), it.file_size(), it.path().generic_string(), m_fs_priority); } @@ -861,7 +861,7 @@ void GraphicPack2::LoadReplacedFiles() { // setup redirections fscDeviceRedirect_map(); - _iterateReplacedFiles(contentPath, false); + _iterateReplacedFiles(contentPath, false, "vol/content/"); } // /aoc/ fs::path aocPath(gfxPackPath); @@ -874,7 +874,18 @@ void GraphicPack2::LoadReplacedFiles() aocTitleId |= 0x0005000c00000000ULL; // setup redirections fscDeviceRedirect_map(); - _iterateReplacedFiles(aocPath, true); + _iterateReplacedFiles(aocPath, true, nullptr); + } + + // /code/ + fs::path codePath(gfxPackPath); + codePath.append("code"); + + if (fs::exists(codePath, ec)) + { + // setup redirections + fscDeviceRedirect_map(); + _iterateReplacedFiles(codePath, false, CafeSystem::GetInternalVirtualCodeFolder().c_str()); } } diff --git a/src/Cafe/GraphicPack/GraphicPack2.h b/src/Cafe/GraphicPack/GraphicPack2.h index 9b6a86d4..fc9603cd 100644 --- a/src/Cafe/GraphicPack/GraphicPack2.h +++ b/src/Cafe/GraphicPack/GraphicPack2.h @@ -260,7 +260,7 @@ private: CustomShader LoadShader(const fs::path& path, uint64 shader_base_hash, uint64 shader_aux_hash, GP_SHADER_TYPE shader_type) const; void ApplyShaderPresets(std::string& shader_source) const; void LoadReplacedFiles(); - void _iterateReplacedFiles(const fs::path& currentPath, bool isAOC); + void _iterateReplacedFiles(const fs::path& currentPath, bool isAOC, const char* virtualMountBase); // ram mappings std::vector<std::pair<MPTR, MPTR>> m_ramMappings; From 2eec6b44c3ec62374913ba9bfe847c2d395714bc Mon Sep 17 00:00:00 2001 From: oltolm <oleg.tolmatcev@gmail.com> Date: Tue, 10 Jun 2025 08:15:25 +0200 Subject: [PATCH 280/299] UI: Use wxListView instead of wxListCtrl (#1584) --- src/gui/MemorySearcherTool.cpp | 11 ++----- src/gui/MemorySearcherTool.h | 2 +- src/gui/components/wxDownloadManagerList.cpp | 13 ++++----- src/gui/components/wxDownloadManagerList.h | 2 +- src/gui/components/wxGameList.cpp | 27 +++++++++-------- src/gui/components/wxGameList.h | 2 +- src/gui/components/wxTitleManagerList.cpp | 12 ++++---- src/gui/components/wxTitleManagerList.h | 2 +- src/gui/debugger/BreakpointWindow.cpp | 6 ++-- src/gui/debugger/SymbolCtrl.cpp | 8 ++--- src/gui/debugger/SymbolCtrl.h | 2 +- .../DebugPPCThreadsWindow.cpp | 29 ++++++++++--------- .../PPCThreadsViewer/DebugPPCThreadsWindow.h | 4 +-- src/gui/wxcomponents/checkedlistctrl.h | 6 ++-- 14 files changed, 61 insertions(+), 65 deletions(-) diff --git a/src/gui/MemorySearcherTool.cpp b/src/gui/MemorySearcherTool.cpp index 2fd60da1..8506c591 100644 --- a/src/gui/MemorySearcherTool.cpp +++ b/src/gui/MemorySearcherTool.cpp @@ -5,6 +5,7 @@ #include <vector> #include <sstream> #include <thread> +#include <wx/listctrl.h> #include "config/ActiveSettings.h" #include "gui/helpers/wxHelpers.h" @@ -79,7 +80,7 @@ MemorySearcherTool::MemorySearcherTool(wxFrame* parent) 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 = new wxListView(this, LIST_RESULTS, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_SORT_ASCENDING); m_listResults->Bind(wxEVT_LEFT_DCLICK, &MemorySearcherTool::OnResultListClick, this); { wxListItem col0; @@ -388,14 +389,8 @@ void MemorySearcherTool::OnEntryListRightClick(wxDataViewEvent& event) void MemorySearcherTool::OnResultListClick(wxMouseEvent& event) { - long selectedIndex = -1; - - while (true) + for (long selectedIndex = m_listResults->GetFirstSelected(); selectedIndex != wxNOT_FOUND; selectedIndex = m_listResults->GetNextSelected(selectedIndex)) { - selectedIndex = m_listResults->GetNextItem(selectedIndex, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); - if (selectedIndex == wxNOT_FOUND) - break; - long address = m_listResults->GetItemData(selectedIndex); auto currValue = m_listResults->GetItemText(selectedIndex, 1); diff --git a/src/gui/MemorySearcherTool.h b/src/gui/MemorySearcherTool.h index 78b5cb77..27e7d27d 100644 --- a/src/gui/MemorySearcherTool.h +++ b/src/gui/MemorySearcherTool.h @@ -173,7 +173,7 @@ wxDECLARE_EVENT_TABLE(); wxComboBox* m_cbDataType; wxTextCtrl* m_textValue; wxButton *m_buttonStart, *m_buttonFilter; - wxListCtrl* m_listResults; + wxListView* m_listResults; wxDataViewListCtrl* m_listEntryTable; wxStaticText* m_textEntryTable; wxGauge* m_gauge; diff --git a/src/gui/components/wxDownloadManagerList.cpp b/src/gui/components/wxDownloadManagerList.cpp index 14bf5cbe..fc63155d 100644 --- a/src/gui/components/wxDownloadManagerList.cpp +++ b/src/gui/components/wxDownloadManagerList.cpp @@ -25,9 +25,8 @@ wxDEFINE_EVENT(wxEVT_REMOVE_ENTRY, wxCommandEvent); - wxDownloadManagerList::wxDownloadManagerList(wxWindow* parent, wxWindowID id) - : wxListCtrl(parent, id, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_VIRTUAL) + : wxListView(parent, id, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_VIRTUAL) { AddColumns(); @@ -52,7 +51,7 @@ wxDownloadManagerList::wxDownloadManagerList(wxWindow* parent, wxWindowID id) boost::optional<const wxDownloadManagerList::TitleEntry&> wxDownloadManagerList::GetSelectedTitleEntry() const { - const auto selection = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + const auto selection = GetFirstSelected(); if (selection != wxNOT_FOUND) { const auto tmp = GetTitleEntry(selection); @@ -65,7 +64,7 @@ boost::optional<const wxDownloadManagerList::TitleEntry&> wxDownloadManagerList: boost::optional<wxDownloadManagerList::TitleEntry&> wxDownloadManagerList::GetSelectedTitleEntry() { - const auto selection = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + const auto selection = GetFirstSelected(); if (selection != wxNOT_FOUND) { const auto tmp = GetTitleEntry(selection); @@ -324,7 +323,7 @@ void wxDownloadManagerList::OnContextMenu(wxContextMenuEvent& event) wxMenu menu; menu.Bind(wxEVT_COMMAND_MENU_SELECTED, &wxDownloadManagerList::OnContextMenuSelected, this); - const auto selection = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + const auto selection = GetFirstSelected(); if (selection == wxNOT_FOUND) return; @@ -379,8 +378,8 @@ 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); + + const auto selection = GetFirstSelected(); if (selection == wxNOT_FOUND) return; diff --git a/src/gui/components/wxDownloadManagerList.h b/src/gui/components/wxDownloadManagerList.h index 3a6b853a..6723056a 100644 --- a/src/gui/components/wxDownloadManagerList.h +++ b/src/gui/components/wxDownloadManagerList.h @@ -9,7 +9,7 @@ #include <utility> #include <vector> -class wxDownloadManagerList : public wxListCtrl +class wxDownloadManagerList : public wxListView { friend class TitleManager; public: diff --git a/src/gui/components/wxGameList.cpp b/src/gui/components/wxGameList.cpp index 95711fef..4901e2d4 100644 --- a/src/gui/components/wxGameList.cpp +++ b/src/gui/components/wxGameList.cpp @@ -6,6 +6,7 @@ #include <numeric> +#include <wx/listctrl.h> #include <wx/wupdlock.h> #include <wx/menu.h> #include <wx/mstream.h> @@ -133,7 +134,7 @@ bool writeICNS(const fs::path& pngPath, const fs::path& icnsPath) { } wxGameList::wxGameList(wxWindow* parent, wxWindowID id) - : wxListCtrl(parent, id, wxDefaultPosition, wxDefaultSize, GetStyleFlags(Style::kList)), m_style(Style::kList) + : wxListView(parent, id, wxDefaultPosition, wxDefaultSize, GetStyleFlags(Style::kList)), m_style(Style::kList) { const auto& config = GetConfig(); @@ -393,7 +394,7 @@ void wxGameList::SetStyle(Style style, bool save) SetWindowStyleFlag(GetStyleFlags(m_style)); uint64 selected_title_id = 0; - auto selection = GetNextItem(wxNOT_FOUND, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + auto selection = GetFirstSelected(); if (selection != wxNOT_FOUND) { selected_title_id = (uint64)GetItemData(selection); @@ -416,8 +417,8 @@ void wxGameList::SetStyle(Style style, bool save) if(selection != wxNOT_FOUND) { - SetItemState(selection, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED); - EnsureVisible(selection); + Select(selection); + Focus(selection); } if(save) @@ -549,15 +550,14 @@ void wxGameList::OnKeyDown(wxListEvent& event) const auto item_count = GetItemCount(); if (item_count > 0) { - auto selection = (int)GetNextItem(wxNOT_FOUND, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + auto selection = (int)GetFirstSelected(); if (selection == wxNOT_FOUND) selection = 0; else selection = std::max(0, selection - GetCountPerPage()); - SetItemState(wxNOT_FOUND, 0, wxLIST_STATE_SELECTED); - SetItemState(selection, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED); - EnsureVisible(selection); + Select(selection); + Focus(selection); } } else if (keycode == WXK_RIGHT) @@ -565,15 +565,14 @@ void wxGameList::OnKeyDown(wxListEvent& event) const auto item_count = GetItemCount(); if (item_count > 0) { - auto selection = (int)GetNextItem(wxNOT_FOUND, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + auto selection = (int)GetFirstSelected(); if (selection == wxNOT_FOUND) selection = 0; selection = std::min(item_count - 1, selection + GetCountPerPage()); - SetItemState(wxNOT_FOUND, 0, wxLIST_STATE_SELECTED); - SetItemState(selection, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED); - EnsureVisible(selection); + Select(selection); + Focus(selection); } } } @@ -613,7 +612,7 @@ void wxGameList::OnContextMenu(wxContextMenuEvent& event) wxMenu menu; menu.Bind(wxEVT_COMMAND_MENU_SELECTED, &wxGameList::OnContextMenuSelected, this); - const auto selection = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + const auto selection = GetFirstSelected(); if (selection != wxNOT_FOUND) { const auto title_id = (uint64)GetItemData(selection); @@ -1632,4 +1631,4 @@ void wxGameList::CreateShortcut(GameInfo2& gameInfo) wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR); } } -#endif \ No newline at end of file +#endif diff --git a/src/gui/components/wxGameList.h b/src/gui/components/wxGameList.h index a6bfa7f7..698f1294 100644 --- a/src/gui/components/wxGameList.h +++ b/src/gui/components/wxGameList.h @@ -30,7 +30,7 @@ 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 +class wxGameList : public wxListView { friend class MainWindow; public: diff --git a/src/gui/components/wxTitleManagerList.cpp b/src/gui/components/wxTitleManagerList.cpp index c0bf5778..2752ddda 100644 --- a/src/gui/components/wxTitleManagerList.cpp +++ b/src/gui/components/wxTitleManagerList.cpp @@ -38,7 +38,7 @@ wxDEFINE_EVENT(wxEVT_TITLE_REMOVED, wxCommandEvent); wxDEFINE_EVENT(wxEVT_REMOVE_ENTRY, wxCommandEvent); wxTitleManagerList::wxTitleManagerList(wxWindow* parent, wxWindowID id) - : wxListCtrl(parent, id, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_VIRTUAL) + : wxListView(parent, id, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_VIRTUAL) { AddColumns(); @@ -74,7 +74,7 @@ wxTitleManagerList::~wxTitleManagerList() boost::optional<const wxTitleManagerList::TitleEntry&> wxTitleManagerList::GetSelectedTitleEntry() const { - const auto selection = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + const auto selection = GetFirstSelected(); if (selection != wxNOT_FOUND) { const auto tmp = GetTitleEntry(selection); @@ -87,7 +87,7 @@ boost::optional<const wxTitleManagerList::TitleEntry&> wxTitleManagerList::GetSe boost::optional<wxTitleManagerList::TitleEntry&> wxTitleManagerList::GetSelectedTitleEntry() { - const auto selection = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + const auto selection = GetFirstSelected(); if (selection != wxNOT_FOUND) { const auto tmp = GetTitleEntry(selection); @@ -757,7 +757,7 @@ void wxTitleManagerList::OnContextMenu(wxContextMenuEvent& event) wxMenu menu; menu.Bind(wxEVT_COMMAND_MENU_SELECTED, &wxTitleManagerList::OnContextMenuSelected, this); - const auto selection = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + const auto selection = GetFirstSelected(); if (selection == wxNOT_FOUND) return; @@ -855,8 +855,8 @@ void wxTitleManagerList::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); + + const auto selection = GetFirstSelected(); if (selection == wxNOT_FOUND) return; diff --git a/src/gui/components/wxTitleManagerList.h b/src/gui/components/wxTitleManagerList.h index 2780a9ce..8057bdd8 100644 --- a/src/gui/components/wxTitleManagerList.h +++ b/src/gui/components/wxTitleManagerList.h @@ -9,7 +9,7 @@ #include <utility> #include <vector> -class wxTitleManagerList : public wxListCtrl +class wxTitleManagerList : public wxListView { friend class TitleManager; public: diff --git a/src/gui/debugger/BreakpointWindow.cpp b/src/gui/debugger/BreakpointWindow.cpp index 141e4603..c693477d 100644 --- a/src/gui/debugger/BreakpointWindow.cpp +++ b/src/gui/debugger/BreakpointWindow.cpp @@ -230,8 +230,8 @@ void BreakpointWindow::OnRightDown(wxMouseEvent& event) } else { - m_breakpoints->SetItemState(index, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED); - m_breakpoints->SetItemState(index, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); + m_breakpoints->Focus(index); + m_breakpoints->Select(index); wxMenu menu; menu.Append(MENU_ID_DELETE_BP, _("Delete breakpoint")); @@ -245,7 +245,7 @@ void BreakpointWindow::OnContextMenuClickSelected(wxCommandEvent& evt) { if (evt.GetId() == MENU_ID_DELETE_BP) { - long sel = m_breakpoints->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + long sel = m_breakpoints->GetFirstSelected(); if (sel != wxNOT_FOUND) { if (sel >= debuggerState.breakpoints.size()) diff --git a/src/gui/debugger/SymbolCtrl.cpp b/src/gui/debugger/SymbolCtrl.cpp index fd079d7b..57cc96f6 100644 --- a/src/gui/debugger/SymbolCtrl.cpp +++ b/src/gui/debugger/SymbolCtrl.cpp @@ -2,6 +2,7 @@ #include "gui/guiWrapper.h" #include "Cafe/OS/RPL/rpl_symbol_storage.h" #include "Cafe/HW/Espresso/Debugger/Debugger.h" +#include <wx/listctrl.h> enum ItemColumns { @@ -10,8 +11,7 @@ enum ItemColumns ColumnModule, }; -SymbolListCtrl::SymbolListCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, const wxSize& size) : - wxListCtrl(parent, id, pos, size, wxLC_REPORT | wxLC_VIRTUAL) +SymbolListCtrl::SymbolListCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, const wxSize& size) : wxListView(parent, id, pos, size, wxLC_REPORT | wxLC_VIRTUAL) { wxListItem col0; col0.SetId(ColumnName); @@ -106,7 +106,7 @@ wxString SymbolListCtrl::OnGetItemText(long item, long column) const void SymbolListCtrl::OnLeftDClick(wxListEvent& event) { - long selected = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + long selected = GetFirstSelected(); if (selected == wxNOT_FOUND) return; const auto text = GetItemText(selected, ColumnAddress); @@ -119,7 +119,7 @@ void SymbolListCtrl::OnLeftDClick(wxListEvent& event) void SymbolListCtrl::OnRightClick(wxListEvent& event) { - long selected = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + long selected = GetFirstSelected(); if (selected == wxNOT_FOUND) return; auto text = GetItemText(selected, ColumnAddress); diff --git a/src/gui/debugger/SymbolCtrl.h b/src/gui/debugger/SymbolCtrl.h index 81ccd326..8a0161bd 100644 --- a/src/gui/debugger/SymbolCtrl.h +++ b/src/gui/debugger/SymbolCtrl.h @@ -2,7 +2,7 @@ #include <wx/listctrl.h> -class SymbolListCtrl : public wxListCtrl +class SymbolListCtrl : public wxListView { public: SymbolListCtrl(wxWindow* parent, const wxWindowID& id, const wxPoint& pos, const wxSize& size); diff --git a/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp b/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp index 6b69b0f0..0669321c 100644 --- a/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp +++ b/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.cpp @@ -9,6 +9,7 @@ #include <cinttypes> #include <helpers/wxHelpers.h> +#include <wx/listctrl.h> enum { @@ -42,7 +43,7 @@ DebugPPCThreadsWindow::DebugPPCThreadsWindow(wxFrame& parent) 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 = new wxListView(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)); @@ -169,7 +170,7 @@ 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); + const int selection = m_thread_list->GetFirstSelected(); if (selection != wxNOT_FOUND) selected_thread = m_thread_list->GetItemData(selection); @@ -267,12 +268,15 @@ void DebugPPCThreadsWindow::RefreshThreadList() 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(); - } + if (selected_thread != 0 && selected_thread == (long)threadItrMPTR) + { + m_thread_list->Select(i); + m_thread_list->Focus(i); + } + } + srwlock_activeThreadList.UnlockWrite(); + __OSUnlockScheduler(); + } m_thread_list->SetScrollPos(0, scrollPos, true); } @@ -436,12 +440,11 @@ void DebugPPCThreadsWindow::OnThreadListRightClick(wxMouseEvent& event) 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); + m_thread_list->Focus(itemIndex); + long sel = m_thread_list->GetFirstSelected(); if (sel != wxNOT_FOUND) - m_thread_list->SetItemState(sel, 0, wxLIST_STATE_SELECTED); - m_thread_list->SetItemState(itemIndex, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); + m_thread_list->Select(sel, false); + m_thread_list->Select(itemIndex); // check if thread is still on the list of active threads MPTR threadMPTR = (MPTR)m_thread_list->GetItemData(itemIndex); __OSLockScheduler(); diff --git a/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.h b/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.h index 649780c5..f6dc9060 100644 --- a/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.h +++ b/src/gui/windows/PPCThreadsViewer/DebugPPCThreadsWindow.h @@ -23,7 +23,7 @@ private: void PresentProfileResults(OSThread_t* thread, const std::unordered_map<VAddr, uint32>& samples); void DumpStackTrace(struct OSThread_t* thread); - wxListCtrl* m_thread_list; + wxListView* m_thread_list; wxCheckBox* m_auto_refresh; wxTimer* m_timer; @@ -32,4 +32,4 @@ private: wxDECLARE_EVENT_TABLE(); -}; \ No newline at end of file +}; diff --git a/src/gui/wxcomponents/checkedlistctrl.h b/src/gui/wxcomponents/checkedlistctrl.h index 926c2ba7..e09536f9 100644 --- a/src/gui/wxcomponents/checkedlistctrl.h +++ b/src/gui/wxcomponents/checkedlistctrl.h @@ -72,7 +72,7 @@ DECLARE_EXPORTED_EVENT_TYPE(WXEXPORT, wxEVT_COMMAND_LIST_ITEM_UNCHECKED, -1); //! This is the class which performs all transactions with the server. //! It uses the wxSocket facilities. -class wxCheckedListCtrl : public wxListCtrl +class wxCheckedListCtrl : public wxListView { protected: @@ -85,7 +85,7 @@ protected: public: wxCheckedListCtrl() - : wxListCtrl(), m_imageList(16, 16, TRUE) {} + : wxListView(), m_imageList(16, 16, TRUE) {} wxCheckedListCtrl(wxWindow *parent, wxWindowID id = wxID_ANY, const wxPoint& pt = wxDefaultPosition, @@ -93,7 +93,7 @@ public: long style = wxCLC_CHECK_WHEN_SELECTING, const wxValidator& validator = wxDefaultValidator, const wxString& name = wxListCtrlNameStr) - : wxListCtrl(), m_imageList(16, 16, TRUE) + : wxListView(), m_imageList(16, 16, TRUE) { Create(parent, id, pt, sz, style, validator, name); } bool Create(wxWindow *parent, wxWindowID id = wxID_ANY, From f3fe6f3455334a2ec858c9e9059a03cb9b9992e3 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Fri, 13 Jun 2025 12:47:46 +0200 Subject: [PATCH 281/299] GameList: Allow sorting by more columns (#1571) --- src/Cafe/IOSU/PDM/iosu_pdm.cpp | 29 +++++++++++++++ src/Cafe/IOSU/PDM/iosu_pdm.h | 6 ++- src/gui/components/wxGameList.cpp | 61 ++++++++++++++++++++++++------- src/gui/components/wxGameList.h | 6 +-- 4 files changed, 84 insertions(+), 18 deletions(-) diff --git a/src/Cafe/IOSU/PDM/iosu_pdm.cpp b/src/Cafe/IOSU/PDM/iosu_pdm.cpp index d94b1dbf..b9dda445 100644 --- a/src/Cafe/IOSU/PDM/iosu_pdm.cpp +++ b/src/Cafe/IOSU/PDM/iosu_pdm.cpp @@ -464,5 +464,34 @@ namespace iosu return static_cast<IOSUModule*>(&sIOSUModuleNNPDM); } + + bool GameListStat::LastPlayDate::operator<(const LastPlayDate& b) const + { + const auto& a = *this; + + if(a.year < b.year) + return true; + if(a.year > b.year) + return false; + + // same year + if(a.month < b.month) + return true; + if(a.month > b.month) + return false; + + // same year and month + return a.day < b.day; + } + + bool GameListStat::LastPlayDate::operator==(const LastPlayDate& b) const + { + const auto& a = *this; + return a.year == b.year && + a.month == b.month && + a.day == b.day; + } + std::weak_ordering GameListStat::LastPlayDate::operator<=>(const LastPlayDate& b) const = default; + }; }; diff --git a/src/Cafe/IOSU/PDM/iosu_pdm.h b/src/Cafe/IOSU/PDM/iosu_pdm.h index 0dd8a39d..63f99a4a 100644 --- a/src/Cafe/IOSU/PDM/iosu_pdm.h +++ b/src/Cafe/IOSU/PDM/iosu_pdm.h @@ -21,11 +21,15 @@ namespace iosu /* Helper for UI game list */ struct GameListStat { - struct + struct LastPlayDate { uint32 year; // if 0 -> never played uint32 month; uint32 day; + + bool operator<(const LastPlayDate& b) const; + bool operator==(const LastPlayDate& b) const; + std::weak_ordering operator<=>(const LastPlayDate& b) const; }last_played; uint32 numMinutesPlayed; }; diff --git a/src/gui/components/wxGameList.cpp b/src/gui/components/wxGameList.cpp index 4901e2d4..61d57bcc 100644 --- a/src/gui/components/wxGameList.cpp +++ b/src/gui/components/wxGameList.cpp @@ -485,25 +485,57 @@ static inline int order_to_int(const std::weak_ordering &wo) return 0; } -int wxGameList::SortComparator(uint64 titleId1, uint64 titleId2, SortData* sortData) +std::weak_ordering wxGameList::SortComparator(uint64 titleId1, uint64 titleId2, SortData* sortData) { - const auto isFavoriteA = GetConfig().IsGameListFavorite(titleId1); - const auto isFavoriteB = GetConfig().IsGameListFavorite(titleId2); - const auto& name1 = GetNameByTitleId(titleId1); - const auto& name2 = GetNameByTitleId(titleId2); + auto titleLastPlayed = [](uint64_t id) + { + iosu::pdm::GameListStat playTimeStat{}; + iosu::pdm::GetStatForGamelist(id, playTimeStat); + return playTimeStat; + }; - if(sortData->dir > 0) - return order_to_int(std::tie(isFavoriteB, name1) <=> std::tie(isFavoriteA, name2)); - else - return order_to_int(std::tie(isFavoriteB, name2) <=> std::tie(isFavoriteA, name1)); + auto titlePlayMinutes = [](uint64_t id) + { + iosu::pdm::GameListStat playTimeStat; + if (!iosu::pdm::GetStatForGamelist(id, playTimeStat)) + return 0u; + return playTimeStat.numMinutesPlayed; + }; + + auto titleRegion = [](uint64_t id) + { + return CafeTitleList::GetGameInfo(id).GetRegion(); + }; + + switch(sortData->column) + { + default: + case ColumnName: + { + const auto isFavoriteA = GetConfig().IsGameListFavorite(titleId1); + const auto isFavoriteB = GetConfig().IsGameListFavorite(titleId2); + const auto nameA = GetNameByTitleId(titleId1); + const auto nameB = GetNameByTitleId(titleId2); + return std::tie(isFavoriteB, nameA) <=> std::tie(isFavoriteA, nameB); + } + case ColumnGameStarted: + return titleLastPlayed(titleId1).last_played <=> titleLastPlayed(titleId2).last_played; + case ColumnGameTime: + return titlePlayMinutes(titleId1) <=> titlePlayMinutes(titleId2); + case ColumnRegion: + return titleRegion(titleId1) <=> titleRegion(titleId2); + case ColumnTitleID: + return titleId1 <=> titleId2; + } + // unreachable + cemu_assert_debug(false); + return std::weak_ordering::less; } int wxGameList::SortFunction(wxIntPtr item1, wxIntPtr item2, wxIntPtr sortData) { const auto sort_data = (SortData*)sortData; - const int dir = sort_data->dir; - - return sort_data->thisptr->SortComparator((uint64)item1, (uint64)item2, sort_data); + return sort_data->dir * order_to_int(sort_data->thisptr->SortComparator((uint64)item1, (uint64)item2, sort_data)); } void wxGameList::SortEntries(int column) @@ -530,8 +562,9 @@ void wxGameList::SortEntries(int column) case ColumnGameTime: case ColumnGameStarted: case ColumnRegion: + case ColumnTitleID: { - SortData data{ this, column, s_direction }; + SortData data{ this, ItemColumns{column}, s_direction }; SortItems(SortFunction, (wxIntPtr)&data); break; } @@ -1049,7 +1082,7 @@ void wxGameList::OnClose(wxCloseEvent& event) int wxGameList::FindInsertPosition(TitleId titleId) { - SortData data{ this, s_last_column, s_direction }; + SortData data{ this, ItemColumns{s_last_column}, s_direction }; const auto itemCount = GetItemCount(); if (itemCount == 0) return 0; diff --git a/src/gui/components/wxGameList.h b/src/gui/components/wxGameList.h index 698f1294..da1baec8 100644 --- a/src/gui/components/wxGameList.h +++ b/src/gui/components/wxGameList.h @@ -68,7 +68,7 @@ private: inline static const wxColour kSecondColor{ 0xFDF9F2 }; void UpdateItemColors(sint32 startIndex = 0); - enum ItemColumns + enum ItemColumns : int { ColumnHiddenName = 0, ColumnIcon, @@ -89,12 +89,12 @@ private: struct SortData { wxGameList* thisptr; - int column; + ItemColumns column; int dir; }; int FindInsertPosition(TitleId titleId); - int SortComparator(uint64 titleId1, uint64 titleId2, SortData* sortData); + std::weak_ordering SortComparator(uint64 titleId1, uint64 titleId2, SortData* sortData); static int SortFunction(wxIntPtr item1, wxIntPtr item2, wxIntPtr sortData); wxTimer* m_tooltip_timer; From 95dc590d2c5805235168096ecbf1ba55a0cb6112 Mon Sep 17 00:00:00 2001 From: oltolm <oleg.tolmatcev@gmail.com> Date: Sat, 14 Jun 2025 10:25:56 +0200 Subject: [PATCH 282/299] UI: Improve wxListView sorting and add sort order indicators (#1597) --- src/gui/components/wxDownloadManagerList.cpp | 42 ++++++++++---------- src/gui/components/wxDownloadManagerList.h | 5 +-- src/gui/components/wxGameList.cpp | 28 ++++++------- src/gui/components/wxGameList.h | 2 - src/gui/components/wxTitleManagerList.cpp | 42 +++++++++----------- src/gui/components/wxTitleManagerList.h | 2 - src/gui/helpers/wxLogEvent.h | 2 +- 7 files changed, 55 insertions(+), 68 deletions(-) diff --git a/src/gui/components/wxDownloadManagerList.cpp b/src/gui/components/wxDownloadManagerList.cpp index fc63155d..d447310c 100644 --- a/src/gui/components/wxDownloadManagerList.cpp +++ b/src/gui/components/wxDownloadManagerList.cpp @@ -47,6 +47,8 @@ wxDownloadManagerList::wxDownloadManagerList(wxWindow* parent, wxWindowID id) Bind(wxEVT_REMOVE_ITEM, &wxDownloadManagerList::OnRemoveItem, this); Bind(wxEVT_REMOVE_ENTRY, &wxDownloadManagerList::OnRemoveEntry, this); Bind(wxEVT_CLOSE_WINDOW, &wxDownloadManagerList::OnClose, this); + + ShowSortIndicator(ColumnName); } boost::optional<const wxDownloadManagerList::TitleEntry&> wxDownloadManagerList::GetSelectedTitleEntry() const @@ -217,16 +219,7 @@ 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(); + SortEntries(column); event.Skip(); } @@ -620,24 +613,31 @@ bool wxDownloadManagerList::SortFunc(std::span<int> sortColumnOrder, const Type_ #include <boost/container/small_vector.hpp> -void wxDownloadManagerList::SortEntries() +void wxDownloadManagerList::SortEntries(int column) { boost::container::small_vector<int, 12> s_SortColumnOrder{ ColumnName, ColumnType, ColumnVersion, ColumnTitleId, ColumnProgress }; - if (m_sort_by_column != -1) + bool ascending; + if (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); + column = GetSortIndicator(); + if (column == -1) + column = ColumnName; + ascending = IsAscendingSortIndicator(); } + else + ascending = GetUpdatedAscendingSortIndicator(column); + + // 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(), column), s_SortColumnOrder.end()); + s_SortColumnOrder.insert(s_SortColumnOrder.begin(), 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; - }); - + [this, &s_SortColumnOrder, ascending](const Type_t& v1, const Type_t& v2) -> bool { + return ascending ? SortFunc(s_SortColumnOrder, v1, v2) : SortFunc(s_SortColumnOrder, v2, v1); + }); + + ShowSortIndicator(column, ascending); RefreshPage(); } diff --git a/src/gui/components/wxDownloadManagerList.h b/src/gui/components/wxDownloadManagerList.h index 6723056a..4febb461 100644 --- a/src/gui/components/wxDownloadManagerList.h +++ b/src/gui/components/wxDownloadManagerList.h @@ -49,7 +49,7 @@ public: // error state? }; - void SortEntries(); + void SortEntries(int column = -1); void RefreshPage(); void Filter(const wxString& filter); void Filter2(bool showTitles, bool showUpdates, bool showInstalled); @@ -138,9 +138,6 @@ private: 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; diff --git a/src/gui/components/wxGameList.cpp b/src/gui/components/wxGameList.cpp index 61d57bcc..d70cb376 100644 --- a/src/gui/components/wxGameList.cpp +++ b/src/gui/components/wxGameList.cpp @@ -193,6 +193,8 @@ wxGameList::wxGameList(wxWindow* parent, wxWindowID id) // start async worker (for icon loading) m_async_worker_active = true; m_async_worker_thread = std::thread(&wxGameList::AsyncWorkerThread, this); + + ShowSortIndicator(ColumnName); } wxGameList::~wxGameList() @@ -540,21 +542,16 @@ int wxGameList::SortFunction(wxIntPtr item1, wxIntPtr item2, wxIntPtr sortData) void wxGameList::SortEntries(int column) { + bool ascending; if (column == -1) - column = s_last_column; - else { - if (s_last_column == column) - { - s_last_column = 0; - s_direction = -1; - } - else - { - s_last_column = column; - s_direction = 1; - } + column = GetSortIndicator(); + if (column == -1) + column = ColumnName; + ascending = IsAscendingSortIndicator(); } + else + ascending = GetUpdatedAscendingSortIndicator(column); switch (column) { @@ -564,8 +561,9 @@ void wxGameList::SortEntries(int column) case ColumnRegion: case ColumnTitleID: { - SortData data{ this, ItemColumns{column}, s_direction }; + SortData data{this, ItemColumns{column}, ascending ? 1 : -1}; SortItems(SortFunction, (wxIntPtr)&data); + ShowSortIndicator(column, ascending); break; } } @@ -577,7 +575,7 @@ void wxGameList::OnKeyDown(wxListEvent& event) if (m_style != Style::kList) return; - const auto keycode = std::tolower(event.m_code); + const auto keycode = event.GetKeyCode(); if (keycode == WXK_LEFT) { const auto item_count = GetItemCount(); @@ -1082,7 +1080,7 @@ void wxGameList::OnClose(wxCloseEvent& event) int wxGameList::FindInsertPosition(TitleId titleId) { - SortData data{ this, ItemColumns{s_last_column}, s_direction }; + SortData data{this, ItemColumns(GetSortIndicator()), IsAscendingSortIndicator()}; const auto itemCount = GetItemCount(); if (itemCount == 0) return 0; diff --git a/src/gui/components/wxGameList.h b/src/gui/components/wxGameList.h index da1baec8..625f7976 100644 --- a/src/gui/components/wxGameList.h +++ b/src/gui/components/wxGameList.h @@ -83,8 +83,6 @@ private: ColumnCounts, }; - int s_last_column = ColumnName; - int s_direction = 1; void SortEntries(int column = -1); struct SortData { diff --git a/src/gui/components/wxTitleManagerList.cpp b/src/gui/components/wxTitleManagerList.cpp index 2752ddda..2f4a9ed1 100644 --- a/src/gui/components/wxTitleManagerList.cpp +++ b/src/gui/components/wxTitleManagerList.cpp @@ -64,6 +64,8 @@ wxTitleManagerList::wxTitleManagerList(wxWindow* parent, wxWindowID id) m_callbackIdTitleList = CafeTitleList::RegisterCallback([](CafeTitleListCallbackEvent* evt, void* ctx) { ((wxTitleManagerList*)ctx)->HandleTitleListCallback(evt); }, this); m_callbackIdSaveList = CafeSaveList::RegisterCallback([](CafeSaveListCallbackEvent* evt, void* ctx) { ((wxTitleManagerList*)ctx)->HandleSaveListCallback(evt); }, this); + + ShowSortIndicator(ColumnTitleId); } wxTitleManagerList::~wxTitleManagerList() @@ -1173,54 +1175,48 @@ bool wxTitleManagerList::SortFunc(int column, const Type_t& v1, const Type_t& v2 { if(entry1.version == entry2.version) return SortFunc(ColumnTitleId, v1, v2); - - return std::underlying_type_t<EntryType>(entry1.version) < std::underlying_type_t<EntryType>(entry2.version); + + return entry1.version < entry2.version; } else if (column == ColumnRegion) { if(entry1.region == entry2.region) return SortFunc(ColumnTitleId, v1, v2); - - return std::underlying_type_t<EntryType>(entry1.region) < std::underlying_type_t<EntryType>(entry2.region); + + return std::underlying_type_t<CafeConsoleRegion>(entry1.region) < std::underlying_type_t<CafeConsoleRegion>(entry2.region); } else if (column == ColumnFormat) { if(entry1.format == entry2.format) return SortFunc(ColumnType, v1, v2); - return std::underlying_type_t<EntryType>(entry1.format) < std::underlying_type_t<EntryType>(entry2.format); + return std::underlying_type_t<EntryFormat>(entry1.format) < std::underlying_type_t<EntryFormat>(entry2.format); } return false; } void wxTitleManagerList::SortEntries(int column) { - if(column == -1) + bool ascending; + if (column == -1) { - column = m_last_column_sorted; - m_last_column_sorted = -1; + column = GetSortIndicator(); if (column == -1) column = ColumnTitleId; + ascending = IsAscendingSortIndicator(); } - + else + ascending = GetUpdatedAscendingSortIndicator(column); + if (column != ColumnTitleId && column != ColumnName && column != ColumnType && column != ColumnVersion && column != ColumnRegion && column != ColumnFormat) return; - if (m_last_column_sorted != column) - { - m_last_column_sorted = column; - m_sort_less = true; - } - else - m_sort_less = !m_sort_less; - std::sort(m_sorted_data.begin(), m_sorted_data.end(), - [this, column](const Type_t& v1, const Type_t& v2) -> bool - { - const bool result = SortFunc(column, v1, v2); - return m_sort_less ? result : !result; - }); - + [this, column, ascending](const Type_t& v1, const Type_t& v2) -> bool { + return ascending ? SortFunc(column, v1, v2) : SortFunc(column, v2, v1); + }); + + ShowSortIndicator(column, ascending); RefreshPage(); } diff --git a/src/gui/components/wxTitleManagerList.h b/src/gui/components/wxTitleManagerList.h index 8057bdd8..c21145b7 100644 --- a/src/gui/components/wxTitleManagerList.h +++ b/src/gui/components/wxTitleManagerList.h @@ -127,8 +127,6 @@ private: 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); diff --git a/src/gui/helpers/wxLogEvent.h b/src/gui/helpers/wxLogEvent.h index 8cef1d2d..23f6533d 100644 --- a/src/gui/helpers/wxLogEvent.h +++ b/src/gui/helpers/wxLogEvent.h @@ -12,7 +12,7 @@ public: : 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) { } + : wxCommandEvent(event), m_filter(event.GetFilter()), m_message(event.GetMessage()) { } wxEvent* Clone() const { return new wxLogEvent(*this); } From da98aa41768e90a58e060adcd7f7c2c58f7dbc8d Mon Sep 17 00:00:00 2001 From: oltolm <oleg.tolmatcev@gmail.com> Date: Sat, 14 Jun 2025 20:38:53 +0200 Subject: [PATCH 283/299] UI: Make code compatible with wxWidgets 3.3 (#1598) --- src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp | 2 +- src/gui/TitleManager.cpp | 2 +- src/gui/components/wxTitleManagerList.cpp | 2 +- src/gui/debugger/DisasmCtrl.cpp | 6 +++--- src/gui/debugger/DumpCtrl.cpp | 4 ++-- src/gui/debugger/RegisterCtrl.cpp | 6 +++--- src/gui/debugger/RegisterWindow.cpp | 6 +++--- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp index 2186f8ea..53e3b995 100644 --- a/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp +++ b/src/gui/EmulatedUSBDevices/EmulatedUSBDeviceFrame.cpp @@ -283,7 +283,7 @@ void EmulatedUSBDeviceFrame::LoadSkylanderPath(uint8 slot, wxString path) std::unique_ptr<FileStream> skyFile(FileStream::openFile2(_utf8ToPath(path.utf8_string()), true)); if (!skyFile) { - wxMessageDialog open_error(this, "Error Opening File: " + path.c_str()); + wxMessageDialog open_error(this, "Error Opening File: " + path); open_error.ShowModal(); return; } diff --git a/src/gui/TitleManager.cpp b/src/gui/TitleManager.cpp index 1dd41b3b..67f13e05 100644 --- a/src/gui/TitleManager.cpp +++ b/src/gui/TitleManager.cpp @@ -356,7 +356,7 @@ void TitleManager::OnTitleSearchComplete(wxCommandEvent& event) void TitleManager::OnSetStatusBarText(wxSetStatusBarTextEvent& event) { - m_status_bar->SetStatusText(_(event.GetText()), event.GetNumber()); + m_status_bar->SetStatusText(event.GetText(), event.GetNumber()); } void TitleManager::OnFilterChanged(wxCommandEvent& event) diff --git a/src/gui/components/wxTitleManagerList.cpp b/src/gui/components/wxTitleManagerList.cpp index 2f4a9ed1..a5774b14 100644 --- a/src/gui/components/wxTitleManagerList.cpp +++ b/src/gui/components/wxTitleManagerList.cpp @@ -576,7 +576,7 @@ void wxTitleManagerList::OnConvertToCompressedFormat(uint64 titleId, uint64 righ } else { - progressDialog.Update(0, _("Collecting list of files..." + fmt::format(" ({})", writerContext.totalFileCount.load()))); + progressDialog.Update(0, _("Collecting list of files...") + fmt::format(" ({})", writerContext.totalFileCount.load())); } if (progressDialog.WasCancelled()) writerContext.cancelled.store(true); diff --git a/src/gui/debugger/DisasmCtrl.cpp b/src/gui/debugger/DisasmCtrl.cpp index e74d64b9..1f4b739f 100644 --- a/src/gui/debugger/DisasmCtrl.cpp +++ b/src/gui/debugger/DisasmCtrl.cpp @@ -615,7 +615,7 @@ void DisasmCtrl::OnMouseDClick(const wxPoint& position, uint32 line) { // 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); + 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 }; @@ -637,7 +637,7 @@ void DisasmCtrl::OnMouseDClick(const wxPoint& position, uint32 line) 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); + 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()); @@ -850,4 +850,4 @@ void DisasmCtrl::GoToAddressDialog() } } } -} \ No newline at end of file +} diff --git a/src/gui/debugger/DumpCtrl.cpp b/src/gui/debugger/DumpCtrl.cpp index 16fdd87d..fad93bd3 100644 --- a/src/gui/debugger/DumpCtrl.cpp +++ b/src/gui/debugger/DumpCtrl.cpp @@ -196,7 +196,7 @@ void DumpCtrl::OnMouseDClick(const wxPoint& position, uint32 line) 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)); + 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); @@ -303,4 +303,4 @@ void DumpCtrl::OnKeyPressed(sint32 key_code, const wxPoint& position) wxSize DumpCtrl::DoGetBestSize() const { return TextList::DoGetBestSize(); -} \ No newline at end of file +} diff --git a/src/gui/debugger/RegisterCtrl.cpp b/src/gui/debugger/RegisterCtrl.cpp index bcf6fb5a..24cae60b 100644 --- a/src/gui/debugger/RegisterCtrl.cpp +++ b/src/gui/debugger/RegisterCtrl.cpp @@ -201,7 +201,7 @@ void RegisterCtrl::OnMouseDClick(const wxPoint& position, uint32 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)); + 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); @@ -220,7 +220,7 @@ void RegisterCtrl::OnMouseDClick(const wxPoint& position, uint32 line) 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)); + 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()); @@ -234,7 +234,7 @@ void RegisterCtrl::OnMouseDClick(const wxPoint& position, uint32 line) 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)); + 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()); diff --git a/src/gui/debugger/RegisterWindow.cpp b/src/gui/debugger/RegisterWindow.cpp index 55c6386f..b18d4e27 100644 --- a/src/gui/debugger/RegisterWindow.cpp +++ b/src/gui/debugger/RegisterWindow.cpp @@ -339,7 +339,7 @@ void RegisterWindow::OnMouseDClickEvent(wxMouseEvent& event) { 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)); + 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); @@ -355,7 +355,7 @@ void RegisterWindow::OnMouseDClickEvent(wxMouseEvent& event) { 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)); + 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()); @@ -371,7 +371,7 @@ void RegisterWindow::OnMouseDClickEvent(wxMouseEvent& event) { 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)); + 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()); From 2f02fda9eaa312f061e26509cba3a8e62e74d393 Mon Sep 17 00:00:00 2001 From: oltolm <oleg.tolmatcev@gmail.com> Date: Mon, 16 Jun 2025 23:25:06 +0200 Subject: [PATCH 284/299] Refactor to use Microsoft::WRL::ComPtr (#1599) --- src/Common/precompiled.h | 2 - src/audio/DirectSoundAPI.cpp | 32 +++----------- src/audio/DirectSoundAPI.h | 15 +++---- src/audio/XAudio2API.cpp | 43 ++++++------------- src/audio/XAudio2API.h | 8 +--- src/gui/components/wxGameList.cpp | 11 +++-- .../api/DirectInput/DirectInputController.cpp | 9 +--- .../api/DirectInput/DirectInputController.h | 7 +-- .../DirectInputControllerProvider.cpp | 5 +-- .../DirectInputControllerProvider.h | 7 +-- src/util/DXGIWrapper/DXGIWrapper.h | 36 ++++++---------- 11 files changed, 56 insertions(+), 119 deletions(-) diff --git a/src/Common/precompiled.h b/src/Common/precompiled.h index 9e5c60f5..1185a34b 100644 --- a/src/Common/precompiled.h +++ b/src/Common/precompiled.h @@ -385,8 +385,6 @@ template <typename T1, typename T2> constexpr bool HAS_FLAG(T1 flags, T2 test_flag) { return (flags & (T1)test_flag) == (T1)test_flag; } template <typename T1, typename T2> constexpr bool HAS_BIT(T1 value, T2 index) { return (value & ((T1)1 << index)) != 0; } -template <typename T> -constexpr void SAFE_RELEASE(T& p) { if (p) { p->Release(); p = nullptr; } } template <typename T> constexpr uint32_t ppcsizeof() { return (uint32_t) sizeof(T); } diff --git a/src/audio/DirectSoundAPI.cpp b/src/audio/DirectSoundAPI.cpp index eabd3a7e..64042515 100644 --- a/src/audio/DirectSoundAPI.cpp +++ b/src/audio/DirectSoundAPI.cpp @@ -1,9 +1,8 @@ #include "DirectSoundAPI.h" -#include "gui/wxgui.h" - #include "util/helpers/helpers.h" #include "gui/guiWrapper.h" +#include <wrl/client.h> #pragma comment(lib, "Dsound.lib") @@ -15,12 +14,9 @@ std::wstring DirectSoundAPI::DirectSoundDeviceDescription::GetIdentifier() const DirectSoundAPI::DirectSoundAPI(GUID* guid, sint32 samplerate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample) : IAudioAPI(samplerate, channels, samples_per_block, bits_per_sample) { - LPDIRECTSOUND8 direct_sound; - if (DirectSoundCreate8(guid, &direct_sound, nullptr) != DS_OK) + if (DirectSoundCreate8(guid, &m_direct_sound, nullptr) != DS_OK) throw std::runtime_error("can't create directsound device"); - m_direct_sound = decltype(m_direct_sound)(direct_sound); - if (FAILED(m_direct_sound->SetCooperativeLevel(gui_getWindowInfo().window_main.hwnd, DSSCL_PRIORITY))) throw std::runtime_error("can't set directsound priority"); @@ -30,7 +26,7 @@ DirectSoundAPI::DirectSoundAPI(GUID* guid, sint32 samplerate, sint32 channels, s bd.dwBufferBytes = kBufferCount * m_bytesPerBlock; // kBlockCount * (samples_per_block * channels * (bits_per_sample / 8)); bd.lpwfxFormat = (LPWAVEFORMATEX)&m_wfx; - LPDIRECTSOUNDBUFFER sound_buffer; + Microsoft::WRL::ComPtr<IDirectSoundBuffer> sound_buffer; if (FAILED(m_direct_sound->CreateSoundBuffer(&bd, &sound_buffer, nullptr))) throw std::runtime_error("can't create directsound soundbuffer"); @@ -41,27 +37,17 @@ DirectSoundAPI::DirectSoundAPI(GUID* guid, sint32 samplerate, sint32 channels, s m_sound_buffer_size = caps.dwBufferBytes; - LPDIRECTSOUNDBUFFER8 sound_buffer8; - LPDIRECTSOUNDNOTIFY8 notify8; - sound_buffer->QueryInterface(IID_IDirectSoundBuffer8, (void**)&sound_buffer8); + Microsoft::WRL::ComPtr<IDirectSoundNotify8> notify8; - if (!sound_buffer8) + if (FAILED(sound_buffer->QueryInterface(IID_IDirectSoundBuffer8, &m_sound_buffer))) { - sound_buffer->Release(); throw std::runtime_error("can't get directsound buffer interface"); } - m_sound_buffer = decltype(m_sound_buffer)(sound_buffer8); - - sound_buffer->QueryInterface(IID_IDirectSoundNotify8, (void**)¬ify8); - if (!notify8) + if (FAILED(sound_buffer->QueryInterface(IID_IDirectSoundNotify8, &m_notify))) { - sound_buffer->Release(); throw std::runtime_error("can't get directsound notify interface"); } - m_notify = decltype(m_notify)(notify8); - - sound_buffer->Release(); { // initialize sound buffer void *ptr1, *ptr2; @@ -155,10 +141,6 @@ DirectSoundAPI::~DirectSoundAPI() if(m_thread.joinable()) m_thread.join(); - m_notify.reset(); - m_sound_buffer.reset(); - m_direct_sound.reset(); - for(auto entry : m_notify_event) { if (entry) @@ -186,7 +168,7 @@ bool DirectSoundAPI::Stop() bool DirectSoundAPI::FeedBlock(sint16* data) { - std::unique_lock lock(m_mutex); + std::lock_guard lock(m_mutex); if (m_buffer.size() > kBlockCount) { cemuLog_logDebug(LogType::Force, "dropped direct sound block since too many buffers are queued"); diff --git a/src/audio/DirectSoundAPI.h b/src/audio/DirectSoundAPI.h index c5ad0d6f..52817fbe 100644 --- a/src/audio/DirectSoundAPI.h +++ b/src/audio/DirectSoundAPI.h @@ -2,8 +2,8 @@ #define DIRECTSOUND_VERSION 0x0800 #include <mmsystem.h> -//#include <mmreg.h> #include <dsound.h> +#include <wrl/client.h> #include "IAudioAPI.h" @@ -41,15 +41,10 @@ public: static std::vector<DeviceDescriptionPtr> GetInputDevices(); private: - struct DirectSoundDeleter - { - void operator()(IUnknown* ptr) const { if (ptr) ptr->Release(); } - }; - - std::unique_ptr<IDirectSound8, DirectSoundDeleter> m_direct_sound; - //std::unique_ptr<IDirectSoundCapture8, DirectSoundDeleter> m_direct_sound_capture; - std::unique_ptr<IDirectSoundBuffer8, DirectSoundDeleter> m_sound_buffer; - std::unique_ptr<IDirectSoundNotify8, DirectSoundDeleter> m_notify; + Microsoft::WRL::ComPtr<IDirectSound8> m_direct_sound; + //Microsoft::WRL::ComPtr<IDirectSoundCapture8> m_direct_sound_capture; + Microsoft::WRL::ComPtr<IDirectSoundBuffer8> m_sound_buffer; + Microsoft::WRL::ComPtr<IDirectSoundNotify8> m_notify; DWORD m_sound_buffer_size = 0; uint32_t m_offset = 0; diff --git a/src/audio/XAudio2API.cpp b/src/audio/XAudio2API.cpp index c92fd451..6b25baed 100644 --- a/src/audio/XAudio2API.cpp +++ b/src/audio/XAudio2API.cpp @@ -2,6 +2,7 @@ //#if (_WIN32_WINNT >= 0x0602 /*_WIN32_WINNT_WIN8*/) +#include <wrl/client.h> #include <xaudio2.h> #ifndef XAUDIO2_DLL @@ -33,17 +34,15 @@ XAudio2API::XAudio2API(std::wstring device_id, uint32 samplerate, uint32 channel throw std::runtime_error("can't find XAudio2Create import"); HRESULT hres; - IXAudio2* xaudio; - if (FAILED((hres = _XAudio2Create(&xaudio, 0, XAUDIO2_DEFAULT_PROCESSOR)))) + if (FAILED((hres = _XAudio2Create(&m_xaudio, 0, XAUDIO2_DEFAULT_PROCESSOR)))) throw std::runtime_error(fmt::format("can't create xaudio device (hres: {:#x})", hres)); - m_xaudio = decltype(m_xaudio)(xaudio); IXAudio2MasteringVoice* mastering_voice; if (FAILED((hres = m_xaudio->CreateMasteringVoice(&mastering_voice, channels, samplerate, 0, m_device_id.empty() ? nullptr : m_device_id.c_str())))) throw std::runtime_error(fmt::format("can't create xaudio mastering voice (hres: {:#x})", hres)); - m_mastering_voice = decltype(m_mastering_voice)(mastering_voice); + m_mastering_voice.reset(mastering_voice); m_wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; m_wfx.Format.nChannels = channels; @@ -88,12 +87,6 @@ XAudio2API::XAudio2API(std::wstring device_id, uint32 samplerate, uint32 channel m_xaudio->StartEngine(); } -void XAudio2API::XAudioDeleter::operator()(IXAudio2* ptr) const -{ - if (ptr) - ptr->Release(); -} - void XAudio2API::VoiceDeleter::operator()(IXAudio2Voice* ptr) const { if (ptr) @@ -106,10 +99,6 @@ XAudio2API::~XAudio2API() m_xaudio->StopEngine(); XAudio2API::Stop(); - - m_source_voice.reset(); - m_mastering_voice.reset(); - m_xaudio.reset(); } void XAudio2API::SetVolume(sint32 volume) @@ -179,10 +168,10 @@ const std::vector<XAudio2API::DeviceDescriptionPtr>& XAudio2API::RefreshDevices( try { - struct IWbemLocator *wbem_locator = nullptr; + Microsoft::WRL::ComPtr<IWbemLocator> wbem_locator; - HRESULT hres = CoCreateInstance(__uuidof(WbemLocator), nullptr, CLSCTX_INPROC_SERVER, __uuidof(IWbemLocator), (LPVOID*)&wbem_locator); - if (FAILED(hres) || !wbem_locator) + HRESULT hres = CoCreateInstance(__uuidof(WbemLocator), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&wbem_locator)); + if (FAILED(hres)) throw std::system_error(hres, std::system_category()); std::shared_ptr<OLECHAR> path(SysAllocString(LR"(\\.\root\cimv2)"), SysFreeString); @@ -191,20 +180,19 @@ const std::vector<XAudio2API::DeviceDescriptionPtr>& XAudio2API::RefreshDevices( std::shared_ptr<OLECHAR> name_row(SysAllocString(L"Name"), SysFreeString); std::shared_ptr<OLECHAR> device_id_row(SysAllocString(L"DeviceID"), SysFreeString); - IWbemServices *wbem_services = nullptr; + Microsoft::WRL::ComPtr<IWbemServices> wbem_services; hres = wbem_locator->ConnectServer(path.get(), nullptr, nullptr, nullptr, 0, nullptr, nullptr, &wbem_services); - wbem_locator->Release(); // Free memory resources. - if (FAILED(hres) || !wbem_services) - throw std::system_error(hres, std::system_category()); - - hres = CoSetProxyBlanket(wbem_services, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_NONE); if (FAILED(hres)) throw std::system_error(hres, std::system_category()); - IEnumWbemClassObject* wbem_enum = nullptr; + hres = CoSetProxyBlanket(wbem_services.Get(), RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_NONE); + if (FAILED(hres)) + throw std::system_error(hres, std::system_category()); + + Microsoft::WRL::ComPtr<IEnumWbemClassObject> wbem_enum; hres = wbem_services->ExecQuery(language.get(), query.get(), WBEM_FLAG_RETURN_WBEM_COMPLETE | WBEM_FLAG_FORWARD_ONLY, nullptr, &wbem_enum); - if (FAILED(hres) || !wbem_enum) + if (FAILED(hres)) throw std::system_error(hres, std::system_category()); ULONG returned; @@ -250,11 +238,6 @@ const std::vector<XAudio2API::DeviceDescriptionPtr>& XAudio2API::RefreshDevices( auto default_device = std::make_shared<XAudio2DeviceDescription>(L"Primary Sound Driver", L""); s_devices.insert(s_devices.begin(), default_device); } - - wbem_enum->Release(); - - // Clean up - wbem_services->Release(); } catch (const std::system_error& ex) { diff --git a/src/audio/XAudio2API.h b/src/audio/XAudio2API.h index b5bb0296..5bb01b10 100644 --- a/src/audio/XAudio2API.h +++ b/src/audio/XAudio2API.h @@ -4,6 +4,7 @@ #include <mmsystem.h> #include <mmreg.h> #include <dsound.h> +#include <wrl/client.h> #include "IAudioAPI.h" @@ -50,11 +51,6 @@ private: static const std::vector<DeviceDescriptionPtr>& RefreshDevices(); - struct XAudioDeleter - { - void operator()(IXAudio2* ptr) const; - }; - struct VoiceDeleter { void operator()(IXAudio2Voice* ptr) const; @@ -63,7 +59,7 @@ private: static HMODULE s_xaudio_dll; static std::vector<DeviceDescriptionPtr> s_devices; - std::unique_ptr<IXAudio2, XAudioDeleter> m_xaudio; + Microsoft::WRL::ComPtr<IXAudio2> m_xaudio; std::wstring m_device_id; std::unique_ptr<IXAudio2MasteringVoice, VoiceDeleter> m_mastering_voice; std::unique_ptr<IXAudio2SourceVoice, VoiceDeleter> m_source_voice; diff --git a/src/gui/components/wxGameList.cpp b/src/gui/components/wxGameList.cpp index d70cb376..587374f6 100644 --- a/src/gui/components/wxGameList.cpp +++ b/src/gui/components/wxGameList.cpp @@ -45,6 +45,7 @@ #include <objidl.h> #include <shlguid.h> #include <shlobj.h> +#include <wrl/client.h> #endif // public events @@ -1630,8 +1631,8 @@ void wxGameList::CreateShortcut(GameInfo2& gameInfo) } } - IShellLinkW* shellLink; - HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLink, reinterpret_cast<LPVOID*>(&shellLink)); + Microsoft::WRL::ComPtr<IShellLinkW> shellLink; + HRESULT hres = CoCreateInstance(__uuidof(ShellLink), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink)); if (SUCCEEDED(hres)) { const auto description = wxString::Format("Play %s on Cemu", titleName); @@ -1647,15 +1648,13 @@ void wxGameList::CreateShortcut(GameInfo2& gameInfo) else shellLink->SetIconLocation(exePath.wstring().c_str(), 0); - IPersistFile* shellLinkFile; + Microsoft::WRL::ComPtr<IPersistFile> shellLinkFile; // save the shortcut - hres = shellLink->QueryInterface(IID_IPersistFile, reinterpret_cast<LPVOID*>(&shellLinkFile)); + hres = shellLink.As(&shellLinkFile); if (SUCCEEDED(hres)) { hres = shellLinkFile->Save(outputPath.wc_str(), TRUE); - shellLinkFile->Release(); } - shellLink->Release(); } if (!SUCCEEDED(hres)) { auto errorMsg = formatWxString(_("Failed to save shortcut to {}"), outputPath); diff --git a/src/input/api/DirectInput/DirectInputController.cpp b/src/input/api/DirectInput/DirectInputController.cpp index dbb7c80c..234696b6 100644 --- a/src/input/api/DirectInput/DirectInputController.cpp +++ b/src/input/api/DirectInput/DirectInputController.cpp @@ -15,9 +15,6 @@ DirectInputController::DirectInputController(const GUID& guid, std::string_view DirectInputController::~DirectInputController() { - if (m_effect) - m_effect->Release(); - if (m_device) { m_device->Unacquire(); @@ -39,8 +36,8 @@ DirectInputController::~DirectInputController() if (kGameCubeController == m_product_guid) should_release_device = false; - if (should_release_device) - m_device->Release(); + if (!should_release_device) + m_device.Detach(); } } @@ -104,7 +101,6 @@ bool DirectInputController::connect() // set data format if (FAILED(m_device->SetDataFormat(m_provider->get_data_format()))) { - SAFE_RELEASE(m_device); return false; } @@ -115,7 +111,6 @@ bool DirectInputController::connect() { if (FAILED(m_device->SetCooperativeLevel(hwndMainWindow, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE))) { - SAFE_RELEASE(m_device); return false; } // rumble can only be used with exclusive access diff --git a/src/input/api/DirectInput/DirectInputController.h b/src/input/api/DirectInput/DirectInputController.h index 2ec371a2..d2c3dba2 100644 --- a/src/input/api/DirectInput/DirectInputController.h +++ b/src/input/api/DirectInput/DirectInputController.h @@ -2,6 +2,7 @@ #include "input/api/DirectInput/DirectInputControllerProvider.h" #include "input/api/Controller.h" +#include <wrl/client.h> class DirectInputController : public Controller<DirectInputControllerProvider> { @@ -41,9 +42,9 @@ private: GUID m_product_guid{}; std::shared_mutex m_mutex; - LPDIRECTINPUTDEVICE8 m_device = nullptr; - LPDIRECTINPUTEFFECT m_effect = nullptr; + Microsoft::WRL::ComPtr<IDirectInputDevice8> m_device; + Microsoft::WRL::ComPtr<IDirectInputEffect> m_effect; std::array<LONG, 6> m_min_axis{}; std::array<LONG, 6> m_max_axis{}; -}; \ No newline at end of file +}; diff --git a/src/input/api/DirectInput/DirectInputControllerProvider.cpp b/src/input/api/DirectInput/DirectInputControllerProvider.cpp index 063cb779..79f31354 100644 --- a/src/input/api/DirectInput/DirectInputControllerProvider.cpp +++ b/src/input/api/DirectInput/DirectInputControllerProvider.cpp @@ -19,7 +19,7 @@ DirectInputControllerProvider::DirectInputControllerProvider() const auto r = DirectInput8Create(GetModuleHandle(nullptr), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&m_dinput8, nullptr); - if (FAILED(r) || !m_dinput8) + if (FAILED(r)) { const auto error = GetLastError(); //FreeLibrary(m_module); @@ -29,9 +29,6 @@ DirectInputControllerProvider::DirectInputControllerProvider() DirectInputControllerProvider::~DirectInputControllerProvider() { - if (m_dinput8) - m_dinput8->Release(); - /*if (m_module) FreeLibrary(m_module); */ diff --git a/src/input/api/DirectInput/DirectInputControllerProvider.h b/src/input/api/DirectInput/DirectInputControllerProvider.h index 5db883c0..de8c3216 100644 --- a/src/input/api/DirectInput/DirectInputControllerProvider.h +++ b/src/input/api/DirectInput/DirectInputControllerProvider.h @@ -4,6 +4,7 @@ #define DIRECTINPUT_VERSION 0x0800 #include <dinput.h> +#include <wrl/client.h> #include "input/api/ControllerProvider.h" @@ -22,7 +23,7 @@ public: std::vector<std::shared_ptr<ControllerBase>> get_controllers() override; - IDirectInput8* get_dinput() const { return m_dinput8; } + IDirectInput8* get_dinput() const { return m_dinput8.Get(); } LPCDIDATAFORMAT get_data_format() const; private: @@ -31,7 +32,7 @@ private: decltype(&DirectInput8Create) m_DirectInput8Create; decltype(&GetdfDIJoystick) m_GetdfDIJoystick = nullptr; - IDirectInput8* m_dinput8 = nullptr; + Microsoft::WRL::ComPtr<IDirectInput8> m_dinput8; }; -#endif \ No newline at end of file +#endif diff --git a/src/util/DXGIWrapper/DXGIWrapper.h b/src/util/DXGIWrapper/DXGIWrapper.h index 54f2454d..363733e7 100644 --- a/src/util/DXGIWrapper/DXGIWrapper.h +++ b/src/util/DXGIWrapper/DXGIWrapper.h @@ -1,7 +1,7 @@ #pragma once #include <dxgi1_4.h> -//#include <atlbase.h> +#include <wrl/client.h> class DXGIWrapper { @@ -23,34 +23,28 @@ public: throw std::runtime_error("can't find CreateDXGIFactory1 in dxgi module"); } - IDXGIFactory1* dxgiFactory = nullptr; + Microsoft::WRL::ComPtr<IDXGIFactory1> dxgiFactory; pCreateDXGIFactory1(IID_PPV_ARGS(&dxgiFactory)); - IDXGIAdapter1* tmpDxgiAdapter = nullptr; + Microsoft::WRL::ComPtr<IDXGIAdapter1> dxgiAdapter; UINT adapterIndex = 0; - while (dxgiFactory->EnumAdapters1(adapterIndex, &tmpDxgiAdapter) != DXGI_ERROR_NOT_FOUND) + while (dxgiFactory->EnumAdapters1(adapterIndex, &dxgiAdapter) != DXGI_ERROR_NOT_FOUND) { DXGI_ADAPTER_DESC1 desc; - tmpDxgiAdapter->GetDesc1(&desc); + dxgiAdapter->GetDesc1(&desc); if (deviceLUID == nullptr || memcmp(&desc.AdapterLuid, deviceLUID, sizeof(LUID)) == 0) { - tmpDxgiAdapter->QueryInterface(IID_PPV_ARGS(&m_dxgiAdapter)); - tmpDxgiAdapter->Release(); + if (FAILED(dxgiAdapter.As(&m_dxgiAdapter))) + { + Cleanup(); + throw std::runtime_error("can't create dxgi adapter"); + } break; } - tmpDxgiAdapter->Release(); ++adapterIndex; } - - dxgiFactory->Release(); - - if (!m_dxgiAdapter) - { - Cleanup(); - throw std::runtime_error("can't create dxgi adapter"); - } } ~DXGIWrapper() @@ -65,15 +59,11 @@ public: private: HMODULE m_moduleHandle = nullptr; - IDXGIAdapter3* m_dxgiAdapter = nullptr; + Microsoft::WRL::ComPtr<IDXGIAdapter3> m_dxgiAdapter; void Cleanup() { - if (m_dxgiAdapter) - { - m_dxgiAdapter->Release(); - m_dxgiAdapter = nullptr; - } + m_dxgiAdapter.Reset(); if (m_moduleHandle) { @@ -81,4 +71,4 @@ private: m_moduleHandle = nullptr; } } -}; \ No newline at end of file +}; From c8ffff8f41212098fb76734d2a065e247ea3e0f9 Mon Sep 17 00:00:00 2001 From: oltolm <oleg.tolmatcev@gmail.com> Date: Wed, 18 Jun 2025 10:34:06 +0200 Subject: [PATCH 285/299] Replace basic_string<> of betype with std::vector (#1601) --- src/Cafe/IOSU/legacy/iosu_fpd.cpp | 8 ++++---- .../OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.cpp | 9 ++++++--- src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.cpp | 6 ++++-- src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.cpp | 7 +++++-- src/Common/CafeString.h | 8 ++++---- src/util/helpers/StringHelpers.h | 4 ++-- 6 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/Cafe/IOSU/legacy/iosu_fpd.cpp b/src/Cafe/IOSU/legacy/iosu_fpd.cpp index 28d248ae..a667e61c 100644 --- a/src/Cafe/IOSU/legacy/iosu_fpd.cpp +++ b/src/Cafe/IOSU/legacy/iosu_fpd.cpp @@ -132,7 +132,7 @@ namespace iosu void convertMultiByteStringToBigEndianWidechar(const char* input, uint16be* output, sint32 maxOutputLength) { - std::basic_string<uint16be> beStr = StringHelpers::FromUtf8(input); + std::vector<uint16be> beStr = StringHelpers::FromUtf8(input); if (beStr.size() >= maxOutputLength - 1) beStr.resize(maxOutputLength-1); for (size_t i = 0; i < beStr.size(); i++) @@ -723,7 +723,7 @@ namespace iosu { if(numVecIn != 0 || numVecOut != 1) return FPResult_InvalidIPCParam; - std::basic_string<uint16be> myComment; + std::vector<uint16be> myComment; if(g_fpd.nexFriendSession) { if(vecOut->size != MY_COMMENT_LENGTH * sizeof(uint16be)) @@ -735,8 +735,8 @@ namespace iosu g_fpd.nexFriendSession->getMyComment(myNexComment); myComment = StringHelpers::FromUtf8(myNexComment.commentString); } - myComment.insert(0, 1, '\0'); - memcpy(vecOut->basePhys.GetPtr(), myComment.c_str(), MY_COMMENT_LENGTH * sizeof(uint16be)); + myComment.insert(myComment.begin(), '\0'); + memcpy(vecOut->basePhys.GetPtr(), myComment.data(), MY_COMMENT_LENGTH * sizeof(uint16be)); return FPResult_Ok; } diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.cpp index db1885af..6e7632e9 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.cpp +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_DownloadCommunityTypes.cpp @@ -145,7 +145,8 @@ namespace nn if (name.size() != 0) { - auto name_utf16 = StringHelpers::FromUtf8(name).substr(0, 128); + auto name_utf16 = StringHelpers::FromUtf8(name); + name_utf16.resize(std::min<size_t>(name_utf16.size(), 128)); if (name_utf16.size() != 0) { for (int i = 0; i < name_utf16.size(); i++) @@ -160,7 +161,8 @@ namespace nn if (description.size() != 0) { - auto description_utf16 = StringHelpers::FromUtf8(description).substr(0, 256); + auto description_utf16 = StringHelpers::FromUtf8(description); + description_utf16.resize(std::min<size_t>(description_utf16.size(), 256)); if (description_utf16.size() != 0) { for (int i = 0; i < description_utf16.size(); i++) @@ -206,7 +208,8 @@ namespace nn if (screen_name.size() != 0) { - auto screen_name_utf16 = StringHelpers::FromUtf8(screen_name).substr(0, 32); + auto screen_name_utf16 = StringHelpers::FromUtf8(screen_name); + screen_name_utf16.resize(std::min<size_t>(screen_name_utf16.size(), 32)); if (screen_name_utf16.size() != 0) { for (int i = 0; i < screen_name_utf16.size(); i++) diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.cpp index 6f3c43b9..21952ceb 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.cpp +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadCommunityTypes.cpp @@ -250,7 +250,8 @@ namespace nn if (name.size() != 0) { - auto name_utf16 = StringHelpers::FromUtf8(name).substr(0, 128); + auto name_utf16 = StringHelpers::FromUtf8(name); + name_utf16.resize(std::min<size_t>(name_utf16.size(), 128)); if (name_utf16.size() != 0) { for (int i = 0; i < name_utf16.size(); i++) @@ -265,7 +266,8 @@ namespace nn if (description.size() != 0) { - auto description_utf16 = StringHelpers::FromUtf8(description).substr(0, 256); + auto description_utf16 = StringHelpers::FromUtf8(description); + description_utf16.resize(std::min<size_t>(description_utf16.size(), 256)); if (description_utf16.size() != 0) { for (int i = 0; i < description_utf16.size(); i++) diff --git a/src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.cpp b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.cpp index 1e2d40ab..912e7a11 100644 --- a/src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.cpp +++ b/src/Cafe/OS/libs/nn_olv/nn_olv_UploadFavoriteTypes.cpp @@ -1,5 +1,6 @@ #include "nn_olv_UploadFavoriteTypes.h" #include <algorithm> +#include <cstddef> namespace nn { @@ -115,7 +116,8 @@ namespace nn if (name.size() != 0) { - auto name_utf16 = StringHelpers::FromUtf8(name).substr(0, 128); + auto name_utf16 = StringHelpers::FromUtf8(name); + name_utf16.resize(std::min<size_t>(name_utf16.size(), 128)); if (name_utf16.size() != 0) { for (int i = 0; i < name_utf16.size(); i++) @@ -130,7 +132,8 @@ namespace nn if (description.size() != 0) { - auto description_utf16 = StringHelpers::FromUtf8(description).substr(0, 256); + auto description_utf16 = StringHelpers::FromUtf8(description); + description_utf16.resize(std::min<size_t>(description_utf16.size(), 256)); if (description_utf16.size() != 0) { for (int i = 0; i < description_utf16.size(); i++) diff --git a/src/Common/CafeString.h b/src/Common/CafeString.h index d902d721..57fc72da 100644 --- a/src/Common/CafeString.h +++ b/src/Common/CafeString.h @@ -51,15 +51,15 @@ class CafeWideString // fixed buffer size, null-terminated, PPC wchar_t (16bit b bool assignFromUTF8(std::string_view sv) { - std::basic_string<uint16be> beStr = StringHelpers::FromUtf8(sv); - if(beStr.length() > N-1) + std::vector<uint16be> beStr = StringHelpers::FromUtf8(sv); + if(beStr.size() > N-1) { memcpy(data, beStr.data(), (N-1)*sizeof(uint16be)); data[N-1] = 0; return false; } - memcpy(data, beStr.data(), beStr.length()*sizeof(uint16be)); - data[beStr.length()] = '\0'; + memcpy(data, beStr.data(), beStr.size()*sizeof(uint16be)); + data[beStr.size()] = '\0'; return true; } diff --git a/src/util/helpers/StringHelpers.h b/src/util/helpers/StringHelpers.h index a98344d6..fb858f4d 100644 --- a/src/util/helpers/StringHelpers.h +++ b/src/util/helpers/StringHelpers.h @@ -111,9 +111,9 @@ namespace StringHelpers } // convert utf8 string to Wii U big-endian wchar_t string - static std::basic_string<uint16be> FromUtf8(std::string_view str) + static std::vector<uint16be> FromUtf8(std::string_view str) { - std::basic_string<uint16be> tmpStr; + std::vector<uint16be> tmpStr; std::wstring w = boost::nowide::widen(str.data(), str.size()); for (auto& c : w) tmpStr.push_back((uint16)c); From 00ff5549d91e6d8ddc43d0565192158a216e4cc2 Mon Sep 17 00:00:00 2001 From: Exverge <exverge@exverge.xyz> Date: Wed, 18 Jun 2025 04:36:05 -0400 Subject: [PATCH 286/299] General aarch64 improvements & Apple Silicon support (#1255) --- .github/workflows/build.yml | 7 +- CMakeLists.txt | 9 +- dependencies/ih264d/CMakeLists.txt | 3 + .../armv8/ih264_intra_pred_chroma_av8.s | 12 +- .../armv8/ih264_intra_pred_luma_16x16_av8.s | 7 +- .../armv8/ih264_intra_pred_luma_8x8_av8.s | 6 +- .../common/armv8/ih264_weighted_bi_pred_av8.s | 31 ++- .../common/armv8/macos_arm_symbol_aliases.s | 185 ++++++++++++++++++ src/CMakeLists.txt | 14 +- src/Cafe/CMakeLists.txt | 2 +- .../BackendAArch64/BackendAArch64.cpp | 4 +- src/Cafe/HW/Latte/Core/LatteIndices.cpp | 126 +++++++++++- src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp | 12 +- src/Common/precompiled.h | 3 +- src/gui/MainWindow.cpp | 3 +- src/util/Fiber/FiberUnix.cpp | 5 + src/util/MemMapper/MemMapperUnix.cpp | 6 +- .../HighResolutionTimer.cpp | 2 + 18 files changed, 405 insertions(+), 32 deletions(-) create mode 100644 dependencies/ih264d/common/armv8/macos_arm_symbol_aliases.s diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c7cbc202..e798c1a7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -177,6 +177,9 @@ jobs: build-macos: runs-on: macos-14 + strategy: + matrix: + arch: [x86_64, arm64] steps: - name: "Checkout repo" uses: actions/checkout@v4 @@ -236,7 +239,7 @@ jobs: cd build cmake .. ${{ env.BUILD_FLAGS }} \ -DCMAKE_BUILD_TYPE=${{ env.BUILD_MODE }} \ - -DCMAKE_OSX_ARCHITECTURES=x86_64 \ + -DCMAKE_OSX_ARCHITECTURES=${{ matrix.arch }} \ -DMACOS_BUNDLE=ON \ -G Ninja @@ -259,5 +262,5 @@ jobs: - name: Upload artifact uses: actions/upload-artifact@v4 with: - name: cemu-bin-macos-x64 + name: cemu-bin-macos-${{ matrix.arch }} path: ./bin/Cemu.dmg diff --git a/CMakeLists.txt b/CMakeLists.txt index eb848ce7..c70b0a40 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -222,7 +222,12 @@ endif() add_subdirectory("dependencies/ih264d" EXCLUDE_FROM_ALL) -if(CMAKE_SYSTEM_PROCESSOR MATCHES "(aarch64)|(AARCH64)") +if (CMAKE_OSX_ARCHITECTURES) + set(CEMU_ARCHITECTURE ${CMAKE_OSX_ARCHITECTURES}) +else() + set(CEMU_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR}) +endif() +if(CEMU_ARCHITECTURE MATCHES "(aarch64)|(AARCH64)|(arm64)|(ARM64)") add_subdirectory("dependencies/xbyak_aarch64" EXCLUDE_FROM_ALL) endif() @@ -231,4 +236,4 @@ if (NOT ZArchive_FOUND) add_subdirectory("dependencies/ZArchive" EXCLUDE_FROM_ALL) endif() -add_subdirectory(src) +add_subdirectory(src) \ No newline at end of file diff --git a/dependencies/ih264d/CMakeLists.txt b/dependencies/ih264d/CMakeLists.txt index 686a9d08..64ac0931 100644 --- a/dependencies/ih264d/CMakeLists.txt +++ b/dependencies/ih264d/CMakeLists.txt @@ -183,6 +183,9 @@ target_sources(ih264d PRIVATE "decoder/arm/ih264d_function_selector.c" ) target_compile_options(ih264d PRIVATE -DARMV8) +if(APPLE) + target_sources(ih264d PRIVATE "common/armv8/macos_arm_symbol_aliases.s") +endif() else() message(FATAL_ERROR "ih264d unknown architecture: ${IH264D_ARCHITECTURE}") endif() diff --git a/dependencies/ih264d/common/armv8/ih264_intra_pred_chroma_av8.s b/dependencies/ih264d/common/armv8/ih264_intra_pred_chroma_av8.s index 39c02560..c0d9cf99 100644 --- a/dependencies/ih264d/common/armv8/ih264_intra_pred_chroma_av8.s +++ b/dependencies/ih264d/common/armv8/ih264_intra_pred_chroma_av8.s @@ -429,8 +429,13 @@ ih264_intra_pred_chroma_8x8_mode_plane_av8: rev64 v7.4h, v2.4h ld1 {v3.2s}, [x10] sub x5, x3, #8 +#ifdef __APPLE__ + adrp x12, _ih264_gai1_intrapred_chroma_plane_coeffs1@GOTPAGE + ldr x12, [x12, _ih264_gai1_intrapred_chroma_plane_coeffs1@GOTPAGEOFF] +#else adrp x12, :got:ih264_gai1_intrapred_chroma_plane_coeffs1 ldr x12, [x12, #:got_lo12:ih264_gai1_intrapred_chroma_plane_coeffs1] +#endif usubl v10.8h, v5.8b, v1.8b ld1 {v8.8b, v9.8b}, [x12] // Load multiplication factors 1 to 8 into D3 mov v8.d[1], v9.d[0] @@ -484,10 +489,13 @@ ih264_intra_pred_chroma_8x8_mode_plane_av8: zip1 v1.8h, v0.8h, v2.8h zip2 v2.8h, v0.8h, v2.8h mov v0.16b, v1.16b - +#ifdef __APPLE__ + adrp x12, _ih264_gai1_intrapred_chroma_plane_coeffs2@GOTPAGE + ldr x12, [x12, _ih264_gai1_intrapred_chroma_plane_coeffs2@GOTPAGEOFF] +#else adrp x12, :got:ih264_gai1_intrapred_chroma_plane_coeffs2 ldr x12, [x12, #:got_lo12:ih264_gai1_intrapred_chroma_plane_coeffs2] - +#endif ld1 {v8.2s, v9.2s}, [x12] mov v8.d[1], v9.d[0] mov v10.16b, v8.16b diff --git a/dependencies/ih264d/common/armv8/ih264_intra_pred_luma_16x16_av8.s b/dependencies/ih264d/common/armv8/ih264_intra_pred_luma_16x16_av8.s index fa19c121..2422d8cd 100644 --- a/dependencies/ih264d/common/armv8/ih264_intra_pred_luma_16x16_av8.s +++ b/dependencies/ih264d/common/armv8/ih264_intra_pred_luma_16x16_av8.s @@ -431,10 +431,13 @@ ih264_intra_pred_luma_16x16_mode_plane_av8: mov x10, x1 //top_left mov x4, #-1 ld1 {v2.2s}, [x1], x8 - +#ifdef __APPLE__ + adrp x7, _ih264_gai1_intrapred_luma_plane_coeffs@GOTPAGE + ldr x7, [x7, _ih264_gai1_intrapred_luma_plane_coeffs@GOTPAGEOFF] +#else adrp x7, :got:ih264_gai1_intrapred_luma_plane_coeffs ldr x7, [x7, #:got_lo12:ih264_gai1_intrapred_luma_plane_coeffs] - +#endif ld1 {v0.2s}, [x1] rev64 v2.8b, v2.8b ld1 {v6.2s, v7.2s}, [x7] diff --git a/dependencies/ih264d/common/armv8/ih264_intra_pred_luma_8x8_av8.s b/dependencies/ih264d/common/armv8/ih264_intra_pred_luma_8x8_av8.s index 273aa81b..6fa31ded 100644 --- a/dependencies/ih264d/common/armv8/ih264_intra_pred_luma_8x8_av8.s +++ b/dependencies/ih264d/common/armv8/ih264_intra_pred_luma_8x8_av8.s @@ -1029,9 +1029,13 @@ ih264_intra_pred_luma_8x8_mode_horz_u_av8: mov v3.d[0], v2.d[1] ext v4.16b, v2.16b , v2.16b , #1 mov v5.d[0], v4.d[1] - +#ifdef __APPLE__ + adrp x12, _ih264_gai1_intrapred_luma_8x8_horz_u@GOTPAGE + ldr x12, [x12, _ih264_gai1_intrapred_luma_8x8_horz_u@GOTPAGEOFF] +#else adrp x12, :got:ih264_gai1_intrapred_luma_8x8_horz_u ldr x12, [x12, #:got_lo12:ih264_gai1_intrapred_luma_8x8_horz_u] +#endif uaddl v20.8h, v0.8b, v2.8b uaddl v22.8h, v1.8b, v3.8b uaddl v24.8h, v2.8b, v4.8b diff --git a/dependencies/ih264d/common/armv8/ih264_weighted_bi_pred_av8.s b/dependencies/ih264d/common/armv8/ih264_weighted_bi_pred_av8.s index 475f690e..8d6aa995 100644 --- a/dependencies/ih264d/common/armv8/ih264_weighted_bi_pred_av8.s +++ b/dependencies/ih264d/common/armv8/ih264_weighted_bi_pred_av8.s @@ -142,14 +142,22 @@ ih264_weighted_bi_pred_luma_av8: sxtw x4, w4 sxtw x5, w5 stp x19, x20, [sp, #-16]! +#ifndef __APPLE__ ldr w8, [sp, #80] //Load wt2 in w8 ldr w9, [sp, #88] //Load ofst1 in w9 - add w6, w6, #1 //w6 = log_WD + 1 - neg w10, w6 //w10 = -(log_WD + 1) - dup v0.8h, w10 //Q0 = -(log_WD + 1) (32-bit) ldr w10, [sp, #96] //Load ofst2 in w10 ldr w11, [sp, #104] //Load ht in w11 ldr w12, [sp, #112] //Load wd in w12 +#else + ldr w8, [sp, #80] //Load wt2 in w8 + ldr w9, [sp, #84] //Load ofst1 in w9 + ldr w10, [sp, #88] //Load ofst2 in w10 + ldr w11, [sp, #92] //Load ht in w11 + ldr w12, [sp, #96] //Load wd in w12 +#endif + add w6, w6, #1 //w6 = log_WD + 1 + neg w10, w6 //w10 = -(log_WD + 1) + dup v0.8h, w10 //Q0 = -(log_WD + 1) (32-bit) add w9, w9, #1 //w9 = ofst1 + 1 add w9, w9, w10 //w9 = ofst1 + ofst2 + 1 mov v2.s[0], w7 @@ -424,17 +432,24 @@ ih264_weighted_bi_pred_chroma_av8: sxtw x5, w5 stp x19, x20, [sp, #-16]! - +#ifndef __APPLE__ ldr w8, [sp, #80] //Load wt2 in w8 + ldr w9, [sp, #88] //Load ofst1 in w9 + ldr w10, [sp, #96] //Load ofst2 in w10 + ldr w11, [sp, #104] //Load ht in w11 + ldr w12, [sp, #112] //Load wd in w12 +#else + ldr w8, [sp, #80] //Load wt2 in w8 + ldr w9, [sp, #84] //Load ofst1 in w9 + ldr w10, [sp, #88] //Load ofst2 in w10 + ldr w11, [sp, #92] //Load ht in w11 + ldr w12, [sp, #96] //Load wd in w12 +#endif dup v4.4s, w8 //Q2 = (wt2_u, wt2_v) (32-bit) dup v2.4s, w7 //Q1 = (wt1_u, wt1_v) (32-bit) add w6, w6, #1 //w6 = log_WD + 1 - ldr w9, [sp, #88] //Load ofst1 in w9 - ldr w10, [sp, #96] //Load ofst2 in w10 neg w20, w6 //w20 = -(log_WD + 1) dup v0.8h, w20 //Q0 = -(log_WD + 1) (16-bit) - ldr w11, [sp, #104] //Load ht in x11 - ldr w12, [sp, #112] //Load wd in x12 dup v20.8h, w9 //0ffset1 dup v21.8h, w10 //0ffset2 srhadd v6.8b, v20.8b, v21.8b diff --git a/dependencies/ih264d/common/armv8/macos_arm_symbol_aliases.s b/dependencies/ih264d/common/armv8/macos_arm_symbol_aliases.s new file mode 100644 index 00000000..3639f1b3 --- /dev/null +++ b/dependencies/ih264d/common/armv8/macos_arm_symbol_aliases.s @@ -0,0 +1,185 @@ +// macOS clang compilers append preceding underscores to function names, this is to prevent +// mismatches with the assembly function names and the C functions as defined in the header. + +.global _ih264_deblk_chroma_horz_bs4_av8 +_ih264_deblk_chroma_horz_bs4_av8 = ih264_deblk_chroma_horz_bs4_av8 + +.global _ih264_deblk_chroma_horz_bslt4_av8 +_ih264_deblk_chroma_horz_bslt4_av8 = ih264_deblk_chroma_horz_bslt4_av8 + +.global _ih264_deblk_chroma_vert_bs4_av8 +_ih264_deblk_chroma_vert_bs4_av8 = ih264_deblk_chroma_vert_bs4_av8 + +.global _ih264_deblk_chroma_vert_bslt4_av8 +_ih264_deblk_chroma_vert_bslt4_av8 = ih264_deblk_chroma_vert_bslt4_av8 + +.global _ih264_deblk_luma_horz_bs4_av8 +_ih264_deblk_luma_horz_bs4_av8 = ih264_deblk_luma_horz_bs4_av8 + +.global _ih264_deblk_luma_horz_bslt4_av8 +_ih264_deblk_luma_horz_bslt4_av8 = ih264_deblk_luma_horz_bslt4_av8 + +.global _ih264_deblk_luma_vert_bs4_av8 +_ih264_deblk_luma_vert_bs4_av8 = ih264_deblk_luma_vert_bs4_av8 + +.global _ih264_deblk_luma_vert_bslt4_av8 +_ih264_deblk_luma_vert_bslt4_av8 = ih264_deblk_luma_vert_bslt4_av8 + +.global _ih264_default_weighted_pred_chroma_av8 +_ih264_default_weighted_pred_chroma_av8 = ih264_default_weighted_pred_chroma_av8 + +.global _ih264_default_weighted_pred_luma_av8 +_ih264_default_weighted_pred_luma_av8 = ih264_default_weighted_pred_luma_av8 + +.global _ih264_ihadamard_scaling_4x4_av8 +_ih264_ihadamard_scaling_4x4_av8 = ih264_ihadamard_scaling_4x4_av8 + +.global _ih264_inter_pred_chroma_av8 +_ih264_inter_pred_chroma_av8 = ih264_inter_pred_chroma_av8 + +.global _ih264_inter_pred_luma_copy_av8 +_ih264_inter_pred_luma_copy_av8 = ih264_inter_pred_luma_copy_av8 + +.global _ih264_inter_pred_luma_horz_av8 +_ih264_inter_pred_luma_horz_av8 = ih264_inter_pred_luma_horz_av8 + +.global _ih264_inter_pred_luma_horz_hpel_vert_hpel_av8 +_ih264_inter_pred_luma_horz_hpel_vert_hpel_av8 = ih264_inter_pred_luma_horz_hpel_vert_hpel_av8 + +.global _ih264_inter_pred_luma_horz_hpel_vert_qpel_av8 +_ih264_inter_pred_luma_horz_hpel_vert_qpel_av8 = ih264_inter_pred_luma_horz_hpel_vert_qpel_av8 + +.global _ih264_inter_pred_luma_horz_qpel_av8 +_ih264_inter_pred_luma_horz_qpel_av8 = ih264_inter_pred_luma_horz_qpel_av8 + +.global _ih264_inter_pred_luma_horz_qpel_vert_hpel_av8 +_ih264_inter_pred_luma_horz_qpel_vert_hpel_av8 = ih264_inter_pred_luma_horz_qpel_vert_hpel_av8 + +.global _ih264_inter_pred_luma_horz_qpel_vert_qpel_av8 +_ih264_inter_pred_luma_horz_qpel_vert_qpel_av8 = ih264_inter_pred_luma_horz_qpel_vert_qpel_av8 + +.global _ih264_inter_pred_luma_vert_av8 +_ih264_inter_pred_luma_vert_av8 = ih264_inter_pred_luma_vert_av8 + +.global _ih264_inter_pred_luma_vert_qpel_av8 +_ih264_inter_pred_luma_vert_qpel_av8 = ih264_inter_pred_luma_vert_qpel_av8 + +.global _ih264_intra_pred_chroma_8x8_mode_horz_av8 +_ih264_intra_pred_chroma_8x8_mode_horz_av8 = ih264_intra_pred_chroma_8x8_mode_horz_av8 + +.global _ih264_intra_pred_chroma_8x8_mode_plane_av8 +_ih264_intra_pred_chroma_8x8_mode_plane_av8 = ih264_intra_pred_chroma_8x8_mode_plane_av8 + +.global _ih264_intra_pred_chroma_8x8_mode_vert_av8 +_ih264_intra_pred_chroma_8x8_mode_vert_av8 = ih264_intra_pred_chroma_8x8_mode_vert_av8 + +.global _ih264_intra_pred_luma_16x16_mode_dc_av8 +_ih264_intra_pred_luma_16x16_mode_dc_av8 = ih264_intra_pred_luma_16x16_mode_dc_av8 + +.global _ih264_intra_pred_luma_16x16_mode_horz_av8 +_ih264_intra_pred_luma_16x16_mode_horz_av8 = ih264_intra_pred_luma_16x16_mode_horz_av8 + +.global _ih264_intra_pred_luma_16x16_mode_plane_av8 +_ih264_intra_pred_luma_16x16_mode_plane_av8 = ih264_intra_pred_luma_16x16_mode_plane_av8 + +.global _ih264_intra_pred_luma_16x16_mode_vert_av8 +_ih264_intra_pred_luma_16x16_mode_vert_av8 = ih264_intra_pred_luma_16x16_mode_vert_av8 + +.global _ih264_intra_pred_luma_4x4_mode_dc_av8 +_ih264_intra_pred_luma_4x4_mode_dc_av8 = ih264_intra_pred_luma_4x4_mode_dc_av8 + +.global _ih264_intra_pred_luma_4x4_mode_diag_dl_av8 +_ih264_intra_pred_luma_4x4_mode_diag_dl_av8 = ih264_intra_pred_luma_4x4_mode_diag_dl_av8 + +.global _ih264_intra_pred_luma_4x4_mode_diag_dr_av8 +_ih264_intra_pred_luma_4x4_mode_diag_dr_av8 = ih264_intra_pred_luma_4x4_mode_diag_dr_av8 + +.global _ih264_intra_pred_luma_4x4_mode_horz_av8 +_ih264_intra_pred_luma_4x4_mode_horz_av8 = ih264_intra_pred_luma_4x4_mode_horz_av8 + +.global _ih264_intra_pred_luma_4x4_mode_horz_d_av8 +_ih264_intra_pred_luma_4x4_mode_horz_d_av8 = ih264_intra_pred_luma_4x4_mode_horz_d_av8 + +.global _ih264_intra_pred_luma_4x4_mode_horz_u_av8 +_ih264_intra_pred_luma_4x4_mode_horz_u_av8 = ih264_intra_pred_luma_4x4_mode_horz_u_av8 + +.global _ih264_intra_pred_luma_4x4_mode_vert_av8 +_ih264_intra_pred_luma_4x4_mode_vert_av8 = ih264_intra_pred_luma_4x4_mode_vert_av8 + +.global _ih264_intra_pred_luma_4x4_mode_vert_l_av8 +_ih264_intra_pred_luma_4x4_mode_vert_l_av8 = ih264_intra_pred_luma_4x4_mode_vert_l_av8 + +.global _ih264_intra_pred_luma_4x4_mode_vert_r_av8 +_ih264_intra_pred_luma_4x4_mode_vert_r_av8 = ih264_intra_pred_luma_4x4_mode_vert_r_av8 + +.global _ih264_intra_pred_luma_8x8_mode_dc_av8 +_ih264_intra_pred_luma_8x8_mode_dc_av8 = ih264_intra_pred_luma_8x8_mode_dc_av8 + +.global _ih264_intra_pred_luma_8x8_mode_diag_dl_av8 +_ih264_intra_pred_luma_8x8_mode_diag_dl_av8 = ih264_intra_pred_luma_8x8_mode_diag_dl_av8 + +.global _ih264_intra_pred_luma_8x8_mode_diag_dr_av8 +_ih264_intra_pred_luma_8x8_mode_diag_dr_av8 = ih264_intra_pred_luma_8x8_mode_diag_dr_av8 + +.global _ih264_intra_pred_luma_8x8_mode_horz_av8 +_ih264_intra_pred_luma_8x8_mode_horz_av8 = ih264_intra_pred_luma_8x8_mode_horz_av8 + +.global _ih264_intra_pred_luma_8x8_mode_horz_d_av8 +_ih264_intra_pred_luma_8x8_mode_horz_d_av8 = ih264_intra_pred_luma_8x8_mode_horz_d_av8 + +.global _ih264_intra_pred_luma_8x8_mode_horz_u_av8 +_ih264_intra_pred_luma_8x8_mode_horz_u_av8 = ih264_intra_pred_luma_8x8_mode_horz_u_av8 + +.global _ih264_intra_pred_luma_8x8_mode_vert_av8 +_ih264_intra_pred_luma_8x8_mode_vert_av8 = ih264_intra_pred_luma_8x8_mode_vert_av8 + +.global _ih264_intra_pred_luma_8x8_mode_vert_l_av8 +_ih264_intra_pred_luma_8x8_mode_vert_l_av8 = ih264_intra_pred_luma_8x8_mode_vert_l_av8 + +.global _ih264_intra_pred_luma_8x8_mode_vert_r_av8 +_ih264_intra_pred_luma_8x8_mode_vert_r_av8 = ih264_intra_pred_luma_8x8_mode_vert_r_av8 + +.global _ih264_iquant_itrans_recon_4x4_av8 +_ih264_iquant_itrans_recon_4x4_av8 = ih264_iquant_itrans_recon_4x4_av8 + +.global _ih264_iquant_itrans_recon_4x4_dc_av8 +_ih264_iquant_itrans_recon_4x4_dc_av8 = ih264_iquant_itrans_recon_4x4_dc_av8 + +.global _ih264_iquant_itrans_recon_8x8_av8 +_ih264_iquant_itrans_recon_8x8_av8 = ih264_iquant_itrans_recon_8x8_av8 + +.global _ih264_iquant_itrans_recon_8x8_dc_av8 +_ih264_iquant_itrans_recon_8x8_dc_av8 = ih264_iquant_itrans_recon_8x8_dc_av8 + +.global _ih264_iquant_itrans_recon_chroma_4x4_av8 +_ih264_iquant_itrans_recon_chroma_4x4_av8 = ih264_iquant_itrans_recon_chroma_4x4_av8 + +.global _ih264_iquant_itrans_recon_chroma_4x4_dc_av8 +_ih264_iquant_itrans_recon_chroma_4x4_dc_av8 = ih264_iquant_itrans_recon_chroma_4x4_dc_av8 + +.global _ih264_pad_left_chroma_av8 +_ih264_pad_left_chroma_av8 = ih264_pad_left_chroma_av8 + +.global _ih264_pad_left_luma_av8 +_ih264_pad_left_luma_av8 = ih264_pad_left_luma_av8 + +.global _ih264_pad_right_chroma_av8 +_ih264_pad_right_chroma_av8 = ih264_pad_right_chroma_av8 + +.global _ih264_pad_right_luma_av8 +_ih264_pad_right_luma_av8 = ih264_pad_right_luma_av8 + +.global _ih264_pad_top_av8 +_ih264_pad_top_av8 = ih264_pad_top_av8 + +.global _ih264_weighted_bi_pred_chroma_av8 +_ih264_weighted_bi_pred_chroma_av8 = ih264_weighted_bi_pred_chroma_av8 + +.global _ih264_weighted_bi_pred_luma_av8 +_ih264_weighted_bi_pred_luma_av8 = ih264_weighted_bi_pred_luma_av8 + +.global _ih264_weighted_pred_chroma_av8 +_ih264_weighted_pred_chroma_av8 = ih264_weighted_pred_chroma_av8 + +.global _ih264_weighted_pred_luma_av8 +_ih264_weighted_pred_luma_av8 = ih264_weighted_pred_luma_av8 \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ee7f8610..04b6dfdd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -101,13 +101,21 @@ if (MACOS_BUNDLE) endforeach(folder) if(CMAKE_BUILD_TYPE STREQUAL "Debug") - set(LIBUSB_PATH "${CMAKE_BINARY_DIR}/vcpkg_installed/x64-osx/debug/lib/libusb-1.0.0.dylib") + set(LIBUSB_PATH "${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/debug/lib/libusb-1.0.0.dylib") else() - set(LIBUSB_PATH "${CMAKE_BINARY_DIR}/vcpkg_installed/x64-osx/lib/libusb-1.0.0.dylib") + set(LIBUSB_PATH "${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/lib/libusb-1.0.0.dylib") endif() + if (EXISTS "/usr/local/lib/libMoltenVK.dylib") + set(MOLTENVK_PATH "/usr/local/lib/libMoltenVK.dylib") + elseif (EXISTS "/opt/homebrew/lib/libMoltenVK.dylib") + set(MOLTENVK_PATH "/opt/homebrew/lib/libMoltenVK.dylib") + else() + message(FATAL_ERROR "failed to find libMoltenVK.dylib") + endif () + add_custom_command (TARGET CemuBin POST_BUILD - COMMAND ${CMAKE_COMMAND} ARGS -E copy "/usr/local/lib/libMoltenVK.dylib" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libMoltenVK.dylib" + COMMAND ${CMAKE_COMMAND} ARGS -E copy "${MOLTENVK_PATH}" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libMoltenVK.dylib" COMMAND ${CMAKE_COMMAND} ARGS -E copy "${LIBUSB_PATH}" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/Frameworks/libusb-1.0.0.dylib" COMMAND ${CMAKE_COMMAND} ARGS -E copy "${CMAKE_SOURCE_DIR}/src/resource/update.sh" "${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/update.sh" COMMAND bash -c "install_name_tool -add_rpath @executable_path/../Frameworks ${CMAKE_SOURCE_DIR}/bin/${OUTPUT_NAME}.app/Contents/MacOS/${OUTPUT_NAME}" diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index 71866b21..64baa337 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -537,7 +537,7 @@ if(APPLE) target_sources(CemuCafe PRIVATE "HW/Latte/Renderer/Vulkan/CocoaSurface.mm") endif() -if(CMAKE_SYSTEM_PROCESSOR MATCHES "(aarch64)|(AARCH64)") +if(CEMU_ARCHITECTURE MATCHES "(aarch64)|(AARCH64)|(arm64)|(ARM64)") target_sources(CemuCafe PRIVATE HW/Espresso/Recompiler/BackendAArch64/BackendAArch64.cpp HW/Espresso/Recompiler/BackendAArch64/BackendAArch64.h diff --git a/src/Cafe/HW/Espresso/Recompiler/BackendAArch64/BackendAArch64.cpp b/src/Cafe/HW/Espresso/Recompiler/BackendAArch64/BackendAArch64.cpp index cb71234d..728460a4 100644 --- a/src/Cafe/HW/Espresso/Recompiler/BackendAArch64/BackendAArch64.cpp +++ b/src/Cafe/HW/Espresso/Recompiler/BackendAArch64/BackendAArch64.cpp @@ -169,8 +169,10 @@ struct AArch64GenContext_t : CodeGenerator bool processAllJumps() { - for (auto&& [jumpStart, jumpInfo] : jumps) + for (auto jump : jumps) { + auto jumpStart = jump.first; + auto jumpInfo = jump.second; bool success = std::visit( [&, this](const auto& jump) { setSize(jumpStart); diff --git a/src/Cafe/HW/Latte/Core/LatteIndices.cpp b/src/Cafe/HW/Latte/Core/LatteIndices.cpp index aec51725..2bbb617d 100644 --- a/src/Cafe/HW/Latte/Core/LatteIndices.cpp +++ b/src/Cafe/HW/Latte/Core/LatteIndices.cpp @@ -6,6 +6,8 @@ #if defined(ARCH_X86_64) && defined(__GNUC__) #include <immintrin.h> +#elif defined(__aarch64__) +#include <arm_neon.h> #endif struct @@ -502,6 +504,114 @@ void LatteIndices_fastConvertU32_AVX2(const void* indexDataInput, void* indexDat indexMax = std::max(indexMax, _maxIndex); indexMin = std::min(indexMin, _minIndex); } +#elif defined(__aarch64__) + +void LatteIndices_fastConvertU16_NEON(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) +{ + const uint16* indicesU16BE = (const uint16*)indexDataInput; + uint16* indexOutput = (uint16*)indexDataOutput; + sint32 count8 = count >> 3; + sint32 countRemaining = count & 7; + + if (count8) + { + uint16x8_t mMin = vdupq_n_u16(0xFFFF); + uint16x8_t mMax = vdupq_n_u16(0x0000); + uint16x8_t mTemp; + uint16x8_t* mRawIndices = (uint16x8_t*) indicesU16BE; + indicesU16BE += count8 * 8; + uint16x8_t* mOutputIndices = (uint16x8_t*) indexOutput; + indexOutput += count8 * 8; + + while (count8--) + { + mTemp = vld1q_u16((uint16*)mRawIndices); + mRawIndices++; + mTemp = vrev16q_u8(mTemp); + mMin = vminq_u16(mMin, mTemp); + mMax = vmaxq_u16(mMax, mTemp); + vst1q_u16((uint16*)mOutputIndices, mTemp); + mOutputIndices++; + } + + uint16* mMaxU16 = (uint16*)&mMax; + uint16* mMinU16 = (uint16*)&mMin; + + for (int i = 0; i < 8; ++i) { + indexMax = std::max(indexMax, (uint32)mMaxU16[i]); + indexMin = std::min(indexMin, (uint32)mMinU16[i]); + } + } + // process remaining indices + uint32 _minIndex = 0xFFFFFFFF; + uint32 _maxIndex = 0; + for (sint32 i = countRemaining; (--i) >= 0;) + { + uint16 idx = _swapEndianU16(*indicesU16BE); + *indexOutput = idx; + indexOutput++; + indicesU16BE++; + _maxIndex = std::max(_maxIndex, (uint32)idx); + _minIndex = std::min(_minIndex, (uint32)idx); + } + // update min/max + indexMax = std::max(indexMax, _maxIndex); + indexMin = std::min(indexMin, _minIndex); +} + +void LatteIndices_fastConvertU32_NEON(const void* indexDataInput, void* indexDataOutput, uint32 count, uint32& indexMin, uint32& indexMax) +{ + const uint32* indicesU32BE = (const uint32*)indexDataInput; + uint32* indexOutput = (uint32*)indexDataOutput; + sint32 count8 = count >> 2; + sint32 countRemaining = count & 3; + + if (count8) + { + uint32x4_t mMin = vdupq_n_u32(0xFFFFFFFF); + uint32x4_t mMax = vdupq_n_u32(0x00000000); + uint32x4_t mTemp; + uint32x4_t* mRawIndices = (uint32x4_t*) indicesU32BE; + indicesU32BE += count8 * 4; + uint32x4_t* mOutputIndices = (uint32x4_t*) indexOutput; + indexOutput += count8 * 4; + + while (count8--) + { + mTemp = vld1q_u32((uint32*)mRawIndices); + mRawIndices++; + mTemp = vrev32q_u8(mTemp); + mMin = vminq_u32(mMin, mTemp); + mMax = vmaxq_u32(mMax, mTemp); + vst1q_u32((uint32*)mOutputIndices, mTemp); + mOutputIndices++; + } + + uint32* mMaxU32 = (uint32*)&mMax; + uint32* mMinU32 = (uint32*)&mMin; + + for (int i = 0; i < 4; ++i) { + indexMax = std::max(indexMax, mMaxU32[i]); + indexMin = std::min(indexMin, mMinU32[i]); + } + } + // process remaining indices + uint32 _minIndex = 0xFFFFFFFF; + uint32 _maxIndex = 0; + for (sint32 i = countRemaining; (--i) >= 0;) + { + uint32 idx = _swapEndianU32(*indicesU32BE); + *indexOutput = idx; + indexOutput++; + indicesU32BE++; + _maxIndex = std::max(_maxIndex, idx); + _minIndex = std::min(_minIndex, idx); + } + // update min/max + indexMax = std::max(indexMax, _maxIndex); + indexMin = std::min(indexMin, _minIndex); +} + #endif template<typename T> @@ -688,27 +798,31 @@ void LatteIndices_decode(const void* indexData, LatteIndexType indexType, uint32 { if (indexType == LatteIndexType::U16_BE) { - #if defined(ARCH_X86_64) +#if defined(ARCH_X86_64) if (g_CPUFeatures.x86.avx2) LatteIndices_fastConvertU16_AVX2(indexData, indexOutputPtr, count, indexMin, indexMax); else if (g_CPUFeatures.x86.sse4_1 && g_CPUFeatures.x86.ssse3) LatteIndices_fastConvertU16_SSE41(indexData, indexOutputPtr, count, indexMin, indexMax); else LatteIndices_convertBE<uint16>(indexData, indexOutputPtr, count, indexMin, indexMax); - #else +#elif defined(__aarch64__) + LatteIndices_fastConvertU16_NEON(indexData, indexOutputPtr, count, indexMin, indexMax); +#else LatteIndices_convertBE<uint16>(indexData, indexOutputPtr, count, indexMin, indexMax); - #endif +#endif } else if (indexType == LatteIndexType::U32_BE) { - #if defined(ARCH_X86_64) +#if defined(ARCH_X86_64) if (g_CPUFeatures.x86.avx2) LatteIndices_fastConvertU32_AVX2(indexData, indexOutputPtr, count, indexMin, indexMax); else LatteIndices_convertBE<uint32>(indexData, indexOutputPtr, count, indexMin, indexMax); - #else +#elif defined(__aarch64__) + LatteIndices_fastConvertU32_NEON(indexData, indexOutputPtr, count, indexMin, indexMax); +#else LatteIndices_convertBE<uint32>(indexData, indexOutputPtr, count, indexMin, indexMax); - #endif +#endif } else if (indexType == LatteIndexType::U16_LE) { diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp index 870d1850..2eef929d 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp @@ -25,7 +25,11 @@ void nnNfp_update(); namespace coreinit { +#ifdef __arm64__ + void __OSFiberThreadEntry(uint32, uint32); +#else void __OSFiberThreadEntry(void* thread); +#endif void __OSAddReadyThreadToRunQueue(OSThread_t* thread); void __OSRemoveThreadFromRunQueues(OSThread_t* thread); }; @@ -49,7 +53,7 @@ namespace coreinit struct OSHostThread { - OSHostThread(OSThread_t* thread) : m_thread(thread), m_fiber(__OSFiberThreadEntry, this, this) + OSHostThread(OSThread_t* thread) : m_thread(thread), m_fiber((void(*)(void*))__OSFiberThreadEntry, this, this) { } @@ -1304,8 +1308,14 @@ namespace coreinit __OSThreadStartTimeslice(hostThread->m_thread, &hostThread->ppcInstance); } +#ifdef __arm64__ + void __OSFiberThreadEntry(uint32 _high, uint32 _low) + { + uint64 _thread = (uint64) _high << 32 | _low; +#else void __OSFiberThreadEntry(void* _thread) { +#endif OSHostThread* hostThread = (OSHostThread*)_thread; #if defined(ARCH_X86_64) diff --git a/src/Common/precompiled.h b/src/Common/precompiled.h index 1185a34b..26fdfd28 100644 --- a/src/Common/precompiled.h +++ b/src/Common/precompiled.h @@ -310,7 +310,8 @@ inline uint64 __rdtsc() inline void _mm_mfence() { - + asm volatile("" ::: "memory"); + std::atomic_thread_fence(std::memory_order_seq_cst); } inline unsigned char _addcarry_u64(unsigned char carry, unsigned long long a, unsigned long long b, unsigned long long *result) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 882c6eab..c3d800e0 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -140,6 +140,7 @@ enum MAINFRAME_MENU_ID_DEBUG_VK_ACCURATE_BARRIERS, // debug->logging + MAINFRAME_MENU_ID_DEBUG_LOGGING_MESSAGE = 21499, MAINFRAME_MENU_ID_DEBUG_LOGGING0 = 21500, MAINFRAME_MENU_ID_DEBUG_ADVANCED_PPC_INFO = 21599, // debug->dump @@ -2234,7 +2235,7 @@ void MainWindow::RecreateMenu() debugLoggingMenu->AppendSeparator(); wxMenu* logCosModulesMenu = new wxMenu(); - logCosModulesMenu->AppendCheckItem(0, _("&Options below are for experts. Leave off if unsure"), wxEmptyString)->Enable(false); + logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING_MESSAGE, _("&Options below are for experts. Leave off if unsure"), wxEmptyString)->Enable(false); logCosModulesMenu->AppendSeparator(); logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitFile), _("coreinit File-Access API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitFile)); logCosModulesMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_LOGGING0 + stdx::to_underlying(LogType::CoreinitThreadSync), _("coreinit Thread-Synchronization API"), wxEmptyString)->Check(cemuLog_isLoggingEnabled(LogType::CoreinitThreadSync)); diff --git a/src/util/Fiber/FiberUnix.cpp b/src/util/Fiber/FiberUnix.cpp index 0d527069..36430449 100644 --- a/src/util/Fiber/FiberUnix.cpp +++ b/src/util/Fiber/FiberUnix.cpp @@ -15,7 +15,12 @@ Fiber::Fiber(void(*FiberEntryPoint)(void* userParam), void* userParam, void* pri ctx->uc_stack.ss_sp = m_stackPtr; ctx->uc_stack.ss_size = stackSize; ctx->uc_link = &ctx[0]; +#ifdef __arm64__ + // https://www.man7.org/linux/man-pages/man3/makecontext.3.html#NOTES + makecontext(ctx, (void(*)())FiberEntryPoint, 2, (uint64) userParam >> 32, userParam); +#else makecontext(ctx, (void(*)())FiberEntryPoint, 1, userParam); +#endif this->m_implData = (void*)ctx; } diff --git a/src/util/MemMapper/MemMapperUnix.cpp b/src/util/MemMapper/MemMapperUnix.cpp index 0ade291d..8e800e53 100644 --- a/src/util/MemMapper/MemMapperUnix.cpp +++ b/src/util/MemMapper/MemMapperUnix.cpp @@ -45,7 +45,11 @@ namespace MemMapper void* r; if(fromReservation) { - if( mprotect(baseAddr, size, GetProt(permissionFlags)) == 0 ) + uint64 page_size = sysconf(_SC_PAGESIZE); + void* page = baseAddr; + if ( (uint64) baseAddr % page_size != 0 ) + page = (void*) ((uint64)baseAddr & ~(page_size - 1)); + if( mprotect(page, size, GetProt(permissionFlags)) == 0 ) r = baseAddr; else r = nullptr; diff --git a/src/util/highresolutiontimer/HighResolutionTimer.cpp b/src/util/highresolutiontimer/HighResolutionTimer.cpp index 67ffa349..bb4a40ab 100644 --- a/src/util/highresolutiontimer/HighResolutionTimer.cpp +++ b/src/util/highresolutiontimer/HighResolutionTimer.cpp @@ -27,6 +27,8 @@ uint64 HighResolutionTimer::m_freq = []() -> uint64 { LARGE_INTEGER freq; QueryPerformanceFrequency(&freq); return (uint64)(freq.QuadPart); +#elif BOOST_OS_MACOS + return 1000000000; #else timespec pc; clock_getres(CLOCK_MONOTONIC_RAW, &pc); From 4f4412b3341d4ac02377e5aa66eddbcfe2072c5c Mon Sep 17 00:00:00 2001 From: Joshua de Reeper <joshua@dereeper.co.nz> Date: Thu, 19 Jun 2025 23:30:19 +0200 Subject: [PATCH 287/299] nsyshid: Play Emulated Portal Audio via Mono Audio (#1478) --- src/Cafe/HW/Latte/Core/LatteShaderCache.cpp | 2 +- src/Cafe/OS/libs/nsyshid/Skylander.cpp | 46 ++++++--- src/Cafe/OS/libs/snd_core/ax_out.cpp | 17 +++- src/audio/IAudioAPI.cpp | 87 ++++++++++++---- src/audio/IAudioAPI.h | 18 +++- src/config/CemuConfig.cpp | 13 +++ src/config/CemuConfig.h | 4 +- src/gui/GeneralSettings2.cpp | 104 +++++++++++++++++++- src/gui/GeneralSettings2.h | 4 +- 9 files changed, 254 insertions(+), 41 deletions(-) diff --git a/src/Cafe/HW/Latte/Core/LatteShaderCache.cpp b/src/Cafe/HW/Latte/Core/LatteShaderCache.cpp index 0d427e34..86035973 100644 --- a/src/Cafe/HW/Latte/Core/LatteShaderCache.cpp +++ b/src/Cafe/HW/Latte/Core/LatteShaderCache.cpp @@ -209,7 +209,7 @@ class BootSoundPlayer try { - bootSndAudioDev = IAudioAPI::CreateDeviceFromConfig(true, sampleRate, nChannels, samplesPerBlock, bitsPerSample); + bootSndAudioDev = IAudioAPI::CreateDeviceFromConfig(IAudioAPI::AudioType::TV, sampleRate, nChannels, samplesPerBlock, bitsPerSample); if(!bootSndAudioDev) return; } diff --git a/src/Cafe/OS/libs/nsyshid/Skylander.cpp b/src/Cafe/OS/libs/nsyshid/Skylander.cpp index 9fab17b6..78337962 100644 --- a/src/Cafe/OS/libs/nsyshid/Skylander.cpp +++ b/src/Cafe/OS/libs/nsyshid/Skylander.cpp @@ -6,6 +6,8 @@ #include "Backend.h" #include "Common/FileStream.h" +#include "audio/IAudioAPI.h" +#include "config/CemuConfig.h" namespace nsyshid { @@ -558,6 +560,26 @@ namespace nsyshid Device::WriteResult SkylanderPortalDevice::Write(WriteMessage* message) { + if (message->length != 64) { + cemu_assert_error(); + } + + if (!g_portalAudio) + { + // Portal audio is mono channel, 16 bit audio. + // Audio is unsigned 16 bit, supplied as 64 bytes which is 32 samples per block + g_portalAudio = IAudioAPI::CreateDeviceFromConfig(IAudioAPI::AudioType::Portal, 8000, 32, 16); + } + std::array<sint16, 32> mono_samples; + for (unsigned int i = 0; i < mono_samples.size(); ++i) + { + sint16 sample = static_cast<uint16>(message->data[i * 2 + 1]) << 8 | static_cast<uint16>(message->data[i * 2]); + mono_samples[i] = sample; + } + if (g_portalAudio) + { + g_portalAudio->FeedBlock(mono_samples.data()); + } message->bytesWritten = message->length; return Device::WriteResult::Success; } @@ -604,20 +626,20 @@ namespace nsyshid *(uint16be*)(currentWritePtr + 7) = 0x001D; // wDescriptorLength currentWritePtr = currentWritePtr + 9; // endpoint descriptor 1 - *(uint8*)(currentWritePtr + 0) = 7; // bLength - *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType - *(uint8*)(currentWritePtr + 2) = 0x81; // bEndpointAddress - *(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes + *(uint8*)(currentWritePtr + 0) = 7; // bLength + *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType + *(uint8*)(currentWritePtr + 2) = 0x81; // bEndpointAddress + *(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes *(uint16be*)(currentWritePtr + 4) = 0x0040; // wMaxPacketSize - *(uint8*)(currentWritePtr + 6) = 0x01; // bInterval + *(uint8*)(currentWritePtr + 6) = 0x01; // bInterval currentWritePtr = currentWritePtr + 7; // endpoint descriptor 2 - *(uint8*)(currentWritePtr + 0) = 7; // bLength - *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType - *(uint8*)(currentWritePtr + 2) = 0x02; // bEndpointAddress - *(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes + *(uint8*)(currentWritePtr + 0) = 7; // bLength + *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType + *(uint8*)(currentWritePtr + 2) = 0x02; // bEndpointAddress + *(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes *(uint16be*)(currentWritePtr + 4) = 0x0040; // wMaxPacketSize - *(uint8*)(currentWritePtr + 6) = 0x01; // bInterval + *(uint8*)(currentWritePtr + 6) = 0x01; // bInterval currentWritePtr = currentWritePtr + 7; cemu_assert_debug((currentWritePtr - configurationDescriptor) == 0x29); @@ -628,8 +650,8 @@ namespace nsyshid } bool SkylanderPortalDevice::SetIdle(uint8 ifIndex, - uint8 reportId, - uint8 duration) + uint8 reportId, + uint8 duration) { return true; } diff --git a/src/Cafe/OS/libs/snd_core/ax_out.cpp b/src/Cafe/OS/libs/snd_core/ax_out.cpp index a88807f2..fe32cfb4 100644 --- a/src/Cafe/OS/libs/snd_core/ax_out.cpp +++ b/src/Cafe/OS/libs/snd_core/ax_out.cpp @@ -404,7 +404,7 @@ namespace snd_core { try { - g_tvAudio = IAudioAPI::CreateDeviceFromConfig(true, 48000, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16); + g_tvAudio = IAudioAPI::CreateDeviceFromConfig(IAudioAPI::AudioType::TV, 48000, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16); } catch (std::runtime_error& ex) { @@ -417,7 +417,7 @@ namespace snd_core { try { - g_padAudio = IAudioAPI::CreateDeviceFromConfig(false, 48000, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16); + g_padAudio = IAudioAPI::CreateDeviceFromConfig(IAudioAPI::AudioType::Gamepad, 48000, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16); if(g_padAudio) g_padVolume = g_padAudio->GetVolume(); } @@ -442,6 +442,11 @@ namespace snd_core g_padAudio->Stop(); g_padAudio.reset(); } + if (g_portalAudio) + { + g_portalAudio->Stop(); + g_portalAudio.reset(); + } } void AXOut_updateDevicePlayState(bool isPlaying) @@ -462,6 +467,14 @@ namespace snd_core else g_padAudio->Stop(); } + + if (g_portalAudio) + { + if (isPlaying) + g_portalAudio->Play(); + else + g_portalAudio->Stop(); + } } // called periodically to check for AX updates diff --git a/src/audio/IAudioAPI.cpp b/src/audio/IAudioAPI.cpp index 587526ab..dc266eed 100644 --- a/src/audio/IAudioAPI.cpp +++ b/src/audio/IAudioAPI.cpp @@ -13,13 +13,14 @@ std::shared_mutex g_audioMutex; AudioAPIPtr g_tvAudio; AudioAPIPtr g_padAudio; +AudioAPIPtr g_portalAudio; std::atomic_int32_t g_padVolume = 0; uint32 IAudioAPI::s_audioDelay = 2; std::array<bool, IAudioAPI::AudioAPIEnd> IAudioAPI::s_availableApis{}; IAudioAPI::IAudioAPI(uint32 samplerate, uint32 channels, uint32 samples_per_block, uint32 bits_per_sample) - : m_samplerate(samplerate), m_channels(channels), m_samplesPerBlock(samples_per_block), m_bitsPerSample(bits_per_sample) + : m_samplerate(samplerate), m_channels(channels), m_samplesPerBlock(samples_per_block), m_bitsPerSample(bits_per_sample) { m_bytesPerBlock = samples_per_block * channels * (bits_per_sample / 8); InitWFX(m_samplerate, m_channels, m_bitsPerSample); @@ -80,7 +81,7 @@ void IAudioAPI::InitializeStatic() #if BOOST_OS_WINDOWS s_availableApis[DirectSound] = true; s_availableApis[XAudio2] = XAudio2API::InitializeStatic(); - if(!s_availableApis[XAudio2]) // don't try to initialize the older lib if the newer version is available + if (!s_availableApis[XAudio2]) // don't try to initialize the older lib if the newer version is available s_availableApis[XAudio27] = XAudio27API::InitializeStatic(); #endif #if HAS_CUBEB @@ -97,30 +98,29 @@ bool IAudioAPI::IsAudioAPIAvailable(AudioAPI api) return false; } -AudioAPIPtr IAudioAPI::CreateDeviceFromConfig(bool TV, sint32 rate, sint32 samples_per_block, sint32 bits_per_sample) +AudioAPIPtr IAudioAPI::CreateDeviceFromConfig(AudioType type, sint32 rate, sint32 samples_per_block, sint32 bits_per_sample) { - auto& config = GetConfig(); - sint32 channels = CemuConfig::AudioChannelsToNChannels(TV ? config.tv_channels : config.pad_channels); - return CreateDeviceFromConfig(TV, rate, channels, samples_per_block, bits_per_sample); + sint32 channels = CemuConfig::AudioChannelsToNChannels(AudioTypeToChannels(type)); + return CreateDeviceFromConfig(type, rate, channels, samples_per_block, bits_per_sample); } -AudioAPIPtr IAudioAPI::CreateDeviceFromConfig(bool TV, sint32 rate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample) +AudioAPIPtr IAudioAPI::CreateDeviceFromConfig(AudioType type, sint32 rate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample) { AudioAPIPtr audioAPIDev; auto& config = GetConfig(); const auto audio_api = (IAudioAPI::AudioAPI)config.audio_api; - auto& selectedDevice = TV ? config.tv_device : config.pad_device; + auto selectedDevice = GetDeviceFromType(type); - if(selectedDevice.empty()) + if (selectedDevice.empty()) return {}; IAudioAPI::DeviceDescriptionPtr device_description; if (IAudioAPI::IsAudioAPIAvailable(audio_api)) { auto devices = IAudioAPI::GetDevices(audio_api); - const auto it = std::find_if(devices.begin(), devices.end(), [&selectedDevice](const auto& d) {return d->GetIdentifier() == selectedDevice; }); + const auto it = std::find_if(devices.begin(), devices.end(), [&selectedDevice](const auto& d) { return d->GetIdentifier() == selectedDevice; }); if (it != devices.end()) device_description = *it; } @@ -128,7 +128,8 @@ AudioAPIPtr IAudioAPI::CreateDeviceFromConfig(bool TV, sint32 rate, sint32 chann throw std::runtime_error("failed to find selected device while trying to create audio device"); audioAPIDev = CreateDevice(audio_api, device_description, rate, channels, samples_per_block, bits_per_sample); - audioAPIDev->SetVolume(TV ? config.tv_volume : config.pad_volume); + audioAPIDev->SetVolume(GetVolumeFromType(type)); + return audioAPIDev; } @@ -137,7 +138,7 @@ AudioAPIPtr IAudioAPI::CreateDevice(AudioAPI api, const DeviceDescriptionPtr& de if (!IsAudioAPIAvailable(api)) return {}; - switch(api) + switch (api) { #if BOOST_OS_WINDOWS case DirectSound: @@ -157,11 +158,11 @@ AudioAPIPtr IAudioAPI::CreateDevice(AudioAPI api, const DeviceDescriptionPtr& de } #endif #if HAS_CUBEB - case Cubeb: - { - const auto tmp = std::dynamic_pointer_cast<CubebAPI::CubebDeviceDescription>(device); - return std::make_unique<CubebAPI>(tmp->GetDeviceId(), samplerate, channels, samples_per_block, bits_per_sample); - } + case Cubeb: + { + const auto tmp = std::dynamic_pointer_cast<CubebAPI::CubebDeviceDescription>(device); + return std::make_unique<CubebAPI>(tmp->GetDeviceId(), samplerate, channels, samples_per_block, bits_per_sample); + } #endif default: throw std::runtime_error(fmt::format("invalid audio api: {}", api)); @@ -172,8 +173,8 @@ std::vector<IAudioAPI::DeviceDescriptionPtr> IAudioAPI::GetDevices(AudioAPI api) { if (!IsAudioAPIAvailable(api)) return {}; - - switch(api) + + switch (api) { #if BOOST_OS_WINDOWS case DirectSound: @@ -209,3 +210,51 @@ uint32 IAudioAPI::GetAudioDelay() const { return m_audioDelayOverride > 0 ? m_audioDelayOverride : s_audioDelay; } + +AudioChannels IAudioAPI::AudioTypeToChannels(AudioType type) +{ + auto& config = GetConfig(); + switch (type) + { + case TV: + return config.tv_channels; + case Gamepad: + return config.pad_channels; + case Portal: + return kMono; + default: + return kMono; + } +} + +std::wstring IAudioAPI::GetDeviceFromType(AudioType type) +{ + auto& config = GetConfig(); + switch (type) + { + case TV: + return config.tv_device; + case Gamepad: + return config.pad_device; + case Portal: + return config.portal_device; + default: + return L""; + } +} + +sint32 IAudioAPI::GetVolumeFromType(AudioType type) +{ + auto& config = GetConfig(); + switch (type) + { + case TV: + return config.tv_volume; + case Gamepad: + return config.pad_volume; + case Portal: + return config.portal_volume; + default: + return 0; + } +} diff --git a/src/audio/IAudioAPI.h b/src/audio/IAudioAPI.h index 8fb510db..34df421a 100644 --- a/src/audio/IAudioAPI.h +++ b/src/audio/IAudioAPI.h @@ -4,6 +4,8 @@ #include <mmreg.h> #endif +#include "config/CemuConfig.h" + class IAudioAPI { friend class GeneralSettings2; @@ -30,6 +32,13 @@ public: using DeviceDescriptionPtr = std::shared_ptr<DeviceDescription>; + enum AudioType + { + TV = 0, + Gamepad, + Portal + }; + enum AudioAPI { DirectSound = 0, @@ -62,8 +71,8 @@ public: static void InitializeStatic(); static bool IsAudioAPIAvailable(AudioAPI api); - static std::unique_ptr<IAudioAPI> CreateDeviceFromConfig(bool TV, sint32 rate, sint32 samples_per_block, sint32 bits_per_sample); - static std::unique_ptr<IAudioAPI> CreateDeviceFromConfig(bool TV, sint32 rate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample); + static std::unique_ptr<IAudioAPI> CreateDeviceFromConfig(AudioType type, sint32 rate, sint32 samples_per_block, sint32 bits_per_sample); + static std::unique_ptr<IAudioAPI> CreateDeviceFromConfig(AudioType type, sint32 rate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample); static std::unique_ptr<IAudioAPI> CreateDevice(AudioAPI api, const DeviceDescriptionPtr& device, sint32 samplerate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample); static std::vector<DeviceDescriptionPtr> GetDevices(AudioAPI api); @@ -84,6 +93,9 @@ protected: private: static uint32 s_audioDelay; void InitWFX(sint32 samplerate, sint32 channels, sint32 bits_per_sample); + static AudioChannels AudioTypeToChannels(AudioType type); + static std::wstring GetDeviceFromType(AudioType type); + static sint32 GetVolumeFromType(AudioType type); }; @@ -93,3 +105,5 @@ extern AudioAPIPtr g_tvAudio; extern AudioAPIPtr g_padAudio; extern std::atomic_int32_t g_padVolume; + +extern AudioAPIPtr g_portalAudio; diff --git a/src/config/CemuConfig.cpp b/src/config/CemuConfig.cpp index 6bb7ac34..809a5470 100644 --- a/src/config/CemuConfig.cpp +++ b/src/config/CemuConfig.cpp @@ -278,6 +278,7 @@ void CemuConfig::Load(XMLConfigParser& parser) tv_volume = audio.get("TVVolume", 20); pad_volume = audio.get("PadVolume", 0); input_volume = audio.get("InputVolume", 20); + portal_volume = audio.get("PortalVolume", 20); const auto tv = audio.get("TVDevice", ""); try @@ -309,6 +310,16 @@ void CemuConfig::Load(XMLConfigParser& parser) cemuLog_log(LogType::Force, "config load error: can't load input device: {}", input_device_name); } + const auto portal_device_name = audio.get("PortalDevice", ""); + try + { + portal_device = boost::nowide::widen(portal_device_name); + } + catch (const std::exception&) + { + cemuLog_log(LogType::Force, "config load error: can't load input device: {}", portal_device_name); + } + // account auto acc = parser.get("Account"); account.m_persistent_id = acc.get("PersistentId", account.m_persistent_id); @@ -511,9 +522,11 @@ void CemuConfig::Save(XMLConfigParser& parser) audio.set("TVVolume", tv_volume); audio.set("PadVolume", pad_volume); audio.set("InputVolume", input_volume); + audio.set("PortalVolume", portal_volume); audio.set("TVDevice", boost::nowide::narrow(tv_device).c_str()); audio.set("PadDevice", boost::nowide::narrow(pad_device).c_str()); audio.set("InputDevice", boost::nowide::narrow(input_device).c_str()); + audio.set("PortalDevice", boost::nowide::narrow(portal_device).c_str()); // account auto acc = config.set("Account"); diff --git a/src/config/CemuConfig.h b/src/config/CemuConfig.h index 191614a2..ca896eed 100644 --- a/src/config/CemuConfig.h +++ b/src/config/CemuConfig.h @@ -480,8 +480,8 @@ struct CemuConfig sint32 audio_api = 0; sint32 audio_delay = 2; AudioChannels tv_channels = kStereo, pad_channels = kStereo, input_channels = kMono; - sint32 tv_volume = 50, pad_volume = 0, input_volume = 50; - std::wstring tv_device{ L"default" }, pad_device, input_device; + sint32 tv_volume = 50, pad_volume = 0, input_volume = 50, portal_volume = 50; + std::wstring tv_device{ L"default" }, pad_device, input_device, portal_device; // account struct diff --git a/src/gui/GeneralSettings2.cpp b/src/gui/GeneralSettings2.cpp index 9b763229..4c23aedc 100644 --- a/src/gui/GeneralSettings2.cpp +++ b/src/gui/GeneralSettings2.cpp @@ -542,6 +542,36 @@ wxPanel* GeneralSettings2::AddAudioPage(wxNotebook* notebook) audio_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); } + { + auto box = new wxStaticBox(audio_panel, wxID_ANY, _("Trap Team Portal")); + auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL); + + auto portal_audio_row = new wxFlexGridSizer(0, 3, 0, 0); + portal_audio_row->SetFlexibleDirection(wxBOTH); + portal_audio_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); + + portal_audio_row->Add(new wxStaticText(box, wxID_ANY, _("Device")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + m_portal_device = new wxChoice(box, wxID_ANY, wxDefaultPosition); + m_portal_device->SetMinSize(wxSize(300, -1)); + m_portal_device->SetToolTip(_("Select the active audio output device for Wii U GamePad")); + portal_audio_row->Add(m_portal_device, 0, wxEXPAND | wxALL, 5); + portal_audio_row->AddSpacer(0); + + m_portal_device->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioDeviceSelected, this); + + portal_audio_row->Add(new wxStaticText(box, wxID_ANY, _("Volume")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); + m_portal_volume = new wxSlider(box, wxID_ANY, 100, 0, 100); + portal_audio_row->Add(m_portal_volume, 0, wxEXPAND | wxALL, 5); + auto audio_pad_volume_text = new wxStaticText(box, wxID_ANY, "100%"); + portal_audio_row->Add(audio_pad_volume_text, 0, wxALIGN_CENTER_VERTICAL | wxALL | wxALIGN_RIGHT, 5); + + m_portal_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnSliderChangedPercent, this, wxID_ANY, wxID_ANY, new wxControlObject(audio_pad_volume_text)); + m_portal_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnVolumeChanged, this); + + box_sizer->Add(portal_audio_row, 1, wxEXPAND, 5); + audio_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5); + } + audio_panel->SetSizerAndFit(audio_panel_sizer); return audio_panel; } @@ -993,6 +1023,7 @@ void GeneralSettings2::StoreConfig() config.tv_volume = m_tv_volume->GetValue(); config.pad_volume = m_pad_volume->GetValue(); config.input_volume = m_input_volume->GetValue(); + config.portal_volume = m_portal_volume->GetValue(); config.tv_device.clear(); const auto tv_device = m_tv_device->GetSelection(); @@ -1021,6 +1052,15 @@ void GeneralSettings2::StoreConfig() config.input_device = device_description->GetDescription()->GetIdentifier(); } + config.portal_device.clear(); + const auto portal_device = m_portal_device->GetSelection(); + if (portal_device != wxNOT_FOUND && portal_device != 0 && m_portal_device->HasClientObjectData()) + { + const auto* device_description = (wxDeviceDescription*)m_portal_device->GetClientObject(portal_device); + if (device_description) + config.portal_device = device_description->GetDescription()->GetIdentifier(); + } + // graphics config.graphic_api = (GraphicAPI)m_graphic_api->GetSelection(); @@ -1131,11 +1171,16 @@ void GeneralSettings2::OnVolumeChanged(wxCommandEvent& event) g_padVolume = event.GetInt(); } } - else + else if (event.GetEventObject() == m_tv_volume) { if (g_tvAudio) g_tvAudio->SetVolume(event.GetInt()); } + else + { + if(g_portalAudio) + g_portalAudio->SetVolume(event.GetInt()); + } } @@ -1195,10 +1240,12 @@ void GeneralSettings2::UpdateAudioDeviceList() m_tv_device->Clear(); m_pad_device->Clear(); m_input_device->Clear(); + m_portal_device->Clear(); m_tv_device->Append(_("Disabled")); m_pad_device->Append(_("Disabled")); m_input_device->Append(_("Disabled")); + m_portal_device->Append(_("Disabled")); const auto audio_api = (IAudioAPI::AudioAPI)GetConfig().audio_api; const auto devices = IAudioAPI::GetDevices(audio_api); @@ -1206,6 +1253,7 @@ void GeneralSettings2::UpdateAudioDeviceList() { m_tv_device->Append(device->GetName(), new wxDeviceDescription(device)); m_pad_device->Append(device->GetName(), new wxDeviceDescription(device)); + m_portal_device->Append(device->GetName(), new wxDeviceDescription(device)); } const auto input_audio_api = IAudioInputAPI::Cubeb; //(IAudioAPI::AudioAPI)GetConfig().input_audio_api; @@ -1225,6 +1273,8 @@ void GeneralSettings2::UpdateAudioDeviceList() m_input_device->SetSelection(0); + m_portal_device->SetSelection(0); + // todo reset global instance of audio device } @@ -1708,6 +1758,22 @@ void GeneralSettings2::ApplyConfig() else m_input_device->SetSelection(0); + SendSliderEvent(m_portal_volume, config.portal_volume); + if (!config.portal_device.empty() && m_portal_device->HasClientObjectData()) + { + for (uint32 i = 0; i < m_portal_device->GetCount(); ++i) + { + const auto device_description = (wxDeviceDescription*)m_portal_device->GetClientObject(i); + if (device_description && config.portal_device == device_description->GetDescription()->GetIdentifier()) + { + m_portal_device->SetSelection(i); + break; + } + } + } + else + m_portal_device->SetSelection(0); + // account UpdateOnlineAccounts(); m_active_account->SetSelection(0); @@ -1866,6 +1932,42 @@ void GeneralSettings2::UpdateAudioDevice() } } } + + // skylander portal audio device + { + const auto selection = m_portal_device->GetSelection(); + if (selection == wxNOT_FOUND) + { + cemu_assert_debug(false); + return; + } + + g_portalAudio.reset(); + + if (m_portal_device->HasClientObjectData()) + { + const auto description = (wxDeviceDescription*)m_portal_device->GetClientObject(selection); + if (description) + { + sint32 channels; + if (m_game_launched && g_portalAudio) + channels = g_portalAudio->GetChannels(); + else + channels = 1; + + try + { + g_portalAudio = IAudioAPI::CreateDevice((IAudioAPI::AudioAPI)config.audio_api, description->GetDescription(), 8000, 1, 32, 16); + g_portalAudio->SetVolume(m_portal_volume->GetValue()); + } + catch (std::runtime_error& ex) + { + cemuLog_log(LogType::Force, "can't initialize portal audio: {}", ex.what()); + } + } + } + + } } void GeneralSettings2::OnAudioDeviceSelected(wxCommandEvent& event) diff --git a/src/gui/GeneralSettings2.h b/src/gui/GeneralSettings2.h index 7fbfecc1..5e27d6e2 100644 --- a/src/gui/GeneralSettings2.h +++ b/src/gui/GeneralSettings2.h @@ -63,9 +63,9 @@ private: // Audio wxChoice* m_audio_api; wxSlider *m_audio_latency; - wxSlider *m_tv_volume, *m_pad_volume, *m_input_volume; + wxSlider *m_tv_volume, *m_pad_volume, *m_input_volume, *m_portal_volume; wxChoice *m_tv_channels, *m_pad_channels, *m_input_channels; - wxChoice *m_tv_device, *m_pad_device, *m_input_device; + wxChoice *m_tv_device, *m_pad_device, *m_input_device, *m_portal_device; // Account wxButton* m_create_account, * m_delete_account; From 057ef4598ea05bc96dc79a383a90abcc121e61bd Mon Sep 17 00:00:00 2001 From: capitalistspz <keipitalists@proton.me> Date: Fri, 20 Jun 2025 12:32:41 +0100 Subject: [PATCH 288/299] cmake: Respect `ENABLE_HIDAPI` option (#1604) --- CMakeLists.txt | 4 ++-- src/input/CMakeLists.txt | 18 +++++++++++------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c70b0a40..aa491b9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -166,7 +166,7 @@ if (UNIX AND NOT APPLE) if(ENABLE_BLUEZ) find_package(bluez REQUIRED) - set(ENABLE_WIIMOTE ON) + set(SUPPORTS_WIIMOTE ON) add_compile_definitions(HAS_BLUEZ) endif() @@ -188,7 +188,7 @@ endif() if (ENABLE_HIDAPI) find_package(hidapi REQUIRED) - set(ENABLE_WIIMOTE ON) + set(SUPPORTS_WIIMOTE ON) add_compile_definitions(HAS_HIDAPI) endif () diff --git a/src/input/CMakeLists.txt b/src/input/CMakeLists.txt index 62fa8d85..af8b8181 100644 --- a/src/input/CMakeLists.txt +++ b/src/input/CMakeLists.txt @@ -61,7 +61,7 @@ if(WIN32) ) endif() -if (ENABLE_WIIMOTE) +if (SUPPORTS_WIIMOTE) target_compile_definitions(CemuInput PUBLIC SUPPORTS_WIIMOTE) target_sources(CemuInput PRIVATE api/Wiimote/WiimoteControllerProvider.h @@ -70,13 +70,17 @@ if (ENABLE_WIIMOTE) api/Wiimote/NativeWiimoteController.h api/Wiimote/NativeWiimoteController.cpp api/Wiimote/WiimoteDevice.h - api/Wiimote/hidapi/HidapiWiimote.cpp - api/Wiimote/hidapi/HidapiWiimote.h ) - if (UNIX AND NOT APPLE) + if (ENABLE_HIDAPI) target_sources(CemuInput PRIVATE - api/Wiimote/l2cap/L2CapWiimote.cpp - api/Wiimote/l2cap/L2CapWiimote.h) + api/Wiimote/hidapi/HidapiWiimote.cpp + api/Wiimote/hidapi/HidapiWiimote.h) + endif () + + if (ENABLE_BLUEZ) + target_sources(CemuInput PRIVATE + api/Wiimote/l2cap/L2CapWiimote.cpp + api/Wiimote/l2cap/L2CapWiimote.h) endif() endif () @@ -95,6 +99,7 @@ target_link_libraries(CemuInput PRIVATE pugixml::pugixml SDL2::SDL2 ) + if (ENABLE_HIDAPI) target_link_libraries(CemuInput PRIVATE hidapi::hidapi) endif() @@ -103,7 +108,6 @@ if (ENABLE_WXWIDGETS) target_link_libraries(CemuInput PRIVATE wx::base wx::core) endif() - if (ENABLE_BLUEZ) target_link_libraries(CemuInput PRIVATE bluez::bluez) endif () \ No newline at end of file From 522b5ef2600904c320464a7cea54dc037e323459 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 21 Jun 2025 18:58:58 +0200 Subject: [PATCH 289/299] UI: Correctly interpret supporter names as UTF8 --- src/gui/MainWindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index c3d800e0..bf880e48 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -2054,7 +2054,7 @@ public: wxString& nameList = ((i % 2) == 0) ? nameListLeft : nameListRight; if (i >= 2) nameList.append("\n"); - nameList.append(name); + nameList.append(wxString::FromUTF8(name)); } gridSizer->Add(new wxStaticText(parent, wxID_ANY, nameListLeft), wxSizerFlags()); From 5a4731f919db19b4d5ba07f70b39c756c805b825 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 22 Jun 2025 20:56:47 +0200 Subject: [PATCH 290/299] HLE: Make HLE table access thread-safe Previous code could sometimes resize the vector while a read access was happening --- .../Interpreter/PPCInterpreterHLE.cpp | 52 +++++++++++-------- src/Cafe/HW/Espresso/PPCState.h | 4 +- 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterHLE.cpp b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterHLE.cpp index 24219e66..cf7ba195 100644 --- a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterHLE.cpp +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterHLE.cpp @@ -2,62 +2,70 @@ #include "PPCInterpreterInternal.h" #include "PPCInterpreterHelper.h" -std::unordered_set<std::string> sUnsupportedHLECalls; +std::unordered_set<std::string> s_unsupportedHLECalls; void PPCInterpreter_handleUnsupportedHLECall(PPCInterpreter_t* hCPU) { const char* libFuncName = (char*)memory_getPointerFromVirtualOffset(hCPU->instructionPointer + 8); std::string tempString = fmt::format("Unsupported lib call: {}", libFuncName); - if (sUnsupportedHLECalls.find(tempString) == sUnsupportedHLECalls.end()) + if (s_unsupportedHLECalls.find(tempString) == s_unsupportedHLECalls.end()) { cemuLog_log(LogType::UnsupportedAPI, "{}", tempString); - sUnsupportedHLECalls.emplace(tempString); + s_unsupportedHLECalls.emplace(tempString); } hCPU->gpr[3] = 0; PPCInterpreter_nextInstruction(hCPU); } -std::vector<void(*)(PPCInterpreter_t* hCPU)>* sPPCHLETable{}; +static constexpr size_t HLE_TABLE_CAPACITY = 0x4000; +HLECALL s_ppcHleTable[HLE_TABLE_CAPACITY]{}; +sint32 s_ppcHleTableWriteIndex = 0; +std::mutex s_ppcHleTableMutex; HLEIDX PPCInterpreter_registerHLECall(HLECALL hleCall, std::string hleName) { - if (!sPPCHLETable) - sPPCHLETable = new std::vector<void(*)(PPCInterpreter_t* hCPU)>(); - for (sint32 i = 0; i < sPPCHLETable->size(); i++) + std::unique_lock _l(s_ppcHleTableMutex); + if (s_ppcHleTableWriteIndex >= HLE_TABLE_CAPACITY) { - if ((*sPPCHLETable)[i] == hleCall) - return i; + cemuLog_log(LogType::Force, "HLE table is full"); + cemu_assert(false); } - HLEIDX newFuncIndex = (sint32)sPPCHLETable->size(); - sPPCHLETable->resize(sPPCHLETable->size() + 1); - (*sPPCHLETable)[newFuncIndex] = hleCall; - return newFuncIndex; + for (sint32 i = 0; i < s_ppcHleTableWriteIndex; i++) + { + if (s_ppcHleTable[i] == hleCall) + { + return i; + } + } + cemu_assert(s_ppcHleTableWriteIndex < HLE_TABLE_CAPACITY); + s_ppcHleTable[s_ppcHleTableWriteIndex] = hleCall; + HLEIDX funcIndex = s_ppcHleTableWriteIndex; + s_ppcHleTableWriteIndex++; + return funcIndex; } HLECALL PPCInterpreter_getHLECall(HLEIDX funcIndex) { - if (funcIndex < 0 || funcIndex >= sPPCHLETable->size()) + if (funcIndex < 0 || funcIndex >= HLE_TABLE_CAPACITY) return nullptr; - return sPPCHLETable->data()[funcIndex]; + return s_ppcHleTable[funcIndex]; } -std::mutex g_hleLogMutex; +std::mutex s_hleLogMutex; void PPCInterpreter_virtualHLE(PPCInterpreter_t* hCPU, unsigned int opcode) { uint32 hleFuncId = opcode & 0xFFFF; - if (hleFuncId == 0xFFD0) + if (hleFuncId == 0xFFD0) [[unlikely]] { - g_hleLogMutex.lock(); + s_hleLogMutex.lock(); PPCInterpreter_handleUnsupportedHLECall(hCPU); - g_hleLogMutex.unlock(); - return; + s_hleLogMutex.unlock(); } else { // os lib function - cemu_assert(hleFuncId < sPPCHLETable->size()); - auto hleCall = (*sPPCHLETable)[hleFuncId]; + auto hleCall = PPCInterpreter_getHLECall(hleFuncId); cemu_assert(hleCall); hleCall(hCPU); } diff --git a/src/Cafe/HW/Espresso/PPCState.h b/src/Cafe/HW/Espresso/PPCState.h index 179e2687..fd943d39 100644 --- a/src/Cafe/HW/Espresso/PPCState.h +++ b/src/Cafe/HW/Espresso/PPCState.h @@ -230,9 +230,9 @@ static inline float flushDenormalToZero(float f) // HLE interface -typedef void(*HLECALL)(PPCInterpreter_t* hCPU); +using HLECALL = void(*)(PPCInterpreter_t*); +using HLEIDX = sint32; -typedef sint32 HLEIDX; HLEIDX PPCInterpreter_registerHLECall(HLECALL hleCall, std::string hleName); HLECALL PPCInterpreter_getHLECall(HLEIDX funcIndex); From 4f4c9594ac77c74ef63a8b4208343ddf06669797 Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 22 Jun 2025 22:17:29 +0200 Subject: [PATCH 291/299] GX2: Fix command buffer padding writing out of bounds --- src/Cafe/OS/libs/gx2/GX2_Command.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Cafe/OS/libs/gx2/GX2_Command.cpp b/src/Cafe/OS/libs/gx2/GX2_Command.cpp index 6699e1e1..d12bf210 100644 --- a/src/Cafe/OS/libs/gx2/GX2_Command.cpp +++ b/src/Cafe/OS/libs/gx2/GX2_Command.cpp @@ -144,6 +144,11 @@ namespace GX2 void GX2Command_StartNewCommandBuffer(uint32 numU32s) { + // On submission command buffers are padded to 32 byte alignment + // but nowhere is it guaranteed that internal command buffers have their size aligned to 32 byte (even on console, but testing is required) + // Thus the padding can write out of bounds but this seems to trigger only very rarely in partice. As a workaround we always pad the command buffer size to 32 bytes here + numU32s = (numU32s + 7) & ~0x7; + uint32 coreIndex = coreinit::OSGetCoreId(); auto& coreCBState = s_perCoreCBState[coreIndex]; numU32s = std::max<uint32>(numU32s, 0x100); From e91740cf29248bfbf2f059ac7e42159e8e7e9e9a Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sun, 22 Jun 2025 23:34:14 +0200 Subject: [PATCH 292/299] coreinit: Make sure thread deallocation runs before join returns Fixes crash in Coaster Crazy Deluxe --- src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp index 2eef929d..2f89000b 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_Thread.cpp @@ -717,7 +717,10 @@ namespace coreinit thread->id = 0x8000; if (!thread->deallocatorFunc.IsNull()) + { __OSQueueThreadDeallocation(thread); + PPCCore_switchToSchedulerWithLock(); // make sure the deallocation function runs before we return + } __OSUnlockScheduler(); @@ -1525,7 +1528,7 @@ namespace coreinit } // queue thread deallocation to run after current thread finishes - // the termination threads run at a higher priority on the same threads + // the termination threads run at a higher priority on the same core void __OSQueueThreadDeallocation(OSThread_t* thread) { uint32 coreIndex = OSGetCoreId(); From 0a121c97c79d329caa027e21714737a70cef80dd Mon Sep 17 00:00:00 2001 From: capitalistspz <keipitalists@proton.me> Date: Thu, 26 Jun 2025 16:25:34 +0100 Subject: [PATCH 293/299] Input: Detect Classic Controller Pro as Classic Controller (#1614) --- src/input/api/Wiimote/WiimoteControllerProvider.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/input/api/Wiimote/WiimoteControllerProvider.cpp b/src/input/api/Wiimote/WiimoteControllerProvider.cpp index 221d75a7..c2454319 100644 --- a/src/input/api/Wiimote/WiimoteControllerProvider.cpp +++ b/src/input/api/Wiimote/WiimoteControllerProvider.cpp @@ -323,6 +323,7 @@ void WiimoteControllerProvider::reader_thread() break; case kExtensionClassicPro: cemuLog_logDebug(LogType::Force,"Extension Type Received: Classic Pro"); + new_state.m_extension = ClassicData{}; break; case kExtensionGuitar: cemuLog_logDebug(LogType::Force,"Extension Type Received: Guitar"); From 7db2b77983ce4059c6bf706b1e8789b1f4038acc Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 28 Jun 2025 14:53:15 +0200 Subject: [PATCH 294/299] CPU: Implement more instructions in interpreter + various fixes All of the changes are verified against real hardware, except for the byte string instructions --- .../Interpreter/PPCInterpreterALU.hpp | 165 +++++++++++++----- .../Interpreter/PPCInterpreterImpl.cpp | 81 ++++++--- .../Interpreter/PPCInterpreterLoadStore.hpp | 115 ++++++++++-- .../Interpreter/PPCInterpreterMain.cpp | 3 +- .../Interpreter/PPCInterpreterOPC.cpp | 1 - .../Interpreter/PPCInterpreterOPC.hpp | 2 + .../OS/libs/coreinit/coreinit_OSScreen.cpp | 1 - 7 files changed, 281 insertions(+), 87 deletions(-) diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterALU.hpp b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterALU.hpp index 769344f8..2fe07509 100644 --- a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterALU.hpp +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterALU.hpp @@ -41,7 +41,7 @@ static void PPCInterpreter_ADD(PPCInterpreter_t* hCPU, uint32 opcode) static void PPCInterpreter_ADDO(PPCInterpreter_t* hCPU, uint32 opcode) { - // untested (Don't Starve Giant Edition uses this instruction + BSO) + // Don't Starve Giant Edition uses this instruction + BSO PPC_OPC_TEMPL3_XO(); uint32 result = hCPU->gpr[rA] + hCPU->gpr[rB]; PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(hCPU->gpr[rA], hCPU->gpr[rB], result)); @@ -113,7 +113,6 @@ static void PPCInterpreter_ADDEO(PPCInterpreter_t* hCPU, uint32 opcode) else hCPU->xer_ca = 0; PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(a, b, hCPU->gpr[rD])); - // update CR if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); @@ -130,7 +129,7 @@ static void PPCInterpreter_ADDI(PPCInterpreter_t* hCPU, uint32 opcode) static void PPCInterpreter_ADDIC(PPCInterpreter_t* hCPU, uint32 opcode) { - int rD, rA; + sint32 rD, rA; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); uint32 a = hCPU->gpr[rA]; @@ -145,7 +144,7 @@ static void PPCInterpreter_ADDIC(PPCInterpreter_t* hCPU, uint32 opcode) static void PPCInterpreter_ADDIC_(PPCInterpreter_t* hCPU, uint32 opcode) { - int rD, rA; + sint32 rD, rA; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); uint32 a = hCPU->gpr[rA]; @@ -155,14 +154,13 @@ static void PPCInterpreter_ADDIC_(PPCInterpreter_t* hCPU, uint32 opcode) hCPU->xer_ca = 1; else hCPU->xer_ca = 0; - // update cr0 flags ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); } static void PPCInterpreter_ADDIS(PPCInterpreter_t* hCPU, uint32 opcode) { - int rD, rA; + sint32 rD, rA; uint32 imm; PPC_OPC_TEMPL_D_Shift16(opcode, rD, rA, imm); hCPU->gpr[rD] = (rA ? hCPU->gpr[rA] : 0) + imm; @@ -185,6 +183,23 @@ static void PPCInterpreter_ADDZE(PPCInterpreter_t* hCPU, uint32 opcode) PPCInterpreter_nextInstruction(hCPU); } +static void PPCInterpreter_ADDZEO(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + PPC_ASSERT(rB == 0); + uint32 a = hCPU->gpr[rA]; + uint32 ca = hCPU->xer_ca; + hCPU->gpr[rD] = a + ca; + PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(a, 0, hCPU->gpr[rD])); + if ((a == 0xffffffff) && ca) + hCPU->xer_ca = 1; + else + hCPU->xer_ca = 0; + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rD]); + PPCInterpreter_nextInstruction(hCPU); +} + static void PPCInterpreter_ADDME(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); @@ -201,6 +216,23 @@ static void PPCInterpreter_ADDME(PPCInterpreter_t* hCPU, uint32 opcode) PPCInterpreter_nextInstruction(hCPU); } +static void PPCInterpreter_ADDMEO(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + PPC_ASSERT(rB == 0); + uint32 a = hCPU->gpr[rA]; + uint32 ca = hCPU->xer_ca; + hCPU->gpr[rD] = a + ca + 0xffffffff; + PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(a, 0xffffffff, hCPU->gpr[rD])); + if (a || ca) + hCPU->xer_ca = 1; + else + hCPU->xer_ca = 0; + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rD]); + PPCInterpreter_nextInstruction(hCPU); +} + static void PPCInterpreter_SUBF(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); @@ -260,7 +292,7 @@ static void PPCInterpreter_SUBFCO(PPCInterpreter_t* hCPU, uint32 opcode) static void PPCInterpreter_SUBFIC(PPCInterpreter_t* hCPU, uint32 opcode) { - int rD, rA; + sint32 rD, rA; uint32 imm; PPC_OPC_TEMPL_D_SImm(opcode, rD, rA, imm); uint32 a = hCPU->gpr[rA]; @@ -284,7 +316,6 @@ static void PPCInterpreter_SUBFE(PPCInterpreter_t* hCPU, uint32 opcode) hCPU->xer_ca = 1; else hCPU->xer_ca = 0; - // update cr0 if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); @@ -304,7 +335,6 @@ static void PPCInterpreter_SUBFEO(PPCInterpreter_t* hCPU, uint32 opcode) else hCPU->xer_ca = 0; PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(~a, b, result)); - // update cr0 if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); @@ -326,9 +356,25 @@ static void PPCInterpreter_SUBFZE(PPCInterpreter_t* hCPU, uint32 opcode) PPCInterpreter_nextInstruction(hCPU); } +static void PPCInterpreter_SUBFZEO(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + PPC_ASSERT(rB == 0); + uint32 a = hCPU->gpr[rA]; + uint32 ca = hCPU->xer_ca; + hCPU->gpr[rD] = ~a + ca; + PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(~a, 0, hCPU->gpr[rD])); + if (a == 0 && ca) + hCPU->xer_ca = 1; + else + hCPU->xer_ca = 0; + if (opHasRC()) + ppc_update_cr0(hCPU, hCPU->gpr[rD]); + PPCInterpreter_nextInstruction(hCPU); +} + static void PPCInterpreter_SUBFME(PPCInterpreter_t* hCPU, uint32 opcode) { - // untested PPC_OPC_TEMPL3_XO(); PPC_ASSERT(rB == 0); uint32 a = hCPU->gpr[rA]; @@ -339,7 +385,24 @@ static void PPCInterpreter_SUBFME(PPCInterpreter_t* hCPU, uint32 opcode) hCPU->xer_ca = 1; else hCPU->xer_ca = 0; - // update cr0 + if (opcode & PPC_OPC_RC) + ppc_update_cr0(hCPU, hCPU->gpr[rD]); + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_SUBFMEO(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL3_XO(); + PPC_ASSERT(rB == 0); + uint32 a = hCPU->gpr[rA]; + uint32 ca = hCPU->xer_ca; + hCPU->gpr[rD] = ~a + 0xFFFFFFFF + ca; + PPCInterpreter_setXerOV(hCPU, checkAdditionOverflow(~a, 0xFFFFFFFF, hCPU->gpr[rD])); + // update xer carry + if (ppc_carry_3(~a, 0xFFFFFFFF, ca)) + hCPU->xer_ca = 1; + else + hCPU->xer_ca = 0; if (opcode & PPC_OPC_RC) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); @@ -352,13 +415,8 @@ static void PPCInterpreter_MULHW_(PPCInterpreter_t* hCPU, uint32 opcode) sint64 b = (sint32)hCPU->gpr[rB]; sint64 c = a * b; hCPU->gpr[rD] = ((uint64)c) >> 32; - if (opcode & PPC_OPC_RC) { - // update cr0 flags -#ifdef CEMU_DEBUG_ASSERT - assert_dbg(); -#endif + if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); - } PPCInterpreter_nextInstruction(hCPU); } @@ -409,14 +467,14 @@ static void PPCInterpreter_MULLI(PPCInterpreter_t* hCPU, uint32 opcode) static void PPCInterpreter_DIVW(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); - sint32 a = hCPU->gpr[rA]; - sint32 b = hCPU->gpr[rB]; + sint32 a = (sint32)hCPU->gpr[rA]; + sint32 b = (sint32)hCPU->gpr[rB]; if (b == 0) - { - cemuLog_logDebug(LogType::Force, "Error: Division by zero! [{:08x}]", (uint32)hCPU->instructionPointer); - b++; - } - hCPU->gpr[rD] = a / b; + hCPU->gpr[rD] = a < 0 ? 0xFFFFFFFF : 0; + else if (a == 0x80000000 && b == 0xFFFFFFFF) + hCPU->gpr[rD] = 0xFFFFFFFF; + else + hCPU->gpr[rD] = a / b; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); @@ -425,16 +483,23 @@ static void PPCInterpreter_DIVW(PPCInterpreter_t* hCPU, uint32 opcode) static void PPCInterpreter_DIVWO(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); - sint32 a = hCPU->gpr[rA]; - sint32 b = hCPU->gpr[rB]; + sint32 a = (sint32)hCPU->gpr[rA]; + sint32 b = (sint32)hCPU->gpr[rB]; if (b == 0) { PPCInterpreter_setXerOV(hCPU, true); - PPCInterpreter_nextInstruction(hCPU); - return; + hCPU->gpr[rD] = a < 0 ? 0xFFFFFFFF : 0; + } + else if(a == 0x80000000 && b == 0xFFFFFFFF) + { + PPCInterpreter_setXerOV(hCPU, true); + hCPU->gpr[rD] = 0xFFFFFFFF; + } + else + { + hCPU->gpr[rD] = a / b; + PPCInterpreter_setXerOV(hCPU, false); } - hCPU->gpr[rD] = a / b; - PPCInterpreter_setXerOV(hCPU, false); if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); @@ -443,12 +508,14 @@ static void PPCInterpreter_DIVWO(PPCInterpreter_t* hCPU, uint32 opcode) static void PPCInterpreter_DIVWU(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); - if (hCPU->gpr[rB] == 0) - { - PPCInterpreter_nextInstruction(hCPU); - return; - } - hCPU->gpr[rD] = hCPU->gpr[rA] / hCPU->gpr[rB]; + uint32 a = hCPU->gpr[rA]; + uint32 b = hCPU->gpr[rB]; + if (b == 0) + hCPU->gpr[rD] = 0; + else if (a == 0x80000000 && b == 0xFFFFFFFF) + hCPU->gpr[rD] = 0; + else + hCPU->gpr[rD] = a / b; if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); @@ -457,14 +524,23 @@ static void PPCInterpreter_DIVWU(PPCInterpreter_t* hCPU, uint32 opcode) static void PPCInterpreter_DIVWUO(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL3_XO(); - if (hCPU->gpr[rB] == 0) + uint32 a = hCPU->gpr[rA]; + uint32 b = hCPU->gpr[rB]; + if (b == 0) { PPCInterpreter_setXerOV(hCPU, true); - PPCInterpreter_nextInstruction(hCPU); - return; + hCPU->gpr[rD] = 0; + } + else if(a == 0x80000000 && b == 0xFFFFFFFF) + { + PPCInterpreter_setXerOV(hCPU, false); + hCPU->gpr[rD] = 0; + } + else + { + hCPU->gpr[rD] = a / b; + PPCInterpreter_setXerOV(hCPU, false); } - hCPU->gpr[rD] = hCPU->gpr[rA] / hCPU->gpr[rB]; - PPCInterpreter_setXerOV(hCPU, false); if (opHasRC()) ppc_update_cr0(hCPU, hCPU->gpr[rD]); PPCInterpreter_nextInstruction(hCPU); @@ -491,6 +567,13 @@ static void PPCInterpreter_CRANDC(PPCInterpreter_t* hCPU, uint32 opcode) PPCInterpreter_nextInstruction(hCPU); } +static void PPCInterpreter_CRNAND(PPCInterpreter_t* hCPU, uint32 opcode) +{ + PPC_OPC_TEMPL_X_CR(); + ppc_setCRBit(hCPU, crD, (ppc_getCRBit(hCPU, crA)&ppc_getCRBit(hCPU, crB)) ^ 1); + PPCInterpreter_nextInstruction(hCPU); +} + static void PPCInterpreter_CROR(PPCInterpreter_t* hCPU, uint32 opcode) { PPC_OPC_TEMPL_X_CR(); diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterImpl.cpp b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterImpl.cpp index cacfa4a9..547472ab 100644 --- a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterImpl.cpp +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterImpl.cpp @@ -428,9 +428,6 @@ public: } }; -uint32 testIP[100]; -uint32 testIPC = 0; - template <typename ppcItpCtrl> class PPCInterpreterContainer { @@ -466,6 +463,10 @@ public: case 1: // virtual HLE PPCInterpreter_virtualHLE(hCPU, opcode); break; + case 3: + cemuLog_logDebug(LogType::Force, "Unsupported TWI instruction executed at {:08x}", hCPU->instructionPointer); + PPCInterpreter_nextInstruction(hCPU); + break; case 4: switch (PPC_getBits(opcode, 30, 5)) { @@ -482,8 +483,9 @@ public: PPCInterpreter_PS_CMPU1(hCPU, opcode); break; default: - debug_printf("Unknown execute %04X as [4->0] at %08X\n", PPC_getBits(opcode, 25, 5), hCPU->instructionPointer); + cemuLog_logDebug(LogType::Force, "Unknown execute {:04x} as [4->0] at {:08x}", PPC_getBits(opcode, 25, 5), hCPU->instructionPointer); cemu_assert_unimplemented(); + hCPU->instructionPointer += 4; break; } break; @@ -509,8 +511,9 @@ public: PPCInterpreter_PS_ABS(hCPU, opcode); break; default: - debug_printf("Unknown execute %04X as [4->8] at %08X\n", PPC_getBits(opcode, 25, 5), hCPU->instructionPointer); + cemuLog_logDebug(LogType::Force, "Unknown execute {:04x} as [4->8] at {:08x}", PPC_getBits(opcode, 25, 5), hCPU->instructionPointer); cemu_assert_unimplemented(); + hCPU->instructionPointer += 4; break; } break; @@ -548,8 +551,9 @@ public: PPCInterpreter_PS_MERGE11(hCPU, opcode); break; default: - debug_printf("Unknown execute %04X as [4->16] at %08X\n", PPC_getBits(opcode, 25, 5), hCPU->instructionPointer); - debugBreakpoint(); + cemuLog_logDebug(LogType::Force, "Unknown execute {:04x} as [4->16] at {:08x}", PPC_getBits(opcode, 25, 5), hCPU->instructionPointer); + cemu_assert_unimplemented(); + hCPU->instructionPointer += 4; break; } break; @@ -590,8 +594,9 @@ public: PPCInterpreter_PS_NMADD(hCPU, opcode); break; default: - debug_printf("Unknown execute %04X as [4] at %08X\n", PPC_getBits(opcode, 30, 5), hCPU->instructionPointer); + cemuLog_logDebug(LogType::Force, "Unknown execute {:04x} as [4] at {:08x}", PPC_getBits(opcode, 30, 5), hCPU->instructionPointer); cemu_assert_unimplemented(); + hCPU->instructionPointer += 4; break; } break; @@ -623,12 +628,15 @@ public: PPCInterpreter_BCX(hCPU, opcode); break; case 17: - if (PPC_getBits(opcode, 30, 1) == 1) { + if (PPC_getBits(opcode, 30, 1) == 1) + { PPCInterpreter_SC(hCPU, opcode); } - else { - debug_printf("Unsupported Opcode [0x17 --> 0x0]\n"); + else + { + cemuLog_logDebug(LogType::Force, "Unsupported Opcode [0x17 --> 0x0]"); cemu_assert_unimplemented(); + hCPU->instructionPointer += 4; } break; case 18: @@ -658,6 +666,9 @@ public: case 193: PPCInterpreter_CRXOR(hCPU, opcode); break; + case 225: + PPCInterpreter_CRNAND(hCPU, opcode); + break; case 257: PPCInterpreter_CRAND(hCPU, opcode); break; @@ -674,8 +685,9 @@ public: PPCInterpreter_BCCTR(hCPU, opcode); break; default: - debug_printf("Unknown execute %04X as [19] at %08X\n", PPC_getBits(opcode, 30, 10), hCPU->instructionPointer); + cemuLog_logDebug(LogType::Force, "Unknown execute {:04x} as [19] at {:08x}\n", PPC_getBits(opcode, 30, 10), hCPU->instructionPointer); cemu_assert_unimplemented(); + hCPU->instructionPointer += 4; break; } break; @@ -713,9 +725,6 @@ public: PPCInterpreter_CMP(hCPU, opcode); break; case 4: - #ifdef CEMU_DEBUG_ASSERT - debug_printf("TW instruction executed at %08x\n", hCPU->instructionPointer); - #endif PPCInterpreter_TW(hCPU, opcode); break; case 8: @@ -895,6 +904,12 @@ public: case 522: PPCInterpreter_ADDCO(hCPU, opcode); break; + case 523: // 11 | OE + PPCInterpreter_MULHWU_(hCPU, opcode); // OE is ignored + break; + case 533: + PPCInterpreter_LSWX(hCPU, opcode); + break; case 534: PPCInterpreter_LWBRX(hCPU, opcode); break; @@ -913,6 +928,9 @@ public: case 567: PPCInterpreter_LFSUX(hCPU, opcode); break; + case 587: // 75 | OE + PPCInterpreter_MULHW_(hCPU, opcode); // OE is ignored for MULHW + break; case 595: PPCInterpreter_MFSR(hCPU, opcode); break; @@ -943,15 +961,30 @@ public: case 663: PPCInterpreter_STFSX(hCPU, opcode); break; + case 661: + PPCInterpreter_STSWX(hCPU, opcode); + break; case 695: PPCInterpreter_STFSUX(hCPU, opcode); break; + case 712: // 200 | OE + PPCInterpreter_SUBFZEO(hCPU, opcode); + break; + case 714: // 202 | OE + PPCInterpreter_ADDZEO(hCPU, opcode); + break; case 725: PPCInterpreter_STSWI(hCPU, opcode); break; case 727: PPCInterpreter_STFDX(hCPU, opcode); break; + case 744: // 232 | OE + PPCInterpreter_SUBFMEO(hCPU, opcode); + break; + case 746: // 234 | OE + PPCInterpreter_ADDMEO(hCPU, opcode); + break; case 747: PPCInterpreter_MULLWO(hCPU, opcode); break; @@ -998,10 +1031,8 @@ public: PPCInterpreter_DCBZ(hCPU, opcode); break; default: - debug_printf("Unknown execute %04X as [31] at %08X\n", PPC_getBits(opcode, 30, 10), hCPU->instructionPointer); - #ifdef CEMU_DEBUG_ASSERT - assert_dbg(); - #endif + cemuLog_logDebug(LogType::Force, "Unknown execute {:04x} as [31] at {:08x}\n", PPC_getBits(opcode, 30, 10), hCPU->instructionPointer); + cemu_assert_unimplemented(); hCPU->instructionPointer += 4; break; } @@ -1084,7 +1115,7 @@ public: case 57: PPCInterpreter_PSQ_LU(hCPU, opcode); break; - case 59: //Opcode category + case 59: // opcode category switch (PPC_getBits(opcode, 30, 5)) { case 18: @@ -1115,8 +1146,9 @@ public: PPCInterpreter_FNMADDS(hCPU, opcode); break; default: - debug_printf("Unknown execute %04X as [59] at %08X\n", PPC_getBits(opcode, 30, 10), hCPU->instructionPointer); + cemuLog_logDebug(LogType::Force, "Unknown execute {:04x} as [59] at {:08x}\n", PPC_getBits(opcode, 30, 10), hCPU->instructionPointer); cemu_assert_unimplemented(); + hCPU->instructionPointer += 4; break; } break; @@ -1195,18 +1227,19 @@ public: case 583: PPCInterpreter_MFFS(hCPU, opcode); break; - case 711: // IBM documentation has this wrong as 771? + case 711: PPCInterpreter_MTFSF(hCPU, opcode); break; default: - debug_printf("Unknown execute %04X as [63] at %08X\n", PPC_getBits(opcode, 30, 10), hCPU->instructionPointer); + cemuLog_logDebug(LogType::Force, "Unknown execute {:04x} as [63] at {:08x}\n", PPC_getBits(opcode, 30, 10), hCPU->instructionPointer); cemu_assert_unimplemented(); + PPCInterpreter_nextInstruction(hCPU); break; } } break; default: - debug_printf("Unknown execute %04X at %08X\n", PPC_getBits(opcode, 5, 6), (unsigned int)hCPU->instructionPointer); + cemuLog_logDebug(LogType::Force, "Unknown execute {:04x} at {:08x}\n", PPC_getBits(opcode, 5, 6), (unsigned int)hCPU->instructionPointer); cemu_assert_unimplemented(); } } diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterLoadStore.hpp b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterLoadStore.hpp index 26467458..ea7bb038 100644 --- a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterLoadStore.hpp +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterLoadStore.hpp @@ -31,7 +31,7 @@ static void PPCInterpreter_STW(PPCInterpreter_t* hCPU, uint32 Opcode) static void PPCInterpreter_STWU(PPCInterpreter_t* hCPU, uint32 Opcode) { - int rA, rS; + sint32 rA, rS; uint32 imm; PPC_OPC_TEMPL_D_SImm(Opcode, rS, rA, imm); ppcItpCtrl::ppcMem_writeDataU32(hCPU, hCPU->gpr[rA] + imm, hCPU->gpr[rS]); @@ -42,7 +42,7 @@ static void PPCInterpreter_STWU(PPCInterpreter_t* hCPU, uint32 Opcode) static void PPCInterpreter_STWX(PPCInterpreter_t* hCPU, uint32 Opcode) { - int rA, rS, rB; + sint32 rA, rS, rB; PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); ppcItpCtrl::ppcMem_writeDataU32(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], hCPU->gpr[rS]); PPCInterpreter_nextInstruction(hCPU); @@ -103,7 +103,7 @@ static void PPCInterpreter_STWCX(PPCInterpreter_t* hCPU, uint32 Opcode) static void PPCInterpreter_STWUX(PPCInterpreter_t* hCPU, uint32 Opcode) { - int rA, rS, rB; + sint32 rA, rS, rB; PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); ppcItpCtrl::ppcMem_writeDataU32(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], hCPU->gpr[rS]); if (rA) @@ -113,7 +113,7 @@ static void PPCInterpreter_STWUX(PPCInterpreter_t* hCPU, uint32 Opcode) static void PPCInterpreter_STWBRX(PPCInterpreter_t* hCPU, uint32 Opcode) { - int rA, rS, rB; + sint32 rA, rS, rB; PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); ppcItpCtrl::ppcMem_writeDataU32(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], _swapEndianU32(hCPU->gpr[rS])); PPCInterpreter_nextInstruction(hCPU); @@ -121,7 +121,7 @@ static void PPCInterpreter_STWBRX(PPCInterpreter_t* hCPU, uint32 Opcode) static void PPCInterpreter_STMW(PPCInterpreter_t* hCPU, uint32 Opcode) { - int rS, rA; + sint32 rS, rA; uint32 imm; PPC_OPC_TEMPL_D_SImm(Opcode, rS, rA, imm); uint32 ea = (rA ? hCPU->gpr[rA] : 0) + imm; @@ -136,7 +136,7 @@ static void PPCInterpreter_STMW(PPCInterpreter_t* hCPU, uint32 Opcode) static void PPCInterpreter_STH(PPCInterpreter_t* hCPU, uint32 Opcode) { - int rA, rS; + sint32 rA, rS; uint32 imm; PPC_OPC_TEMPL_D_SImm(Opcode, rS, rA, imm); ppcItpCtrl::ppcMem_writeDataU16(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm, (uint16)hCPU->gpr[rS]); @@ -145,7 +145,7 @@ static void PPCInterpreter_STH(PPCInterpreter_t* hCPU, uint32 Opcode) static void PPCInterpreter_STHU(PPCInterpreter_t* hCPU, uint32 Opcode) { - int rA, rS; + sint32 rA, rS; uint32 imm; PPC_OPC_TEMPL_D_SImm(Opcode, rS, rA, imm); ppcItpCtrl::ppcMem_writeDataU16(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm, (uint16)hCPU->gpr[rS]); @@ -156,7 +156,7 @@ static void PPCInterpreter_STHU(PPCInterpreter_t* hCPU, uint32 Opcode) static void PPCInterpreter_STHX(PPCInterpreter_t* hCPU, uint32 Opcode) { - int rA, rS, rB; + sint32 rA, rS, rB; PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); ppcItpCtrl::ppcMem_writeDataU16(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], (uint16)hCPU->gpr[rS]); PPCInterpreter_nextInstruction(hCPU); @@ -164,7 +164,7 @@ static void PPCInterpreter_STHX(PPCInterpreter_t* hCPU, uint32 Opcode) static void PPCInterpreter_STHUX(PPCInterpreter_t* hCPU, uint32 Opcode) { - int rA, rS, rB; + sint32 rA, rS, rB; PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); ppcItpCtrl::ppcMem_writeDataU16(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], (uint16)hCPU->gpr[rS]); if (rA) @@ -174,7 +174,7 @@ static void PPCInterpreter_STHUX(PPCInterpreter_t* hCPU, uint32 Opcode) static void PPCInterpreter_STHBRX(PPCInterpreter_t* hCPU, uint32 Opcode) { - int rA, rS, rB; + sint32 rA, rS, rB; PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); ppcItpCtrl::ppcMem_writeDataU16(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], _swapEndianU16((uint16)hCPU->gpr[rS])); PPCInterpreter_nextInstruction(hCPU); @@ -182,7 +182,7 @@ static void PPCInterpreter_STHBRX(PPCInterpreter_t* hCPU, uint32 Opcode) static void PPCInterpreter_STB(PPCInterpreter_t* hCPU, uint32 Opcode) { - int rA, rS; + sint32 rA, rS; uint32 imm; PPC_OPC_TEMPL_D_SImm(Opcode, rS, rA, imm); ppcItpCtrl::ppcMem_writeDataU8(hCPU, (rA ? hCPU->gpr[rA] : 0) + imm, (uint8)hCPU->gpr[rS]); @@ -191,7 +191,7 @@ static void PPCInterpreter_STB(PPCInterpreter_t* hCPU, uint32 Opcode) static void PPCInterpreter_STBU(PPCInterpreter_t* hCPU, uint32 Opcode) { - int rA, rS; + sint32 rA, rS; uint32 imm; PPC_OPC_TEMPL_D_SImm(Opcode, rS, rA, imm); ppcItpCtrl::ppcMem_writeDataU8(hCPU, hCPU->gpr[rA] + imm, (uint8)hCPU->gpr[rS]); @@ -201,7 +201,7 @@ static void PPCInterpreter_STBU(PPCInterpreter_t* hCPU, uint32 Opcode) static void PPCInterpreter_STBX(PPCInterpreter_t* hCPU, uint32 Opcode) { - int rA, rS, rB; + sint32 rA, rS, rB; PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); ppcItpCtrl::ppcMem_writeDataU8(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], (uint8)hCPU->gpr[rS]); PPCInterpreter_nextInstruction(hCPU); @@ -209,7 +209,7 @@ static void PPCInterpreter_STBX(PPCInterpreter_t* hCPU, uint32 Opcode) static void PPCInterpreter_STBUX(PPCInterpreter_t* hCPU, uint32 Opcode) { - int rA, rS, rB; + sint32 rA, rS, rB; PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); ppcItpCtrl::ppcMem_writeDataU8(hCPU, (rA ? hCPU->gpr[rA] : 0) + hCPU->gpr[rB], (uint8)hCPU->gpr[rS]); if (rA) @@ -219,7 +219,7 @@ static void PPCInterpreter_STBUX(PPCInterpreter_t* hCPU, uint32 Opcode) static void PPCInterpreter_STSWI(PPCInterpreter_t* hCPU, uint32 Opcode) { - int rA, rS, nb; + sint32 rA, rS, nb; PPC_OPC_TEMPL_X(Opcode, rS, rA, nb); if (nb == 0) nb = 32; uint32 ea = rA ? hCPU->gpr[rA] : 0; @@ -229,7 +229,39 @@ static void PPCInterpreter_STSWI(PPCInterpreter_t* hCPU, uint32 Opcode) { if (i == 0) { - r = hCPU->gpr[rS]; + r = rS < 32 ? hCPU->gpr[rS] : 0; // what happens if rS is out of bounds? + rS++; + rS %= 32; + i = 4; + } + ppcItpCtrl::ppcMem_writeDataU8(hCPU, ea, (r >> 24)); + r <<= 8; + ea++; + i--; + nb--; + } + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_STSWX(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + sint32 rA, rS, rB; + PPC_OPC_TEMPL_X(Opcode, rS, rA, rB); + sint32 nb = hCPU->spr.XER&0x7F; + if (nb == 0) + { + PPCInterpreter_nextInstruction(hCPU); + return; + } + uint32 ea = rA ? hCPU->gpr[rA] : 0; + ea += hCPU->gpr[rB]; + uint32 r = 0; + int i = 0; + while (nb > 0) + { + if (i == 0) + { + r = rS < 32 ? hCPU->gpr[rS] : 0; // what happens if rS is out of bounds? rS++; rS %= 32; i = 4; @@ -460,7 +492,6 @@ static void PPCInterpreter_LSWI(PPCInterpreter_t* hCPU, uint32 Opcode) PPC_OPC_TEMPL_X(Opcode, rD, rA, nb); if (nb == 0) nb = 32; - uint32 ea = rA ? hCPU->gpr[rA] : 0; uint32 r = 0; int i = 4; @@ -470,7 +501,8 @@ static void PPCInterpreter_LSWI(PPCInterpreter_t* hCPU, uint32 Opcode) if (i == 0) { i = 4; - hCPU->gpr[rD] = r; + if(rD < 32) + hCPU->gpr[rD] = r; rD++; rD %= 32; r = 0; @@ -487,7 +519,52 @@ static void PPCInterpreter_LSWI(PPCInterpreter_t* hCPU, uint32 Opcode) r <<= 8; i--; } - hCPU->gpr[rD] = r; + if(rD < 32) + hCPU->gpr[rD] = r; + PPCInterpreter_nextInstruction(hCPU); +} + +static void PPCInterpreter_LSWX(PPCInterpreter_t* hCPU, uint32 Opcode) +{ + sint32 rA, rD, rB; + PPC_OPC_TEMPL_X(Opcode, rD, rA, rB); + // byte count comes from XER + uint32 nb = (hCPU->spr.XER>>0)&0x7F; + if (nb == 0) + { + PPCInterpreter_nextInstruction(hCPU); + return; // no-op + } + uint32 ea = rA ? hCPU->gpr[rA] : 0; + ea += hCPU->gpr[rB]; + uint32 r = 0; + int i = 4; + uint8 v; + while (nb>0) + { + if (i == 0) + { + i = 4; + if(rD < 32) + hCPU->gpr[rD] = r; + rD++; + rD %= 32; + r = 0; + } + v = ppcItpCtrl::ppcMem_readDataU8(hCPU, ea); + r <<= 8; + r |= v; + ea++; + i--; + nb--; + } + while (i) + { + r <<= 8; + i--; + } + if(rD < 32) + hCPU->gpr[rD] = r; PPCInterpreter_nextInstruction(hCPU); } diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterMain.cpp b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterMain.cpp index 08d6765a..4449f135 100644 --- a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterMain.cpp +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterMain.cpp @@ -77,7 +77,8 @@ uint32 PPCInterpreter_getXER(PPCInterpreter_t* hCPU) void PPCInterpreter_setXER(PPCInterpreter_t* hCPU, uint32 v) { - hCPU->spr.XER = v; + const uint32 XER_MASK = 0xE0FFFFFF; // some bits are masked out. Figure out which ones exactly + hCPU->spr.XER = v & XER_MASK; hCPU->xer_ca = (v >> XER_BIT_CA) & 1; hCPU->xer_so = (v >> XER_BIT_SO) & 1; hCPU->xer_ov = (v >> XER_BIT_OV) & 1; diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterOPC.cpp b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterOPC.cpp index d6b643ee..7809a01d 100644 --- a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterOPC.cpp +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterOPC.cpp @@ -93,7 +93,6 @@ void PPCInterpreter_MTCRF(PPCInterpreter_t* hCPU, uint32 Opcode) { // frequently used by GCC compiled code (e.g. SM64 port) // tested - uint32 rS; uint32 crfMask; PPC_OPC_TEMPL_XFX(Opcode, rS, crfMask); diff --git a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterOPC.hpp b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterOPC.hpp index 718162be..9bfcd53d 100644 --- a/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterOPC.hpp +++ b/src/Cafe/HW/Espresso/Interpreter/PPCInterpreterOPC.hpp @@ -68,6 +68,8 @@ static void PPCInterpreter_TW(PPCInterpreter_t* hCPU, uint32 opcode) PPC_OPC_TEMPL_X(opcode, to, rA, rB); cemu_assert_debug(to == 0); + if(to != 0) + PPCInterpreter_nextInstruction(hCPU); if (rA == DEBUGGER_BP_T_DEBUGGER) debugger_enterTW(hCPU); diff --git a/src/Cafe/OS/libs/coreinit/coreinit_OSScreen.cpp b/src/Cafe/OS/libs/coreinit/coreinit_OSScreen.cpp index 371ead3c..7b51629e 100644 --- a/src/Cafe/OS/libs/coreinit/coreinit_OSScreen.cpp +++ b/src/Cafe/OS/libs/coreinit/coreinit_OSScreen.cpp @@ -96,7 +96,6 @@ namespace coreinit { ppcDefineParamU32(screenIndex, 0); cemu_assert(screenIndex < 2); - cemuLog_logDebug(LogType::Force, "OSScreenFlipBuffersEx {}", screenIndex); LatteGPUState.osScreen.screen[screenIndex].flipRequestCount++; _updateCurrentDrawScreen(screenIndex); osLib_returnFromFunction(hCPU, 0); From 13ccf9a160f38eff5d0b85db28625a6dd38b191d Mon Sep 17 00:00:00 2001 From: Exzap <13877693+Exzap@users.noreply.github.com> Date: Sat, 28 Jun 2025 21:43:40 +0200 Subject: [PATCH 295/299] MMU: Fix bit width for 32bit MMIO reads This resolves the ghost input issue in N64 virtual console --- src/Cafe/HW/MMU/MMU.cpp | 2 +- src/Cafe/HW/MMU/MMU.h | 2 +- src/Cafe/HW/SI/SI.cpp | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Cafe/HW/MMU/MMU.cpp b/src/Cafe/HW/MMU/MMU.cpp index 04ee8877..f88c3d0d 100644 --- a/src/Cafe/HW/MMU/MMU.cpp +++ b/src/Cafe/HW/MMU/MMU.cpp @@ -500,7 +500,7 @@ namespace MMU // todo - instead of passing the physical address to Read/WriteMMIO we should pass an interface id and a relative address? This would allow remapping the hardware address (tho we can just unregister + register at different addresses) - uint16 ReadMMIO_32(PAddr address) + uint32 ReadMMIO_32(PAddr address) { cemu_assert_debug((address & 0x3) == 0); auto itr = g_mmioHandlerR32->find(address); diff --git a/src/Cafe/HW/MMU/MMU.h b/src/Cafe/HW/MMU/MMU.h index 794785fa..a8367f88 100644 --- a/src/Cafe/HW/MMU/MMU.h +++ b/src/Cafe/HW/MMU/MMU.h @@ -261,7 +261,7 @@ namespace MMU void WriteMMIO_32(PAddr address, uint32 value); void WriteMMIO_16(PAddr address, uint16 value); - uint16 ReadMMIO_32(PAddr address); + uint32 ReadMMIO_32(PAddr address); uint16 ReadMMIO_16(PAddr address); } diff --git a/src/Cafe/HW/SI/SI.cpp b/src/Cafe/HW/SI/SI.cpp index 16cfb894..026543d8 100644 --- a/src/Cafe/HW/SI/SI.cpp +++ b/src/Cafe/HW/SI/SI.cpp @@ -87,7 +87,6 @@ namespace HW_SI HWREG::SICOMCSR SI_COMCSR_R32(PAddr addr) { - //cemuLog_logDebug(LogType::Force, "Read SICOMCSR"); return g_si.registerState.sicomcsr; } From 9fb3c76b761d141045914cc907b0b805822c5500 Mon Sep 17 00:00:00 2001 From: Colin Kinloch <colin@kinlo.ch> Date: Sun, 29 Jun 2025 18:36:22 +0100 Subject: [PATCH 296/299] UI: Include wx button header for wxWidgets 3.3 compatibility (#1621) --- src/gui/GettingStartedDialog.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/GettingStartedDialog.cpp b/src/gui/GettingStartedDialog.cpp index 22426cf2..b613c38c 100644 --- a/src/gui/GettingStartedDialog.cpp +++ b/src/gui/GettingStartedDialog.cpp @@ -5,6 +5,7 @@ #include <wx/statbox.h> #include <wx/msgdlg.h> #include <wx/radiobox.h> +#include <wx/button.h> #include "config/ActiveSettings.h" #include "gui/CemuApp.h" From 6c392d5a22a827437de7dbab11c47d90d0b443cf Mon Sep 17 00:00:00 2001 From: oltolm <oleg.tolmatcev@gmail.com> Date: Mon, 30 Jun 2025 00:15:23 +0200 Subject: [PATCH 297/299] UI: Fix assertions (#1623) --- src/gui/DownloadGraphicPacksWindow.cpp | 6 +++--- src/gui/input/panels/InputPanel.cpp | 7 +------ 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/gui/DownloadGraphicPacksWindow.cpp b/src/gui/DownloadGraphicPacksWindow.cpp index 9ea9e1dd..f2a90959 100644 --- a/src/gui/DownloadGraphicPacksWindow.cpp +++ b/src/gui/DownloadGraphicPacksWindow.cpp @@ -182,14 +182,14 @@ void DownloadGraphicPacksWindow::UpdateThread() if (checkGraphicPackDownloadedVersion(assetName, hasVersionFile)) { // already up to date - wxMessageBox(_("No updates available."), _("Graphic packs"), wxOK | wxCENTRE, this->GetParent()); + wxMessageBox(_("No updates available."), _("Graphic packs"), wxOK | wxCENTRE, this); 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) + if (wxMessageBox(_("Updated graphic packs are available. Do you want to download and install them?"), _("Graphic packs"), wxYES_NO, this) != wxYES) { // cancel update m_threadState = ThreadFinished; @@ -336,7 +336,7 @@ int DownloadGraphicPacksWindow::ShowModal() { if(CafeSystem::IsTitleRunning()) { - wxMessageBox(_("Graphic packs cannot be updated while a game is running."), _("Graphic packs"), 5, this->GetParent()); + wxMessageBox(_("Graphic packs cannot be updated while a game is running."), _("Graphic packs"), 5, this); return wxID_CANCEL; } m_thread = std::thread(&DownloadGraphicPacksWindow::UpdateThread, this); diff --git a/src/gui/input/panels/InputPanel.cpp b/src/gui/input/panels/InputPanel.cpp index 514461fd..941bbd66 100644 --- a/src/gui/input/panels/InputPanel.cpp +++ b/src/gui/input/panels/InputPanel.cpp @@ -174,12 +174,7 @@ void InputPanel::load_controller(const EmulatedControllerPtr& controller) 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 + text->ChangeValue(button_name); } } From 35ecfa3f5478a94791b17e8156831fa312ad9cd1 Mon Sep 17 00:00:00 2001 From: qurious-pixel <62252937+qurious-pixel@users.noreply.github.com> Date: Mon, 30 Jun 2025 20:00:11 -0700 Subject: [PATCH 298/299] build: Fix glslang dependency for Fedora 42 (#1622) --- src/Cafe/CMakeLists.txt | 2 +- vcpkg.json | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index 64baa337..2900059b 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -552,7 +552,7 @@ target_include_directories(CemuCafe PUBLIC "../") if (glslang_VERSION VERSION_LESS "15.0.0") set(glslang_target "glslang::SPIRV") else() - set(glslang_target "glslang") + set(glslang_target "glslang::glslang") endif() target_link_libraries(CemuCafe PRIVATE diff --git a/vcpkg.json b/vcpkg.json index 0a46e32e..c746e00c 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -57,6 +57,10 @@ "libusb" ], "overrides": [ + { + "name": "glslang", + "version": "15.1.0" + }, { "name": "sdl2", "version": "2.30.3" From e68c31e5fbc089fddb25b60d51969f12ba909ce0 Mon Sep 17 00:00:00 2001 From: goeiecool9999 <7033575+goeiecool9999@users.noreply.github.com> Date: Wed, 2 Jul 2025 12:41:04 +0200 Subject: [PATCH 299/299] Fix path text encoding creating shortcuts on windows also fix a memory leak (hopefully) fixes: #1627 --- src/gui/components/wxGameList.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/gui/components/wxGameList.cpp b/src/gui/components/wxGameList.cpp index 587374f6..e30b16f5 100644 --- a/src/gui/components/wxGameList.cpp +++ b/src/gui/components/wxGameList.cpp @@ -1589,9 +1589,11 @@ void wxGameList::CreateShortcut(GameInfo2& gameInfo) PWSTR userShortcutFolder; SHGetKnownFolderPath(FOLDERID_Programs, 0, NULL, &userShortcutFolder); const wxString shortcutName = wxString::Format("%s.lnk", titleName); - wxFileDialog shortcutDialog(this, _("Choose shortcut location"), _pathToUtf8(userShortcutFolder), shortcutName, + wxFileDialog shortcutDialog(this, _("Choose shortcut location"), userShortcutFolder, shortcutName, "Shortcut (*.lnk)|*.lnk", wxFD_SAVE | wxFD_CHANGE_DIR | wxFD_OVERWRITE_PROMPT); + CoTaskMemFree(userShortcutFolder); + const auto result = shortcutDialog.ShowModal(); if (result == wxID_CANCEL) return; @@ -1621,7 +1623,7 @@ void wxGameList::CreateShortcut(GameInfo2& gameInfo) } icon_path = folder / fmt::format("{:016x}.ico", titleId); - auto stream = wxFileOutputStream(_pathToUtf8(*icon_path)); + auto stream = wxFileOutputStream(icon_path->wstring()); auto image = bitmap.ConvertToImage(); wxICOHandler icohandler{}; if (!icohandler.SaveFile(&image, stream, false))