diff --git a/CMakeLists.txt b/CMakeLists.txt index 11b292973e..70ae3ef945 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -84,8 +84,8 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Android") set(ANDROID ON) endif() -# We only support Vulkan on Unix, Android and Windows. -if(ANDROID OR WIN32 OR (UNIX AND NOT APPLE AND NOT ARM_NO_VULKAN)) +# We only support Vulkan on Unix, macOS (by MoltenVK), Android and Windows. +if(ANDROID OR WIN32 OR (UNIX AND NOT ARM_NO_VULKAN)) set(VULKAN ON) endif() @@ -188,6 +188,7 @@ include(FindThreads) if(APPLE) find_library(COCOA_LIBRARY Cocoa) + find_library(QUARTZ_CORE_LIBRARY QuartzCore) endif() include_directories("${CMAKE_SOURCE_DIR}") @@ -717,7 +718,7 @@ if(ANDROID) Common/GL/GLInterfaceBase.h ) - set(nativeExtra ${nativeExtra} + set(NativeAppSource ${NativeAppSource} ext/native/base/NativeApp.h android/jni/app-android.cpp android/jni/AndroidEGLContext.cpp @@ -744,6 +745,10 @@ elseif(IOS) ios/ViewController.h ios/iOSCoreAudio.mm ios/iOSCoreAudio.h + ios/CameraHelper.mm + ios/CameraHelper.h + ios/LocationHelper.mm + ios/LocationHelper.h ios/PPSSPPUIApplication.h ios/PPSSPPUIApplication.mm ios/SmartKeyboardMap.cpp @@ -754,7 +759,7 @@ elseif(IOS) ios/iCade/iCadeReaderView.m ios/iCade/iCadeState.h ) - set(nativeExtraLibs ${nativeExtraLibs} "-framework Foundation -framework MediaPlayer -framework AudioToolbox -framework CoreGraphics -framework QuartzCore -framework UIKit -framework GLKit -framework OpenAL -framework AVFoundation") + set(nativeExtraLibs ${nativeExtraLibs} "-framework Foundation -framework MediaPlayer -framework AudioToolbox -framework CoreGraphics -framework QuartzCore -framework UIKit -framework GLKit -framework OpenAL -framework AVFoundation -framework CoreLocation -framework CoreVideo -framework CoreMedia" ) if(EXISTS "${CMAKE_IOS_SDK_ROOT}/System/Library/Frameworks/GameController.framework") set(nativeExtraLibs ${nativeExtraLibs} "-weak_framework GameController") endif() @@ -769,15 +774,13 @@ elseif(IOS) set_source_files_properties(ios/PPSSPPUIApplication.mm PROPERTIES COMPILE_FLAGS -fobjc-arc) set_source_files_properties(ios/iCade/iCadeReaderView.m PROPERTIES COMPILE_FLAGS -fobjc-arc) set_source_files_properties(ios/main.mm PROPERTIES COMPILE_FLAGS -fobjc-arc) - + set_source_files_properties(ios/CameraHelper.mm PROPERTIES COMPILE_FLAGS -fobjc-arc) + set_source_files_properties(ios/LocationHelper.mm PROPERTIES COMPILE_FLAGS -fobjc-arc) set(TargetBin PPSSPP) elseif(USING_QT_UI) set(CMAKE_AUTOMOC ON) - find_package(Qt5 COMPONENTS OpenGL Gui Core) - if(NOT SDL2_FOUND) - find_package(Qt5 COMPONENTS Multimedia) - endif(NOT SDL2_FOUND) + find_package(Qt5 COMPONENTS OpenGL Gui Core Multimedia) list(APPEND NativeAppSource Qt/QtMain.cpp Qt/QtMain.h @@ -791,7 +794,7 @@ elseif(USING_QT_UI) add_definitions(-DQT_OPENGL_ES -DQT_OPENGL_ES_2) endif() include_directories(${CMAKE_CURRENT_BINARY_DIR} Qt) - set(nativeExtraLibs ${nativeExtraLibs} Qt5::OpenGL Qt5::Gui Qt5::Core) + set(nativeExtraLibs ${nativeExtraLibs} Qt5::OpenGL Qt5::Gui Qt5::Core Qt5::Multimedia) set(TargetBin PPSSPPQt) # Enable SDL if found @@ -802,8 +805,6 @@ elseif(USING_QT_UI) SDL/SDLJoystick.cpp ) set(nativeExtraLibs ${nativeExtraLibs} SDL2::SDL2) - else(SDL2_FOUND) - set(nativeExtraLibs ${nativeExtraLibs} Qt5::Multimedia) endif() elseif(WIN32) @@ -828,8 +829,8 @@ elseif(TARGET SDL2::SDL2) ) set(nativeExtraLibs ${nativeExtraLibs} SDL2::SDL2) if(APPLE) - set(nativeExtra ${nativeExtra} SDL/SDLMain.h SDL/SDLMain.mm) - set(nativeExtraLibs ${nativeExtraLibs} ${COCOA_LIBRARY}) + set(nativeExtra ${nativeExtra} SDL/SDLMain.h SDL/SDLMain.mm SDL/SDLCocoaMetalLayer.h SDL/SDLCocoaMetalLayer.mm) + set(nativeExtraLibs ${nativeExtraLibs} ${COCOA_LIBRARY} ${QUARTZ_CORE_LIBRARY}) elseif(USING_EGL) set(nativeExtraLibs ${nativeExtraLibs} pthread) endif() @@ -879,7 +880,10 @@ if(ANDROID) elseif(ARM64) set(NativeAppSource ${NativeAppSource} android/jni/Arm64EmitterTest.cpp) endif() - set(nativeExtra ${nativeExtra} ${NativeAppSource}) + + if (NOT LIBRETRO) + set(nativeExtra ${nativeExtra} ${NativeAppSource}) + endif() endif() set(THIN3D_PLATFORMS ext/native/thin3d/thin3d_gl.cpp @@ -1668,6 +1672,8 @@ add_library(${CoreLibName} ${CoreLinkType} Core/HW/SimpleAudioDec.h Core/HW/AsyncIOManager.cpp Core/HW/AsyncIOManager.h + Core/HW/Camera.cpp + Core/HW/Camera.h Core/HW/MediaEngine.cpp Core/HW/MediaEngine.h Core/HW/MpegDemux.cpp @@ -1864,6 +1870,9 @@ set(WindowsFiles Windows/GEDebugger/GEDebugger.h Windows/GEDebugger/TabDisplayLists.h Windows/GEDebugger/TabVertices.h + Windows/BufferLock.h + Windows/CaptureDevice.cpp + Windows/CaptureDevice.h Windows/DinputDevice.cpp Windows/DinputDevice.h Windows/DSoundStream.cpp @@ -1917,7 +1926,7 @@ set(WindowsFiles list(APPEND LinkCommon ${CoreLibName} ${CMAKE_THREAD_LIBS_INIT}) if(WIN32) - list(APPEND LinkCommon kernel32 user32 gdi32 shell32 comctl32 dsound xinput d3d9 winmm dinput8 ole32 winspool ksuser) + list(APPEND LinkCommon kernel32 user32 gdi32 shell32 comctl32 dsound xinput d3d9 winmm dinput8 ole32 winspool ksuser mf mfplat mfreadwrite mfuuid shlwapi) #setup_target_project(${TargetBin} Windows) list(APPEND NativeAppSource ${WindowsFiles}) endif() @@ -1951,7 +1960,7 @@ if(HEADLESS) headless/SDLHeadlessHost.cpp headless/SDLHeadlessHost.h ) - target_link_libraries(PPSSPPHeadless ${COCOA_LIBRARY} ${LinkCommon}) + target_link_libraries(PPSSPPHeadless ${COCOA_LIBRARY} ${QUARTZ_CORE_LIBRARY} ${LinkCommon}) setup_target_project(PPSSPPHeadless headless) endif() @@ -1966,7 +1975,7 @@ if(UNITTEST) Core/MIPS/ARM/ArmRegCache.cpp Core/MIPS/ARM/ArmRegCacheFPU.cpp ) - target_link_libraries(unitTest ${COCOA_LIBRARY} ${LinkCommon} Common) + target_link_libraries(unitTest ${COCOA_LIBRARY} ${QUARTZ_CORE_LIBRARY} ${LinkCommon} Common) setup_target_project(unitTest unittest) endif() @@ -2003,6 +2012,8 @@ if(TargetBin) add_executable(${TargetBin} MACOSX_BUNDLE ${ICON_PATH_ABS} ${NativeAssets} ${SHADER_FILES} ${FLASH0_FILES} ${LANG_FILES} ${NativeAppSource}) if(TARGET SDL2::SDL2 AND NOT USING_QT_UI) add_custom_command(TARGET ${TargetBin} POST_BUILD COMMAND /bin/bash "${CMAKE_SOURCE_DIR}/SDL/macbundle.sh" "${CMAKE_BINARY_DIR}/PPSSPPSDL.app") + elseif(USING_QT_UI) + add_custom_command(TARGET ${TargetBin} POST_BUILD COMMAND /bin/bash "${CMAKE_SOURCE_DIR}/Qt/macbundle.sh" "${CMAKE_BINARY_DIR}/PPSSPPQt.app") endif() endif() elseif(WIN32) diff --git a/Common/FileUtil.cpp b/Common/FileUtil.cpp index 04ae730c1c..d1ad8a5b72 100644 --- a/Common/FileUtil.cpp +++ b/Common/FileUtil.cpp @@ -792,12 +792,23 @@ const std::string &GetExeDirectory() if (ExePath.empty()) { #ifdef _WIN32 - TCHAR program_path[4096] = {0}; - GetModuleFileName(NULL, program_path, ARRAY_SIZE(program_path) - 1); - program_path[ARRAY_SIZE(program_path) - 1] = '\0'; - TCHAR *last_slash = _tcsrchr(program_path, '\\'); - if (last_slash != NULL) - *(last_slash + 1) = '\0'; +#ifdef UNICODE + std::wstring program_path; +#else + std::string program_path; +#endif + size_t sz; + do { + program_path.resize(program_path.size() + MAX_PATH); + // On failure, this will return the same value as passed in, but success will always be one lower. + sz = GetModuleFileName(nullptr, &program_path[0], (DWORD)program_path.size()); + } while (sz >= program_path.size()); + + TCHAR *last_slash = _tcsrchr(&program_path[0], '\\'); + if (last_slash != nullptr) + program_path.resize(last_slash - &program_path[0] + 1); + else + program_path.resize(sz); #ifdef UNICODE ExePath = ConvertWStringToUTF8(program_path); #else diff --git a/Common/Hashmaps.h b/Common/Hashmaps.h index f0a6fe4709..e5e36be02e 100644 --- a/Common/Hashmaps.h +++ b/Common/Hashmaps.h @@ -1,5 +1,6 @@ #pragma once +#include /* uint32_t */ #include #include diff --git a/Common/MemArenaAndroid.cpp b/Common/MemArenaAndroid.cpp index 31eda9d3d0..a0b8614f19 100644 --- a/Common/MemArenaAndroid.cpp +++ b/Common/MemArenaAndroid.cpp @@ -20,6 +20,7 @@ #ifdef __ANDROID__ #include "base/logging.h" +#include "base/NativeApp.h" #include "MemoryUtil.h" #include "MemArena.h" #include "StringUtils.h" @@ -30,6 +31,7 @@ #include #include #include +#include // Hopefully this ABI will never change... @@ -41,9 +43,31 @@ bool MemArena::NeedsProbing() { // ashmem_create_region - creates a new ashmem region and returns the file // descriptor, or <0 on error +// This function is defined in much later version of the ndk, so we can only access it via dlopen(). // `name' is an optional label to give the region (visible in /proc/pid/maps) // `size' is the size of the region, in page-aligned bytes static int ashmem_create_region(const char *name, size_t size) { + static void* handle = dlopen("libandroid.so", RTLD_LAZY | RTLD_LOCAL); + using type_ASharedMemory_create = int(*)(const char *name, size_t size); + static type_ASharedMemory_create function_create = nullptr; + + if (handle != nullptr) { + function_create = + reinterpret_cast(dlsym(handle, "ASharedMemory_create")); + } + + if (function_create != nullptr) { + return function_create(name, size); + } else { + return -1; + } +} + +// legacy_ashmem_create_region - creates a new ashmem region and returns the file +// descriptor, or <0 on error +// `name' is an optional label to give the region (visible in /proc/pid/maps) +// `size' is the size of the region, in page-aligned bytes +static int legacy_ashmem_create_region(const char *name, size_t size) { int fd = open(ASHMEM_DEVICE, O_RDWR); if (fd < 0) return fd; @@ -76,7 +100,14 @@ size_t MemArena::roundup(size_t x) { void MemArena::GrabLowMemSpace(size_t size) { // Use ashmem so we don't have to allocate a file on disk! - fd = ashmem_create_region("PPSSPP_RAM", size); + const char* name = "PPSSPP_RAM"; + + // Since version 26 Android provides a new api for accessing SharedMemory. + if (System_GetPropertyInt(SYSPROP_SYSTEMVERSION) >= 26) { + fd = ashmem_create_region(name, size); + } else { + fd = legacy_ashmem_create_region(name, size); + } // Note that it appears that ashmem is pinned by default, so no need to pin. if (fd < 0) { ERROR_LOG(MEMMAP, "Failed to grab ashmem space of size: %08x errno: %d", (int)size, (int)(errno)); diff --git a/Common/OSVersion.cpp b/Common/OSVersion.cpp index f66c441365..adc2f1d310 100644 --- a/Common/OSVersion.cpp +++ b/Common/OSVersion.cpp @@ -95,6 +95,25 @@ bool IsVistaOrHigher() { #endif } +bool IsWin7OrHigher() { +#if PPSSPP_PLATFORM(UWP) + return true; +#else + OSVERSIONINFOEX osvi; + DWORDLONG dwlConditionMask = 0; + int op = VER_GREATER_EQUAL; + ZeroMemory(&osvi, sizeof(osvi)); + osvi.dwOSVersionInfoSize = sizeof(osvi); + osvi.dwMajorVersion = 6; // Win7 is 6.1 + osvi.dwMinorVersion = 1; + + VER_SET_CONDITION(dwlConditionMask, VER_MAJORVERSION, op); + VER_SET_CONDITION(dwlConditionMask, VER_MINORVERSION, op); + + return VerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_MINORVERSION, dwlConditionMask) != FALSE; +#endif +} + std::string GetWindowsVersion() { const bool IsWindowsXPSP2 = DoesVersionMatchWindows(5, 1, 2, 0, false); const bool IsWindowsXPSP3 = DoesVersionMatchWindows(5, 1, 3, 0, false); diff --git a/Common/OSVersion.h b/Common/OSVersion.h index 1f441e412c..262cba05ac 100644 --- a/Common/OSVersion.h +++ b/Common/OSVersion.h @@ -5,6 +5,7 @@ #ifdef _WIN32 bool IsVistaOrHigher(); +bool IsWin7OrHigher(); bool DoesVersionMatchWindows(uint32_t major, uint32_t minor, uint32_t spMajor, uint32_t spMinor, bool acceptGreater); std::string GetWindowsVersion(); std::string GetWindowsSystemArchitecture(); diff --git a/Common/Vulkan/VulkanContext.cpp b/Common/Vulkan/VulkanContext.cpp index be07dd66c6..cb4dc0b33a 100644 --- a/Common/Vulkan/VulkanContext.cpp +++ b/Common/Vulkan/VulkanContext.cpp @@ -125,6 +125,11 @@ VkResult VulkanContext::CreateInstance(const CreateInfo &info) { instance_extensions_enabled_.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME); } #endif +#if defined(VK_USE_PLATFORM_METAL_EXT) + if (IsInstanceExtensionAvailable(VK_EXT_METAL_SURFACE_EXTENSION_NAME)) { + instance_extensions_enabled_.push_back(VK_EXT_METAL_SURFACE_EXTENSION_NAME); + } +#endif #endif if (flags_ & VULKAN_FLAG_VALIDATE) { @@ -760,6 +765,16 @@ VkResult VulkanContext::ReinitSurface() { return vkCreateAndroidSurfaceKHR(instance_, &android, nullptr, &surface_); } #endif +#if defined(VK_USE_PLATFORM_METAL_EXT) + case WINDOWSYSTEM_METAL_EXT: + { + VkMetalSurfaceCreateInfoEXT metal{ VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT }; + metal.flags = 0; + metal.pLayer = winsysData1_; + metal.pNext = winsysData2_; + return vkCreateMetalSurfaceEXT(instance_, &metal, nullptr, &surface_); + } +#endif #if defined(VK_USE_PLATFORM_XLIB_KHR) case WINDOWSYSTEM_XLIB: { @@ -968,10 +983,10 @@ bool VulkanContext::InitSwapchain() { // Application must settle for fewer images than desired: desiredNumberOfSwapChainImages = surfCapabilities_.maxImageCount; } - + // We mostly follow the practices from // https://arm-software.github.io/vulkan_best_practice_for_mobile_developers/samples/surface_rotation/surface_rotation_tutorial.html - // + // VkSurfaceTransformFlagBitsKHR preTransform; std::string supportedTransforms = surface_transforms_to_string(surfCapabilities_.supportedTransforms); std::string currentTransform = surface_transforms_to_string(surfCapabilities_.currentTransform); diff --git a/Common/Vulkan/VulkanContext.h b/Common/Vulkan/VulkanContext.h index 63fa5108e7..fccc3dc98e 100644 --- a/Common/Vulkan/VulkanContext.h +++ b/Common/Vulkan/VulkanContext.h @@ -34,6 +34,9 @@ enum WindowSystem { #ifdef __ANDROID__ WINDOWSYSTEM_ANDROID, #endif +#ifdef VK_USE_PLATFORM_METAL_EXT + WINDOWSYSTEM_METAL_EXT, +#endif #ifdef VK_USE_PLATFORM_XLIB_KHR WINDOWSYSTEM_XLIB, #endif diff --git a/Common/Vulkan/VulkanLoader.cpp b/Common/Vulkan/VulkanLoader.cpp index 93aff18cec..a523ab4968 100644 --- a/Common/Vulkan/VulkanLoader.cpp +++ b/Common/Vulkan/VulkanLoader.cpp @@ -176,6 +176,9 @@ PFN_vkCreateAndroidSurfaceKHR vkCreateAndroidSurfaceKHR; #elif defined(_WIN32) PFN_vkCreateWin32SurfaceKHR vkCreateWin32SurfaceKHR; #endif +#if defined(VK_USE_PLATFORM_METAL_EXT) +PFN_vkCreateMetalSurfaceEXT vkCreateMetalSurfaceEXT; +#endif #if defined(VK_USE_PLATFORM_XLIB_KHR) PFN_vkCreateXlibSurfaceKHR vkCreateXlibSurfaceKHR; #endif @@ -236,10 +239,14 @@ static const char *device_name_blacklist[] = { }; static const char *so_names[] = { +#if defined(__APPLE__) + "libMoltenVK.dylib", +#else "libvulkan.so", #if !defined(__ANDROID__) "libvulkan.so.1", #endif +#endif }; void VulkanSetAvailable(bool available) { @@ -310,6 +317,8 @@ bool VulkanMayBeAvailable() { const char * const platformSurfaceExtension = VK_KHR_WIN32_SURFACE_EXTENSION_NAME; #elif defined(__ANDROID__) const char *platformSurfaceExtension = VK_KHR_ANDROID_SURFACE_EXTENSION_NAME; +#elif defined(VK_USE_PLATFORM_METAL_EXT) + const char * const platformSurfaceExtension = VK_EXT_METAL_SURFACE_EXTENSION_NAME; #else const char *platformSurfaceExtension = 0; #endif @@ -515,6 +524,8 @@ void VulkanLoadInstanceFunctions(VkInstance instance, const VulkanDeviceExtensio LOAD_INSTANCE_FUNC(instance, vkCreateWin32SurfaceKHR); #elif defined(__ANDROID__) LOAD_INSTANCE_FUNC(instance, vkCreateAndroidSurfaceKHR); +#elif defined(VK_USE_PLATFORM_METAL_EXT) + LOAD_INSTANCE_FUNC(instance, vkCreateMetalSurfaceEXT); #endif #if defined(VK_USE_PLATFORM_XLIB_KHR) LOAD_INSTANCE_FUNC(instance, vkCreateXlibSurfaceKHR); diff --git a/Common/Vulkan/VulkanLoader.h b/Common/Vulkan/VulkanLoader.h index 3d1ccae86b..99b560cfa6 100644 --- a/Common/Vulkan/VulkanLoader.h +++ b/Common/Vulkan/VulkanLoader.h @@ -25,6 +25,8 @@ #ifndef NOMINMAX #define NOMINMAX #endif +#elif defined(__APPLE__) +#define VK_USE_PLATFORM_METAL_EXT #endif #define VK_NO_PROTOTYPES @@ -173,6 +175,8 @@ extern PFN_vkCmdExecuteCommands vkCmdExecuteCommands; extern PFN_vkCreateAndroidSurfaceKHR vkCreateAndroidSurfaceKHR; #elif defined(_WIN32) extern PFN_vkCreateWin32SurfaceKHR vkCreateWin32SurfaceKHR; +#elif defined(VK_USE_PLATFORM_METAL_EXT) +extern PFN_vkCreateMetalSurfaceEXT vkCreateMetalSurfaceEXT; #endif #if defined(VK_USE_PLATFORM_XLIB_KHR) extern PFN_vkCreateXlibSurfaceKHR vkCreateXlibSurfaceKHR; diff --git a/Core/Config.cpp b/Core/Config.cpp index 4d09454d5f..30d765404b 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -718,6 +718,7 @@ static ConfigSetting graphicsSettings[] = { #ifdef _WIN32 ConfigSetting("D3D11Device", &g_Config.sD3D11Device, "", true, false), #endif + ConfigSetting("CameraDevice", &g_Config.sCameraDevice, "", true, false), ConfigSetting("VendorBugChecksEnabled", &g_Config.bVendorBugChecksEnabled, true, false, false), ReportedConfigSetting("RenderingMode", &g_Config.iRenderingMode, 1, true, true), ConfigSetting("SoftwareRenderer", &g_Config.bSoftwareRendering, false, true, true), @@ -896,6 +897,7 @@ static ConfigSetting controlSettings[] = { ConfigSetting("fcombo4X", "fcombo4Y", "comboKeyScale4", "ShowComboKey4", &g_Config.touchCombo4, defaultTouchPosHide, true, true), ConfigSetting("Speed1KeyX", "Speed1KeyY", "Speed1KeyScale", "ShowSpeed1Key", &g_Config.touchSpeed1Key, defaultTouchPosHide, true, true), ConfigSetting("Speed2KeyX", "Speed2KeyY", "Speed2KeyScale", "ShowSpeed2Key", &g_Config.touchSpeed2Key, defaultTouchPosHide, true, true), + ConfigSetting("RapidFireKeyX", "RapidFireKeyY", "RapidFireKeyScale", "ShowRapidFireKey", &g_Config.touchRapidFireKey, defaultTouchPosHide, true, true), #ifdef _WIN32 ConfigSetting("DInputAnalogDeadzone", &g_Config.fDInputAnalogDeadzone, 0.1f, true, true), @@ -998,6 +1000,7 @@ static ConfigSetting debuggerSettings[] = { ConfigSetting("ShowGpuProfile", &g_Config.bShowGpuProfile, false, false), ConfigSetting("SkipDeadbeefFilling", &g_Config.bSkipDeadbeefFilling, false), ConfigSetting("FuncHashMap", &g_Config.bFuncHashMap, false), + ConfigSetting("DrawFrameGraph", &g_Config.bDrawFrameGraph, false), ConfigSetting(false), }; @@ -1620,6 +1623,7 @@ void Config::ResetControlLayout() { reset(g_Config.touchCombo4); reset(g_Config.touchSpeed1Key); reset(g_Config.touchSpeed2Key); + reset(g_Config.touchRapidFireKey); } void Config::GetReportingInfo(UrlEncoder &data) { diff --git a/Core/Config.h b/Core/Config.h index e33eb7ad1d..0c08473b07 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -135,6 +135,7 @@ public: // If not set, will use the "best" device. std::string sVulkanDevice; std::string sD3D11Device; // Windows only + std::string sCameraDevice; bool bSoftwareRendering; bool bHardwareTransform; // only used in the GLES backend @@ -317,6 +318,7 @@ public: ConfigTouchPos touchCombo4; ConfigTouchPos touchSpeed1Key; ConfigTouchPos touchSpeed2Key; + ConfigTouchPos touchRapidFireKey; // Controls Visibility bool bShowTouchControls; @@ -416,6 +418,7 @@ public: // Double edged sword: much easier debugging, but not accurate. bool bSkipDeadbeefFilling; bool bFuncHashMap; + bool bDrawFrameGraph; // Volatile development settings bool bShowFrameProfiler; diff --git a/Core/Core.cpp b/Core/Core.cpp index 93c6eaf8d8..3f66794576 100644 --- a/Core/Core.cpp +++ b/Core/Core.cpp @@ -150,8 +150,13 @@ static bool IsWindowSmall(int pixelWidth, int pixelHeight) { // TODO: Feels like this belongs elsewhere. bool UpdateScreenScale(int width, int height) { bool smallWindow; -#if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP) - g_dpi = (float)System_GetPropertyInt(SYSPROP_DISPLAY_DPI); +#if defined(USING_QT_UI) + g_dpi = System_GetPropertyFloat(SYSPROP_DISPLAY_DPI); + float g_logical_dpi = System_GetPropertyFloat(SYSPROP_DISPLAY_LOGICAL_DPI); + g_dpi_scale_x = g_logical_dpi / g_dpi; + g_dpi_scale_y = g_logical_dpi / g_dpi; +#elif PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP) + g_dpi = System_GetPropertyFloat(SYSPROP_DISPLAY_DPI); g_dpi_scale_x = 96.0f / g_dpi; g_dpi_scale_y = 96.0f / g_dpi; #else diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 22d258ecad..021cbfb153 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -375,6 +375,7 @@ + @@ -903,6 +904,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 7f096c1b50..d1257aa716 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -740,6 +740,9 @@ HLE\Libraries + + HW + @@ -1373,6 +1376,9 @@ HLE\Libraries + + HW + diff --git a/Core/CwCheat.cpp b/Core/CwCheat.cpp index 8c62c42e3a..886b0acbb1 100644 --- a/Core/CwCheat.cpp +++ b/Core/CwCheat.cpp @@ -340,7 +340,7 @@ void CWCheatEngine::CreateCheatFile() { fclose(f); } if (!File::Exists(activeCheatFile)) { - I18NCategory *err = GetI18NCategory("Error"); + auto err = GetI18NCategory("Error"); host->NotifyUserMessage(err->T("Unable to create cheat file, disk may be full")); } } diff --git a/Core/Dialog/PSPDialog.cpp b/Core/Dialog/PSPDialog.cpp index b6f00f86a3..64b5d6197d 100644 --- a/Core/Dialog/PSPDialog.cpp +++ b/Core/Dialog/PSPDialog.cpp @@ -202,7 +202,7 @@ void PSPDialog::DisplayButtons(int flags, const char *caption) truncate_cpy(safeCaption, caption); } - I18NCategory *di = GetI18NCategory("Dialog"); + auto di = GetI18NCategory("Dialog"); float x1 = 183.5f, x2 = 261.5f; if (GetCommonParam()->buttonSwap == 1) { x1 = 261.5f; diff --git a/Core/Dialog/PSPMsgDialog.cpp b/Core/Dialog/PSPMsgDialog.cpp index ca9e283f3e..7ef30d7691 100755 --- a/Core/Dialog/PSPMsgDialog.cpp +++ b/Core/Dialog/PSPMsgDialog.cpp @@ -156,7 +156,7 @@ void PSPMsgDialog::DisplayMessage(std::string text, bool hasYesNo, bool hasOK) if (hasYesNo) { - I18NCategory *di = GetI18NCategory("Dialog"); + auto di = GetI18NCategory("Dialog"); const char *choiceText; u32 yesColor, noColor; float x, w; @@ -193,7 +193,7 @@ void PSPMsgDialog::DisplayMessage(std::string text, bool hasYesNo, bool hasOK) } if (hasOK) { - I18NCategory *di = GetI18NCategory("Dialog"); + auto di = GetI18NCategory("Dialog"); float x, w; x = 240.0f; w = 15.0f; diff --git a/Core/Dialog/PSPNetconfDialog.cpp b/Core/Dialog/PSPNetconfDialog.cpp index 25c9d42baf..a4f4df8083 100644 --- a/Core/Dialog/PSPNetconfDialog.cpp +++ b/Core/Dialog/PSPNetconfDialog.cpp @@ -62,14 +62,14 @@ void PSPNetconfDialog::DrawBanner() { // TODO: Draw a hexagon icon PPGeDrawImage(10, 6, 12.0f, 12.0f, 1, 10, 1, 10, 10, 10, CalcFadedColor(0xFFFFFFFF)); - I18NCategory *di = GetI18NCategory("Dialog"); + auto di = GetI18NCategory("Dialog"); PPGeDrawText(di->T("Network Connection"), 30, 11, PPGE_ALIGN_VCENTER, 0.6f, CalcFadedColor(0xFFFFFFFF)); } int PSPNetconfDialog::Update(int animSpeed) { UpdateButtons(); - I18NCategory *di = GetI18NCategory("Dialog"); - I18NCategory *err = GetI18NCategory("Error"); + auto di = GetI18NCategory("Dialog"); + auto err = GetI18NCategory("Error"); const float WRAP_WIDTH = 254.0f; const int confirmBtnImage = g_Config.iButtonPreference == PSP_SYSTEMPARAM_BUTTON_CROSS ? I_CROSS : I_CIRCLE; const int confirmBtn = g_Config.iButtonPreference == PSP_SYSTEMPARAM_BUTTON_CROSS ? CTRL_CROSS : CTRL_CIRCLE; diff --git a/Core/Dialog/PSPOskDialog.cpp b/Core/Dialog/PSPOskDialog.cpp index cf09c4db42..b1b7cde8af 100755 --- a/Core/Dialog/PSPOskDialog.cpp +++ b/Core/Dialog/PSPOskDialog.cpp @@ -917,7 +917,7 @@ int PSPOskDialog::Update(int animSpeed) { PPGeDrawRect(0, 0, 480, 272, CalcFadedColor(0x63636363)); RenderKeyboard(); - I18NCategory *di = GetI18NCategory("Dialog"); + auto di = GetI18NCategory("Dialog"); PPGeDrawImage(I_SQUARE, 365, 222, 16, 16, 0, CalcFadedColor(0xFFFFFFFF)); PPGeDrawText(di->T("Space"), 390, 222, PPGE_ALIGN_LEFT, 0.5f, CalcFadedColor(0xFFFFFFFF)); diff --git a/Core/Dialog/PSPSaveDialog.cpp b/Core/Dialog/PSPSaveDialog.cpp index d078382897..e0a27decaf 100755 --- a/Core/Dialog/PSPSaveDialog.cpp +++ b/Core/Dialog/PSPSaveDialog.cpp @@ -280,7 +280,7 @@ const std::string PSPSaveDialog::GetSelectedSaveDirName() const void PSPSaveDialog::DisplayBanner(int which) { - I18NCategory *di = GetI18NCategory("Dialog"); + auto di = GetI18NCategory("Dialog"); PPGeDrawRect(0, 0, 480, 23, CalcFadedColor(0x65636358)); const char *title; switch (which) @@ -394,7 +394,7 @@ void PSPSaveDialog::DisplaySaveDataInfo1() { std::lock_guard guard(paramLock); if (param.GetFileInfo(currentSelectedSave).size == 0) { - I18NCategory *di = GetI18NCategory("Dialog"); + auto di = GetI18NCategory("Dialog"); PPGeDrawText(di->T("NEW DATA"), 180, 136, PPGE_ALIGN_VCENTER, 0.6f, CalcFadedColor(0xFFFFFFFF)); } else { char title[512]; @@ -539,7 +539,7 @@ void PSPSaveDialog::DisplayMessage(std::string text, bool hasYesNo) float h2 = h * (float)n / 2.0f; if (hasYesNo) { - I18NCategory *di = GetI18NCategory("Dialog"); + auto di = GetI18NCategory("Dialog"); const char *choiceText; u32 yesColor, noColor; float x, w; @@ -621,7 +621,7 @@ int PSPSaveDialog::Update(int animSpeed) cancelButtonFlag = CTRL_CIRCLE; } - I18NCategory *di = GetI18NCategory("Dialog"); + auto di = GetI18NCategory("Dialog"); switch (display) { diff --git a/Core/Dialog/SavedataParam.cpp b/Core/Dialog/SavedataParam.cpp index 646a6bd20c..c014d0ca3f 100644 --- a/Core/Dialog/SavedataParam.cpp +++ b/Core/Dialog/SavedataParam.cpp @@ -392,7 +392,7 @@ int SavedataParam::Save(SceUtilitySavedataParam* param, const std::string &saveD if (!pspFileSystem.GetFileInfo(dirPath).exists) { if (!pspFileSystem.MkDir(dirPath)) { - I18NCategory *err = GetI18NCategory("Error"); + auto err = GetI18NCategory("Error"); host->NotifyUserMessage(err->T("Unable to write savedata, disk may be full")); } } @@ -422,7 +422,7 @@ int SavedataParam::Save(SceUtilitySavedataParam* param, const std::string &saveD } if (EncryptData(decryptMode, cryptedData, &cryptedSize, &aligned_len, cryptedHash, (hasKey ? param->key : 0)) != 0) { - I18NCategory *err = GetI18NCategory("Error"); + auto err = GetI18NCategory("Error"); host->NotifyUserMessage(err->T("Save encryption failed. This save won't work on real PSP"), 6.0f); ERROR_LOG(SCEUTILITY,"Save encryption failed. This save won't work on real PSP"); delete[] cryptedData; @@ -689,7 +689,7 @@ void SavedataParam::LoadCryptedSave(SceUtilitySavedataParam *param, u8 *data, co // Don't notify the user if we're not going to upgrade the save. if (!g_Config.bEncryptSave) { - I18NCategory *di = GetI18NCategory("Dialog"); + auto di = GetI18NCategory("Dialog"); host->NotifyUserMessage(di->T("When you save, it will load on a PSP, but not an older PPSSPP"), 6.0f); host->NotifyUserMessage(di->T("Old savedata detected"), 6.0f); } @@ -701,7 +701,7 @@ void SavedataParam::LoadCryptedSave(SceUtilitySavedataParam *param, u8 *data, co } if (g_Config.bSavedataUpgrade) { decryptMode = prevCryptMode; - I18NCategory *di = GetI18NCategory("Dialog"); + auto di = GetI18NCategory("Dialog"); host->NotifyUserMessage(di->T("When you save, it will not work on outdated PSP Firmware anymore"), 6.0f); host->NotifyUserMessage(di->T("Old savedata detected"), 6.0f); } diff --git a/Core/ELF/PBPReader.cpp b/Core/ELF/PBPReader.cpp index 76fdbb36e4..41653bfcba 100644 --- a/Core/ELF/PBPReader.cpp +++ b/Core/ELF/PBPReader.cpp @@ -37,7 +37,7 @@ PBPReader::PBPReader(FileLoader *fileLoader) : file_(nullptr), header_(), isELF_ } if (memcmp(header_.magic, "\0PBP", 4) != 0) { if (memcmp(header_.magic, "\nFLE", 4) != 0) { - DEBUG_LOG(LOADER, "%s: File actually an ELF, not a PBP", fileLoader->Path().c_str()); + VERBOSE_LOG(LOADER, "%s: File actually an ELF, not a PBP", fileLoader->Path().c_str()); isELF_ = true; } else { ERROR_LOG(LOADER, "Magic number in %s indicated no PBP: %s", fileLoader->Path().c_str(), header_.magic); diff --git a/Core/FileSystems/BlockDevices.cpp b/Core/FileSystems/BlockDevices.cpp index f5ac8f9264..0131abdeb1 100644 --- a/Core/FileSystems/BlockDevices.cpp +++ b/Core/FileSystems/BlockDevices.cpp @@ -64,7 +64,7 @@ u32 BlockDevice::CalculateCRC() { } void BlockDevice::NotifyReadError() { - I18NCategory *err = GetI18NCategory("Error"); + auto err = GetI18NCategory("Error"); if (!reportedError_) { host->NotifyUserMessage(err->T("Game disc read error - ISO corrupt"), 6.0f); reportedError_ = true; @@ -134,18 +134,11 @@ CISOFileBlockDevice::CISOFileBlockDevice(FileLoader *fileLoader) CISO_H hdr; size_t readSize = fileLoader->ReadAt(0, sizeof(CISO_H), 1, &hdr); - if (readSize != 1 || memcmp(hdr.magic, "CISO", 4) != 0) - { + if (readSize != 1 || memcmp(hdr.magic, "CISO", 4) != 0) { WARN_LOG(LOADER, "Invalid CSO!"); } - else - { - VERBOSE_LOG(LOADER, "Valid CSO!"); - } - if (hdr.ver > 1) - { - ERROR_LOG(LOADER, "CSO version too high!"); - //ARGH! + if (hdr.ver > 1) { + WARN_LOG(LOADER, "CSO version too high!"); } frameSize = hdr.block_size; @@ -174,10 +167,11 @@ CISOFileBlockDevice::CISOFileBlockDevice(FileLoader *fileLoader) zlibBufferFrame = numFrames; const u32 indexSize = numFrames + 1; + const size_t headerEnd = hdr.ver > 1 ? (size_t)hdr.header_size : sizeof(hdr); #if COMMON_LITTLE_ENDIAN index = new u32[indexSize]; - if (fileLoader->ReadAt(sizeof(hdr), sizeof(u32), indexSize, index) != indexSize) { + if (fileLoader->ReadAt(headerEnd, sizeof(u32), indexSize, index) != indexSize) { NotifyReadError(); memset(index, 0, indexSize * sizeof(u32)); } @@ -185,7 +179,7 @@ CISOFileBlockDevice::CISOFileBlockDevice(FileLoader *fileLoader) index = new u32[indexSize]; u32_le *indexTemp = new u32_le[indexSize]; - if (fileLoader->ReadAt(sizeof(hdr), sizeof(u32), indexSize, indexTemp) != indexSize) { + if (fileLoader->ReadAt(headerEnd, sizeof(u32), indexSize, indexTemp) != indexSize) { NotifyReadError(); memset(indexTemp, 0, indexSize * sizeof(u32_le)); } @@ -196,6 +190,8 @@ CISOFileBlockDevice::CISOFileBlockDevice(FileLoader *fileLoader) delete[] indexTemp; #endif + ver_ = hdr.ver; + // Double check that the CSO is not truncated. In most cases, this will be the exact size. u64 fileSize = fileLoader->FileSize(); u64 lastIndexPos = index[indexSize - 1] & 0x7FFFFFFF; @@ -216,8 +212,7 @@ CISOFileBlockDevice::~CISOFileBlockDevice() bool CISOFileBlockDevice::ReadBlock(int blockNumber, u8 *outPtr, bool uncached) { FileLoader::Flags flags = uncached ? FileLoader::Flags::HINT_UNCACHED : FileLoader::Flags::NONE; - if ((u32)blockNumber >= numBlocks) - { + if ((u32)blockNumber >= numBlocks) { memset(outPtr, 0, GetBlockSize()); return false; } @@ -233,20 +228,19 @@ bool CISOFileBlockDevice::ReadBlock(int blockNumber, u8 *outPtr, bool uncached) const size_t compressedReadSize = (size_t)(compressedReadEnd - compressedReadPos); const u32 compressedOffset = (blockNumber & ((1 << blockShift) - 1)) * GetBlockSize(); - const int plain = idx & 0x80000000; - if (plain) - { + bool plain = (idx & 0x80000000) != 0; + if (ver_ >= 2) { + // CSO v2+ requires blocks be uncompressed if large enough to be. High bit means other things. + plain = compressedReadSize >= frameSize; + } + if (plain) { int readSize = (u32)fileLoader_->ReadAt(compressedReadPos + compressedOffset, 1, GetBlockSize(), outPtr, flags); if (readSize < GetBlockSize()) memset(outPtr + readSize, 0, GetBlockSize() - readSize); - } - else if (zlibBufferFrame == frameNumber) - { + } else if (zlibBufferFrame == frameNumber) { // We already have it. Just apply the offset and copy. memcpy(outPtr, zlibBuffer + compressedOffset, GetBlockSize()); - } - else - { + } else { const u32 readSize = (u32)fileLoader_->ReadAt(compressedReadPos, 1, compressedReadSize, readBuffer, flags); z.zalloc = Z_NULL; diff --git a/Core/FileSystems/BlockDevices.h b/Core/FileSystems/BlockDevices.h index e3b9dc280b..6e17aaa4ea 100644 --- a/Core/FileSystems/BlockDevices.h +++ b/Core/FileSystems/BlockDevices.h @@ -72,6 +72,7 @@ private: u32 frameSize; u32 numBlocks; u32 numFrames; + int ver_; }; diff --git a/Core/FileSystems/DirectoryFileSystem.cpp b/Core/FileSystems/DirectoryFileSystem.cpp index e6e97546e2..972c1c5d03 100644 --- a/Core/FileSystems/DirectoryFileSystem.cpp +++ b/Core/FileSystems/DirectoryFileSystem.cpp @@ -251,7 +251,7 @@ bool DirectoryFileHandle::Open(const std::string &basePath, std::string &fileNam if (w32err == ERROR_DISK_FULL || w32err == ERROR_NOT_ENOUGH_QUOTA) { // This is returned when the disk is full. - I18NCategory *err = GetI18NCategory("Error"); + auto err = GetI18NCategory("Error"); host->NotifyUserMessage(err->T("Disk full while writing data")); error = SCE_KERNEL_ERROR_ERRNO_NO_PERM; } else if (!success) { @@ -315,7 +315,7 @@ bool DirectoryFileHandle::Open(const std::string &basePath, std::string &fileNam } } else if (errno == ENOSPC) { // This is returned when the disk is full. - I18NCategory *err = GetI18NCategory("Error"); + auto err = GetI18NCategory("Error"); host->NotifyUserMessage(err->T("Disk full while writing data")); error = SCE_KERNEL_ERROR_ERRNO_NO_PERM; } else { @@ -383,7 +383,7 @@ size_t DirectoryFileHandle::Write(const u8* pointer, s64 size) if (diskFull) { ERROR_LOG(FILESYS, "Disk full"); - I18NCategory *err = GetI18NCategory("Error"); + auto err = GetI18NCategory("Error"); host->NotifyUserMessage(err->T("Disk full while writing data")); // We only return an error when the disk is actually full. // When writing this would cause the disk to be full, so it wasn't written, we return 0. diff --git a/Core/HLE/ReplaceTables.cpp b/Core/HLE/ReplaceTables.cpp index e64adef5fa..a1d9e25e5c 100644 --- a/Core/HLE/ReplaceTables.cpp +++ b/Core/HLE/ReplaceTables.cpp @@ -1230,6 +1230,15 @@ static int Hook_starocean_clear_framebuf_after() { return 0; } +static int Hook_motorstorm_pixel_read() { + u32 fb_address = Memory::Read_U32(currentMIPS->r[MIPS_REG_A0] + 0x18); + u32 fb_height = Memory::Read_U16(currentMIPS->r[MIPS_REG_A0] + 0x26); + u32 fb_stride = Memory::Read_U16(currentMIPS->r[MIPS_REG_A0] + 0x28); + gpu->PerformMemoryDownload(fb_address, fb_height * fb_stride); + CBreakPoints::ExecMemCheck(fb_address, true, fb_height * fb_stride, currentMIPS->pc); + return 0; +} + #define JITFUNC(f) (&MIPSComp::MIPSFrontendInterface::f) // Can either replace with C functions or functions emitted in Asm/ArmAsm. @@ -1341,6 +1350,7 @@ static const ReplacementTableEntry entries[] = { { "marvelalliance1_copy", &Hook_marvelalliance1_copy_after, 0, REPFLAG_HOOKENTER, 0x69c }, { "starocean_clear_framebuf", &Hook_starocean_clear_framebuf_before, 0, REPFLAG_HOOKENTER, 0 }, { "starocean_clear_framebuf", &Hook_starocean_clear_framebuf_after, 0, REPFLAG_HOOKEXIT, 0 }, + { "motorstorm_pixel_read", &Hook_motorstorm_pixel_read, 0, REPFLAG_HOOKENTER, 0 }, {} }; diff --git a/Core/HLE/proAdhoc.cpp b/Core/HLE/proAdhoc.cpp index 829f8eff3a..419d6ce53f 100644 --- a/Core/HLE/proAdhoc.cpp +++ b/Core/HLE/proAdhoc.cpp @@ -1539,7 +1539,7 @@ int initNetwork(SceNetAdhocctlAdhocId *adhoc_id){ int sent = send(metasocket, (char*)&packet, sizeof(packet), 0); changeBlockingMode(metasocket, 1); // Change to non-blocking if (sent > 0) { - I18NCategory *n = GetI18NCategory("Networking"); + auto n = GetI18NCategory("Networking"); host->NotifyUserMessage(n->T("Network Initialized"), 1.0); return 0; } diff --git a/Core/HLE/sceCtrl.cpp b/Core/HLE/sceCtrl.cpp index 6074b1601f..79888ebea5 100644 --- a/Core/HLE/sceCtrl.cpp +++ b/Core/HLE/sceCtrl.cpp @@ -200,6 +200,11 @@ void __CtrlSetRapidFire(bool state) emuRapidFire = state; } +bool __CtrlGetRapidFire() +{ + return emuRapidFire; +} + static int __CtrlReadSingleBuffer(PSPPointer data, bool negative) { if (data.IsValid()) diff --git a/Core/HLE/sceCtrl.h b/Core/HLE/sceCtrl.h index db7c09b582..0c2d757dde 100644 --- a/Core/HLE/sceCtrl.h +++ b/Core/HLE/sceCtrl.h @@ -79,6 +79,7 @@ void __CtrlSetAnalogY(float value, int stick = CTRL_STICK_LEFT); // Call this to enable rapid-fire. This will cause buttons other than arrows to alternate. void __CtrlSetRapidFire(bool state); +bool __CtrlGetRapidFire(); // For use by internal UI like MsgDialog u32 __CtrlPeekButtons(); diff --git a/Core/HLE/sceDisplay.cpp b/Core/HLE/sceDisplay.cpp index de093d830c..876e4b5dcc 100644 --- a/Core/HLE/sceDisplay.cpp +++ b/Core/HLE/sceDisplay.cpp @@ -153,6 +153,12 @@ static double fpsHistory[120]; static int fpsHistorySize = (int)ARRAY_SIZE(fpsHistory); static int fpsHistoryPos = 0; static int fpsHistoryValid = 0; +static double frameTimeHistory[600]; +static double frameSleepHistory[600]; +static const int frameTimeHistorySize = (int)ARRAY_SIZE(frameTimeHistory); +static int frameTimeHistoryPos = 0; +static int frameTimeHistoryValid = 0; +static double lastFrameTimeHistory = 0.0; static double monitorFpsUntil = 0.0; static int lastNumFlips = 0; static float flips = 0.0f; @@ -235,6 +241,9 @@ void __DisplayInit() { lastNumFlips = 0; fpsHistoryValid = 0; fpsHistoryPos = 0; + frameTimeHistoryValid = 0; + frameTimeHistoryPos = 0; + lastFrameTimeHistory = 0.0; __KernelRegisterWaitTypeFuncs(WAITTYPE_VBLANK, __DisplayVblankBeginCallback, __DisplayVblankEndCallback); } @@ -435,8 +444,7 @@ static bool IsRunningSlow() { best = std::max(fpsHistory[index], best); } - // Note that SYSPROP_DISPLAY_REFRESH_RATE is multiplied by 1000. - return best < System_GetPropertyInt(SYSPROP_DISPLAY_REFRESH_RATE) * (1.0 / 1001.0); + return best < System_GetPropertyFloat(SYSPROP_DISPLAY_REFRESH_RATE) * 0.999; } return false; @@ -464,6 +472,23 @@ static void CalculateFPS() { ++fpsHistoryValid; } } + + if (g_Config.bDrawFrameGraph) { + frameTimeHistory[frameTimeHistoryPos++] = now - lastFrameTimeHistory; + lastFrameTimeHistory = now; + frameTimeHistoryPos = frameTimeHistoryPos % frameTimeHistorySize; + if (frameTimeHistoryValid < frameTimeHistorySize) { + ++frameTimeHistoryValid; + } + frameSleepHistory[frameTimeHistoryPos] = 0.0; + } +} + +double *__DisplayGetFrameTimes(int *out_valid, int *out_pos, double **out_sleep) { + *out_valid = frameTimeHistoryValid; + *out_pos = frameTimeHistoryPos; + *out_sleep = frameSleepHistory; + return frameTimeHistory; } void __DisplayGetDebugStats(char *stats, size_t bufsize) { @@ -510,12 +535,12 @@ static void DoFrameDropLogging(float scaledTimestep) { static int CalculateFrameSkip() { int frameSkipNum; - if (g_Config.iFrameSkipType == 1) { + if (g_Config.iFrameSkipType == 1) { // Calculate the frames to skip dynamically using the set percentage of the current fps - frameSkipNum = ceil( flips * (static_cast(g_Config.iFrameSkip) / 100.00) ); - } else { + frameSkipNum = ceil( flips * (static_cast(g_Config.iFrameSkip) / 100.00) ); + } else { // Use the set number of frames to skip - frameSkipNum = g_Config.iFrameSkip; + frameSkipNum = g_Config.iFrameSkip; } return frameSkipNum; } @@ -616,7 +641,8 @@ static void DoFrameIdleTiming() { time_update(); - double dist = time_now_d() - lastFrameTime; + double before = time_now_d(); + double dist = before - lastFrameTime; // Ignore if the distance is just crazy. May mean wrap or pause. if (dist < 0.0 || dist >= 15 * timePerVblank) { return; @@ -634,7 +660,7 @@ static void DoFrameIdleTiming() { // This prevents fast forward during loading screens. // Give a little extra wiggle room in case the next vblank does more work. const double goal = lastFrameTime + (numVBlanksSinceFlip - 1) * scaledVblank - 0.001; - if (numVBlanksSinceFlip >= 2 && time_now_d() < goal) { + if (numVBlanksSinceFlip >= 2 && before < goal) { while (time_now_d() < goal) { #ifdef _WIN32 sleep_ms(1); @@ -644,6 +670,10 @@ static void DoFrameIdleTiming() { #endif time_update(); } + + if (g_Config.bDrawFrameGraph) { + frameSleepHistory[frameTimeHistoryPos] += time_now_d() - before; + } } } @@ -714,6 +744,7 @@ void __DisplayFlip(int cyclesLate) { postEffectRequiresFlip = shaderInfo->requires60fps; const bool fbDirty = gpu->FramebufferDirty(); if (fbDirty || noRecentFlip || postEffectRequiresFlip) { + int frameSleepPos = frameTimeHistoryPos; CalculateFPS(); // Let the user know if we're running slow, so they know to adjust settings. @@ -721,7 +752,7 @@ void __DisplayFlip(int cyclesLate) { static bool hasNotifiedSlow = false; if (!g_Config.bHideSlowWarnings && !hasNotifiedSlow && PSP_CoreParameter().fpsLimit == FPSLimit::NORMAL && IsRunningSlow()) { #ifndef _DEBUG - I18NCategory *err = GetI18NCategory("Error"); + auto err = GetI18NCategory("Error"); if (g_Config.bSoftwareRendering) { host->NotifyUserMessage(err->T("Running slow: Try turning off Software Rendering"), 6.0f, 0xFF30D0D0); } else { @@ -775,6 +806,11 @@ void __DisplayFlip(int cyclesLate) { CoreTiming::ScheduleEvent(0 - cyclesLate, afterFlipEvent, 0); numVBlanksSinceFlip = 0; + + if (g_Config.bDrawFrameGraph) { + // Track how long we sleep (whether vsync or sleep_ms.) + frameSleepHistory[frameSleepPos] += real_time_now() - lastFrameTimeHistory; + } } else { // Okay, there's no new frame to draw. But audio may be playing, so we need to time still. DoFrameIdleTiming(); @@ -821,6 +857,7 @@ void hleLagSync(u64 userdata, int cyclesLate) { const double goal = lastLagSync + (scale / 1000.0f); time_update(); + double before = time_now_d(); // Don't lag too long ever, if they leave it paused. while (time_now_d() < goal && goal < time_now_d() + 0.01) { #ifndef _WIN32 @@ -833,6 +870,10 @@ void hleLagSync(u64 userdata, int cyclesLate) { const int emuOver = (int)cyclesToUs(cyclesLate); const int over = (int)((time_now_d() - goal) * 1000000); ScheduleLagSync(over - emuOver); + + if (g_Config.bDrawFrameGraph) { + frameSleepHistory[frameTimeHistoryPos] += time_now_d() - before; + } } static u32 sceDisplayIsVblank() { diff --git a/Core/HLE/sceDisplay.h b/Core/HLE/sceDisplay.h index db6cbcd1e1..47294c56d7 100644 --- a/Core/HLE/sceDisplay.h +++ b/Core/HLE/sceDisplay.h @@ -40,6 +40,7 @@ void __DisplayGetDebugStats(char stats[], size_t bufsize); void __DisplayGetFPS(float *out_vps, float *out_fps, float *out_actual_fps); void __DisplayGetVPS(float *out_vps); void __DisplayGetAveragedFPS(float *out_vps, float *out_fps); +double *__DisplayGetFrameTimes(int *out_valid, int *out_pos, double **out_sleep); int __DisplayGetNumVblanks(); int __DisplayGetVCount(); int __DisplayGetFlipCount(); diff --git a/Core/HLE/sceIo.cpp b/Core/HLE/sceIo.cpp index f1de847b12..04e1df23f7 100644 --- a/Core/HLE/sceIo.cpp +++ b/Core/HLE/sceIo.cpp @@ -2719,8 +2719,8 @@ static int IoAsyncFinish(int id) { case IoAsyncOp::CLOSE: f->asyncResult = 0; - // TODO: Rough estimate. - us = 100; + // CLOSE shouldn't have a delay. See #12549. + us = 0; DEBUG_LOG(SCEIO, "ASYNC %lli = sceIoCloseAsync(%d)", f->asyncResult, id); break; diff --git a/Core/HLE/sceKernel.cpp b/Core/HLE/sceKernel.cpp index c375c59a04..ad571355ef 100644 --- a/Core/HLE/sceKernel.cpp +++ b/Core/HLE/sceKernel.cpp @@ -169,6 +169,7 @@ void __KernelShutdown() kernelObjects.Clear(); __UsbCamShutdown(); + __UsbGpsShutdown(); __AudioCodecShutdown(); __VideoPmpShutdown(); @@ -738,7 +739,7 @@ const HLEFunction ThreadManForUser[] = {0X3B183E26, &WrapI_I, "sceKernelGetThreadExitStatus", 'i', "i" }, {0X52089CA1, &WrapI_I, "sceKernelGetThreadStackFreeSize", 'i', "i" }, {0XFFC36A14, &WrapU_UU, "sceKernelReferThreadRunStatus", 'x', "xx" }, - {0X17C1684E, &WrapU_UU, "sceKernelReferThreadStatus", 'x', "xx" }, + {0X17C1684E, &WrapU_UU, "sceKernelReferThreadStatus", 'i', "xp" }, {0X2C34E053, &WrapI_I, "sceKernelReleaseWaitThread", 'i', "i" }, {0X75156E8F, &WrapI_I, "sceKernelResumeThread", 'i', "i" }, {0X3AD58B8C, &WrapU_V, "sceKernelSuspendDispatchThread", 'x', "", HLE_NOT_IN_INTERRUPT }, diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index f166e9310f..8485136e7a 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -93,6 +93,7 @@ static const char * const lieAboutSuccessModules[] = { "disc0:/PSP_GAME/SYSDIR/UPDATE/EBOOT.BIN", }; +// Modules to not load. TODO: Look into loosening this a little (say sceFont). static const char * const blacklistedModules[] = { "sceATRAC3plus_Library", "sceFont_Library", @@ -109,6 +110,8 @@ static const char * const blacklistedModules[] = { "sceNetResolver_Library", "sceNet_Library", "sceSsl_Module", + "sceDEFLATE_Library", + "sceMD5_Library", }; struct VarSymbolImport { @@ -1064,7 +1067,6 @@ static bool KernelImportModuleFuncs(Module *module, u32 *firstImportStubAddr, bo static int gzipDecompress(u8 *OutBuffer, int OutBufferLength, u8 *InBuffer) { int err; - uLong crc; z_stream stream; u8 *outBufferPtr; @@ -1335,16 +1337,21 @@ static Module *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 loadAdd if (scan && codeSections.empty()) { u32 scanStart = module->textStart; u32 scanEnd = module->textEnd; - // Skip the exports and imports sections, they're not code. - if (scanEnd >= std::min(modinfo->libent, modinfo->libstub)) { - insertSymbols = MIPSAnalyst::ScanForFunctions(scanStart, std::min(modinfo->libent, modinfo->libstub) - 4, insertSymbols); - scanStart = std::min(modinfo->libentend, modinfo->libstubend); + + if (Memory::IsValidRange(scanStart, scanEnd - scanStart)) { + // Skip the exports and imports sections, they're not code. + if (scanEnd >= std::min(modinfo->libent, modinfo->libstub)) { + insertSymbols = MIPSAnalyst::ScanForFunctions(scanStart, std::min(modinfo->libent, modinfo->libstub) - 4, insertSymbols); + scanStart = std::min(modinfo->libentend, modinfo->libstubend); + } + if (scanEnd >= std::max(modinfo->libent, modinfo->libstub)) { + insertSymbols = MIPSAnalyst::ScanForFunctions(scanStart, std::max(modinfo->libent, modinfo->libstub) - 4, insertSymbols); + scanStart = std::max(modinfo->libentend, modinfo->libstubend); + } + insertSymbols = MIPSAnalyst::ScanForFunctions(scanStart, scanEnd, insertSymbols); + } else { + ERROR_LOG(LOADER, "Bad text scan range %08x-%08x", scanStart, scanEnd); } - if (scanEnd >= std::max(modinfo->libent, modinfo->libstub)) { - insertSymbols = MIPSAnalyst::ScanForFunctions(scanStart, std::max(modinfo->libent, modinfo->libstub) - 4, insertSymbols); - scanStart = std::max(modinfo->libentend, modinfo->libstubend); - } - insertSymbols = MIPSAnalyst::ScanForFunctions(scanStart, scanEnd, insertSymbols); } if (scan) { diff --git a/Core/HLE/sceKernelThread.cpp b/Core/HLE/sceKernelThread.cpp index d9a5c00053..3255af760e 100644 --- a/Core/HLE/sceKernelThread.cpp +++ b/Core/HLE/sceKernelThread.cpp @@ -1257,44 +1257,37 @@ u32 sceKernelReferThreadStatus(u32 threadID, u32 statusPtr) u32 error; Thread *t = kernelObjects.Get(threadID, error); - if (!t) - { - ERROR_LOG(SCEKERNEL, "%08x=sceKernelReferThreadStatus(%i, %08x): bad thread", error, threadID, statusPtr); - return error; + if (!t) { + hleEatCycles(700); + hleReSchedule("refer thread status"); + return hleLogError(SCEKERNEL, error, "bad thread"); } u32 wantedSize = Memory::Read_U32(statusPtr); - if (sceKernelGetCompiledSdkVersion() > 0x02060010) - { - if (wantedSize > THREADINFO_SIZE_AFTER_260) - { - ERROR_LOG(SCEKERNEL, "%08x=sceKernelReferThreadStatus(%i, %08x): bad size %d", SCE_KERNEL_ERROR_ILLEGAL_SIZE, threadID, statusPtr, wantedSize); - return SCE_KERNEL_ERROR_ILLEGAL_SIZE; + if (sceKernelGetCompiledSdkVersion() > 0x02060010) { + if (wantedSize > THREADINFO_SIZE_AFTER_260) { + hleEatCycles(1200); + hleReSchedule("refer thread status"); + return hleLogError(SCEKERNEL, SCE_KERNEL_ERROR_ILLEGAL_SIZE, "bad size %d", wantedSize); } - VERBOSE_LOG(SCEKERNEL, "sceKernelReferThreadStatus(%i, %08x)", threadID, statusPtr); - t->nt.nativeSize = THREADINFO_SIZE_AFTER_260; if (wantedSize != 0) Memory::Memcpy(statusPtr, &t->nt, std::min(wantedSize, (u32)sizeof(t->nt))); // TODO: What is this value? Basic tests show 0... if (wantedSize > sizeof(t->nt)) Memory::Memset(statusPtr + sizeof(t->nt), 0, wantedSize - sizeof(t->nt)); - } - else - { - VERBOSE_LOG(SCEKERNEL, "sceKernelReferThreadStatus(%i, %08x)", threadID, statusPtr); - + } else { t->nt.nativeSize = THREADINFO_SIZE; u32 sz = std::min(THREADINFO_SIZE, wantedSize); if (sz != 0) Memory::Memcpy(statusPtr, &t->nt, sz); } - hleEatCycles(1220); + hleEatCycles(1400); hleReSchedule("refer thread status"); - return 0; + return hleLogSuccessVerboseI(SCEKERNEL, 0); } // Thanks JPCSP diff --git a/Core/HLE/sceUsb.cpp b/Core/HLE/sceUsb.cpp index 361f55f26b..cd2e5e5e16 100644 --- a/Core/HLE/sceUsb.cpp +++ b/Core/HLE/sceUsb.cpp @@ -99,6 +99,16 @@ static int sceUsbDeactivate(u32 pid) { return 0; } +static int sceUsbWaitState(int state, int waitMode, u32 timeoutAddr) { + ERROR_LOG(HLE, "UNIMPL sceUsbWaitStat(%i, %i, %08x)", state, waitMode, timeoutAddr); + return sceUsbGetState(); +} + +static int sceUsbWaitStateCB(int state, int waitMode, u32 timeoutAddr) { + ERROR_LOG(HLE, "UNIMPL sceUsbWaitStateCB(%i, %i, %08x)", state, waitMode, timeoutAddr); + return 0; +} + const HLEFunction sceUsb[] = { {0XAE5DE6AF, &WrapI_CUU, "sceUsbStart", 'i', "sxx"}, @@ -108,8 +118,8 @@ const HLEFunction sceUsb[] = {0X112CC951, nullptr, "sceUsbGetDrvState", '?', "" }, {0X586DB82C, &WrapI_U, "sceUsbActivate", 'i', "x" }, {0XC572A9C8, &WrapI_U, "sceUsbDeactivate", 'i', "x" }, - {0X5BE0E002, nullptr, "sceUsbWaitState", '?', "" }, - {0X616F2B61, nullptr, "sceUsbWaitStateCB", '?', "" }, + {0X5BE0E002, &WrapI_IIU, "sceUsbWaitState", '?', "xxx"}, + {0X616F2B61, &WrapI_IIU, "sceUsbWaitStateCB", '?', "xxx"}, {0X1C360735, nullptr, "sceUsbWaitCancel", '?', "" }, }; diff --git a/Core/HLE/sceUsbCam.cpp b/Core/HLE/sceUsbCam.cpp index 7667cc335f..c2ab2979fa 100644 --- a/Core/HLE/sceUsbCam.cpp +++ b/Core/HLE/sceUsbCam.cpp @@ -19,14 +19,21 @@ #include #include "base/NativeApp.h" +#include "ppsspp_config.h" #include "Common/ChunkFile.h" #include "Core/HLE/HLE.h" -#include "Core/HLE/FunctionWrappers.h" #include "Core/HLE/sceUsbCam.h" +#include "Core/HW/Camera.h" #include "Core/MemMapHelpers.h" -PspUsbCamSetupMicParam micParam; -PspUsbCamSetupVideoParam videoParam; +#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP) +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include "Windows/CaptureDevice.h" +#endif + +Camera::Config *config; unsigned int videoBufferLength = 0; unsigned int nextVideoFrame = 0; @@ -38,21 +45,72 @@ enum { }; void __UsbCamInit() { - videoBuffer = new uint8_t[VIDEO_BUFFER_SIZE]; + config = new Camera::Config; + config->mode = Camera::Mode::Unused; + config->type = Camera::ConfigType::CfNone; + videoBuffer = new uint8_t[VIDEO_BUFFER_SIZE]; +} + +void __UsbCamDoState(PointerWrap &p) { + auto s = p.Section("sceUsbCam", 0, 1); + if (!s) { + return; + } + + p.Do(*config); + if (config->mode == Camera::Mode::Video) { // stillImage? TBD + Camera::stopCapture(); + Camera::startCapture(); + } } void __UsbCamShutdown() { + if (config->mode == Camera::Mode::Video) { // stillImage? TBD + Camera::stopCapture(); + } delete[] videoBuffer; videoBuffer = nullptr; + delete[] config; + config = nullptr; } // TODO: Technically, we should store the videoBuffer into the savestate, if this // module has been initialized. +static int getCameraResolution(Camera::ConfigType type, int *width, int *height) { + if (type == Camera::ConfigType::CfStill || type == Camera::ConfigType::CfVideo) { + switch(config->stillParam.resolution) { + case 0: *width = 160; *height = 120; return 0; + case 1: *width = 176; *height = 144; return 0; + case 2: *width = 320; *height = 240; return 0; + case 3: *width = 352; *height = 288; return 0; + case 4: *width = 640; *height = 480; return 0; + case 5: *width =1024; *height = 768; return 0; + case 6: *width =1280; *height = 960; return 0; + case 7: *width = 480; *height = 272; return 0; + case 8: *width = 360; *height = 272; return 0; + } + } else if (type == Camera::ConfigType::CfStillEx || type == Camera::ConfigType::CfVideoEx) { + switch(config->stillExParam.resolution) { + case 0: *width = 160; *height = 120; return 0; + case 1: *width = 176; *height = 144; return 0; + case 2: *width = 320; *height = 240; return 0; + case 3: *width = 352; *height = 288; return 0; + case 4: *width = 360; *height = 272; return 0; + case 5: *width = 480; *height = 272; return 0; + case 6: *width = 640; *height = 480; return 0; + case 7: *width =1024; *height = 768; return 0; + case 8: *width =1280; *height = 960; return 0; + } + } + *width = 0; *height = 0; return 1; +} + + static int sceUsbCamSetupMic(u32 paramAddr, u32 workareaAddr, int wasize) { INFO_LOG(HLE, "UNIMPL sceUsbCamSetupMic"); - if (Memory::IsValidRange(paramAddr, sizeof(micParam))) { - Memory::ReadStruct(paramAddr, &micParam); + if (Memory::IsValidRange(paramAddr, sizeof(PspUsbCamSetupMicParam))) { + Memory::ReadStruct(paramAddr, &config->micParam); } return 0; } @@ -74,47 +132,54 @@ static int sceUsbCamReadMicBlocking(u32 bufAddr, u32 size) { Memory::Write_U8(i & 0xFF, bufAddr + i); } } - hleEatMicro(1000000 / micParam.frequency * (size / 2)); + hleEatMicro(1000000 / config->micParam.frequency * (size / 2)); return size; } static int sceUsbCamSetupVideo(u32 paramAddr, u32 workareaAddr, int wasize) { - INFO_LOG(HLE, "UNIMPL sceUsbCamSetupVideo"); - if (Memory::IsValidRange(paramAddr, sizeof(videoParam))) { - Memory::ReadStruct(paramAddr, &videoParam); + if (Memory::IsValidRange(paramAddr, sizeof(PspUsbCamSetupVideoParam))) { + Memory::ReadStruct(paramAddr, &config->videoParam); } + config->type = Camera::ConfigType::CfVideo; + return 0; +} - INFO_LOG(HLE, "UNIMPL sceUsbCamSetupVideo - size: %d", videoParam.size); - INFO_LOG(HLE, "UNIMPL sceUsbCamSetupVideo - resolution: %d", videoParam.resolution); - INFO_LOG(HLE, "UNIMPL sceUsbCamSetupVideo - framesize: %d", videoParam.framesize); - - std::lock_guard lock(videoBufferMutex); - videoBufferLength = sizeof(sceUsbCamDummyImage); - memset(videoBuffer, 0, VIDEO_BUFFER_SIZE); - memcpy(videoBuffer, sceUsbCamDummyImage, sizeof(sceUsbCamDummyImage)); +static int sceUsbCamSetupVideoEx(u32 paramAddr, u32 workareaAddr, int wasize) { + if (Memory::IsValidRange(paramAddr, sizeof(PspUsbCamSetupVideoExParam))) { + Memory::ReadStruct(paramAddr, &config->videoExParam); + } + config->type = Camera::ConfigType::CfVideoEx; return 0; } static int sceUsbCamStartVideo() { - INFO_LOG(HLE, "UNIMPL sceUsbCamStartVideo"); - System_SendMessage("camera_command", "startVideo"); + std::lock_guard lock(videoBufferMutex); + + int width, height; + getCameraResolution(config->type, &width, &height); + + unsigned char* jpegData = nullptr; + int jpegLen = 0; + __cameraDummyImage(width, height, &jpegData, &jpegLen); + videoBufferLength = jpegLen; + memset(videoBuffer, 0, VIDEO_BUFFER_SIZE); + if (jpegData) { + memcpy(videoBuffer, jpegData, jpegLen); + free(jpegData); + jpegData = nullptr; + } + + Camera::startCapture(); return 0; } static int sceUsbCamStopVideo() { - INFO_LOG(HLE, "UNIMPL sceUsbCamStopVideo"); - System_SendMessage("camera_command", "stopVideo"); - return 0; -} - -static int sceUsbCamAutoImageReverseSW(int rev) { - INFO_LOG(HLE, "UNIMPL sceUsbCamAutoImageReverseSW"); + Camera::stopCapture(); return 0; } static int sceUsbCamReadVideoFrameBlocking(u32 bufAddr, u32 size) { std::lock_guard lock(videoBufferMutex); - u32 transferSize = std::min(videoBufferLength, size); if (Memory::IsValidRange(bufAddr, size)) { Memory::Memcpy(bufAddr, videoBuffer, transferSize); @@ -137,6 +202,39 @@ static int sceUsbCamPollReadVideoFrameEnd() { return nextVideoFrame; } +static int sceUsbCamSetupStill(u32 paramAddr) { + INFO_LOG(HLE, "UNIMPL sceUsbCamSetupStill"); + if (Memory::IsValidRange(paramAddr, sizeof(PspUsbCamSetupStillParam))) { + Memory::ReadStruct(paramAddr, &config->stillParam); + } + config->type = Camera::ConfigType::CfStill; + return 0; +} + +static int sceUsbCamSetupStillEx(u32 paramAddr) { + INFO_LOG(HLE, "UNIMPL sceUsbCamSetupStillEx"); + if (Memory::IsValidRange(paramAddr, sizeof(PspUsbCamSetupStillExParam))) { + Memory::ReadStruct(paramAddr, &config->stillExParam); + } + config->type = Camera::ConfigType::CfStillEx; + return 0; +} + +static int sceUsbCamAutoImageReverseSW(int on) { + INFO_LOG(HLE, "UNIMPL sceUsbCamAutoImageReverseSW: %d", on); + return 0; +} + +static int sceUsbCamGetLensDirection() { + INFO_LOG(HLE, "UNIMPL sceUsbCamGetLensDirection"); + return 0; +} + +static int sceUsbCamSetReverseMode(int reverseflags) { + INFO_LOG(HLE, "UNIMPL sceUsbCamSetReverseMode %d", reverseflags); + return 0; +} + const HLEFunction sceUsbCam[] = { { 0X03ED7A82, &WrapI_UUI, "sceUsbCamSetupMic", 'i', "xxi" }, @@ -151,7 +249,7 @@ const HLEFunction sceUsbCam[] = { 0X08AEE98A, nullptr, "sceUsbCamSetMicGain", '?', "" }, { 0X17F7B2FB, &WrapI_UUI, "sceUsbCamSetupVideo", 'i', "xxi" }, - { 0XCFE9E999, nullptr, "sceUsbCamSetupVideoEx", '?', "" }, + { 0XCFE9E999, &WrapI_UUI, "sceUsbCamSetupVideoEx", 'i', "xxi" }, { 0X574A8C3F, &WrapI_V, "sceUsbCamStartVideo", 'i', "" }, { 0X6CF32CB9, &WrapI_V, "sceUsbCamStopVideo", 'i', "" }, { 0X7DAC0C71, &WrapI_UU, "sceUsbCamReadVideoFrameBlocking", 'i', "xx" }, @@ -160,8 +258,8 @@ const HLEFunction sceUsbCam[] = { 0X41E73E95, &WrapI_V, "sceUsbCamPollReadVideoFrameEnd", 'i', "" }, { 0XDF9D0C92, nullptr, "sceUsbCamGetReadVideoFrameSize", '?', "" }, - { 0X3F0CF289, nullptr, "sceUsbCamSetupStill", '?', "" }, - { 0X0A41A298, nullptr, "sceUsbCamSetupStillEx", '?', "" }, + { 0X3F0CF289, &WrapI_U, "sceUsbCamSetupStill", 'i', "x" }, + { 0X0A41A298, &WrapI_U, "sceUsbCamSetupStillEx", 'i', "x" }, { 0X61BE5CAC, nullptr, "sceUsbCamStillInputBlocking", '?', "" }, { 0XFB0A6C5D, nullptr, "sceUsbCamStillInput", '?', "" }, { 0X7563AFA1, nullptr, "sceUsbCamStillWaitInputEnd", '?', "" }, @@ -171,7 +269,7 @@ const HLEFunction sceUsbCam[] = { 0XF93C4669, &WrapI_I, "sceUsbCamAutoImageReverseSW", 'i', "i" }, { 0X11A1F128, nullptr, "sceUsbCamGetAutoImageReverseState", '?', "" }, - { 0X4C34F553, nullptr, "sceUsbCamGetLensDirection", '?', "" }, + { 0X4C34F553, &WrapI_V, "sceUsbCamGetLensDirection", 'i', "" }, { 0X383E9FA8, nullptr, "sceUsbCamGetSaturation", '?', "" }, { 0X6E205974, nullptr, "sceUsbCamSetSaturation", '?', "" }, @@ -186,7 +284,7 @@ const HLEFunction sceUsbCam[] = { 0X2BCD50C0, nullptr, "sceUsbCamGetEvLevel", '?', "" }, { 0X1D686870, nullptr, "sceUsbCamSetEvLevel", '?', "" }, { 0XD5279339, nullptr, "sceUsbCamGetReverseMode", '?', "" }, - { 0X951BEDF5, nullptr, "sceUsbCamSetReverseMode", '?', "" }, + { 0X951BEDF5, &WrapI_I, "sceUsbCamSetReverseMode", 'i', "i" }, { 0X9E8AAF8D, nullptr, "sceUsbCamGetZoom", '?', "" }, { 0XC484901F, nullptr, "sceUsbCamSetZoom", '?', "" }, { 0XAA7D94BA, nullptr, "sceUsbCamGetAntiFlicker", '?', "" }, @@ -201,8 +299,78 @@ void Register_sceUsbCam() RegisterModule("sceUsbCam", ARRAY_SIZE(sceUsbCam), sceUsbCam); } +std::vector Camera::getDeviceList() { + #if defined(_WIN32) && !PPSSPP_PLATFORM(UWP) + if (winCamera) { + return winCamera->getDeviceList(); + } + #elif PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(IOS) + return __cameraGetDeviceList(); + #elif defined(USING_QT_UI) // Qt:macOS / Qt:Linux + return __qt_getDeviceList(); + #elif PPSSPP_PLATFORM(LINUX) // SDL:Linux + return __v4l_getDeviceList(); + #endif + return std::vector(); +} + +int Camera::startCapture() { + int width, height; + getCameraResolution(config->type, &width, &height); + INFO_LOG(HLE, "%s resolution: %dx%d", __FUNCTION__, width, height); + + config->mode = Camera::Mode::Video; + #if defined(_WIN32) && !PPSSPP_PLATFORM(UWP) + if (winCamera) { + if (winCamera->isShutDown()) { + delete winCamera; + winCamera = new WindowsCaptureDevice(CAPTUREDEVIDE_TYPE::VIDEO); + winCamera->sendMessage({ CAPTUREDEVIDE_COMMAND::INITIALIZE, nullptr }); + } + void* resolution = static_cast(new std::vector({ width, height })); + winCamera->sendMessage({ CAPTUREDEVIDE_COMMAND::START, resolution }); + } + #elif PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(IOS) || defined(USING_QT_UI) + char command[40] = {0}; + snprintf(command, sizeof(command), "startVideo_%dx%d", width, height); + System_SendMessage("camera_command", command); + #elif PPSSPP_PLATFORM(LINUX) + __v4l_startCapture(width, height); + #else + ERROR_LOG(HLE, "%s not implemented", __FUNCTION__); + #endif + return 0; +} + +int Camera::stopCapture() { + INFO_LOG(HLE, "%s", __FUNCTION__); + #if defined(_WIN32) && !PPSSPP_PLATFORM(UWP) + if (winCamera) { + winCamera->sendMessage({ CAPTUREDEVIDE_COMMAND::STOP, nullptr }); + } + #elif PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(IOS) || defined(USING_QT_UI) + System_SendMessage("camera_command", "stopVideo"); + #elif PPSSPP_PLATFORM(LINUX) + __v4l_stopCapture(); + #else + ERROR_LOG(HLE, "%s not implemented", __FUNCTION__); + #endif + config->mode = Camera::Mode::Unused; + return 0; +} + +void Camera::onCameraDeviceChange() { + if (config != nullptr && config->mode == Camera::Mode::Video) { + stopCapture(); + startCapture(); + } +} + void Camera::pushCameraImage(long long length, unsigned char* image) { std::lock_guard lock(videoBufferMutex); + if (!videoBuffer) { + return; + } memset(videoBuffer, 0, VIDEO_BUFFER_SIZE); if (length > VIDEO_BUFFER_SIZE) { videoBufferLength = 0; diff --git a/Core/HLE/sceUsbCam.h b/Core/HLE/sceUsbCam.h index ebb5388db6..4d4e4f0831 100644 --- a/Core/HLE/sceUsbCam.h +++ b/Core/HLE/sceUsbCam.h @@ -17,16 +17,18 @@ #pragma once +#include +#include +#include +#include "Core/HLE/FunctionWrappers.h" + void Register_sceUsbCam(); void __UsbCamInit(); +void __UsbCamDoState(PointerWrap &p); void __UsbCamShutdown(); -namespace Camera { - void pushCameraImage(long long length, unsigned char *image); -} - -typedef struct PspUsbCamSetupMicParam { +typedef struct { int size; u32 unk; int gain; @@ -34,7 +36,30 @@ typedef struct PspUsbCamSetupMicParam { int frequency; } PspUsbCamSetupMicParam; -typedef struct PspUsbCamSetupVideoParam { +typedef struct { + int size; + int resolution; + int jpegsize; + int reverseflags; + int delay; + int complevel; +} PspUsbCamSetupStillParam; + +typedef struct { + int size; + u32 unk; + int resolution; + int jpegsize; + int complevel; + u32 unk2; + u32 unk3; + int flip; + int mirror; + int delay; + u32 unk4[5]; +} PspUsbCamSetupStillExParam; + +typedef struct { int size; int resolution; int framerate; @@ -49,856 +74,51 @@ typedef struct PspUsbCamSetupVideoParam { int evlevel; } PspUsbCamSetupVideoParam; -static const unsigned char sceUsbCamDummyImage[] = { - 0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x00, 0x60, - 0x00, 0x60, 0x00, 0x00, 0xFF, 0xE1, 0x00, 0x68, 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x4D, 0x4D, - 0x00, 0x2A, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x01, 0x1A, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x3E, 0x01, 0x1B, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, - 0x01, 0x28, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x01, 0x31, 0x00, 0x02, - 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x4E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x01, 0x70, 0x61, 0x69, 0x6E, - 0x74, 0x2E, 0x6E, 0x65, 0x74, 0x20, 0x34, 0x2E, 0x30, 0x2E, 0x31, 0x37, 0x00, 0x00, 0xFF, 0xDB, - 0x00, 0x43, 0x00, 0x04, 0x02, 0x03, 0x03, 0x03, 0x02, 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, - 0x04, 0x05, 0x09, 0x06, 0x05, 0x05, 0x05, 0x05, 0x0B, 0x08, 0x08, 0x06, 0x09, 0x0D, 0x0B, 0x0D, - 0x0D, 0x0D, 0x0B, 0x0C, 0x0C, 0x0E, 0x10, 0x14, 0x11, 0x0E, 0x0F, 0x13, 0x0F, 0x0C, 0x0C, 0x12, - 0x18, 0x12, 0x13, 0x15, 0x16, 0x17, 0x17, 0x17, 0x0E, 0x11, 0x19, 0x1B, 0x19, 0x16, 0x1A, 0x14, - 0x16, 0x17, 0x16, 0xFF, 0xDB, 0x00, 0x43, 0x01, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x0A, 0x06, - 0x06, 0x0A, 0x16, 0x0F, 0x0C, 0x0F, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, - 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, - 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, - 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x01, 0x10, 0x01, - 0xE0, 0x03, 0x01, 0x22, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, 0xC4, 0x00, 0x1F, 0x00, - 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0xFF, 0xC4, 0x00, 0xB5, - 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, - 0x7D, 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, - 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, 0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1, - 0xF0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0A, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, - 0x28, 0x29, 0x2A, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, - 0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, - 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, - 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, - 0xA7, 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, - 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE1, - 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, - 0xF8, 0xF9, 0xFA, 0xFF, 0xC4, 0x00, 0x1F, 0x01, 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, - 0x08, 0x09, 0x0A, 0x0B, 0xFF, 0xC4, 0x00, 0xB5, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, - 0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, - 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, - 0x91, 0xA1, 0xB1, 0xC1, 0x09, 0x23, 0x33, 0x52, 0xF0, 0x15, 0x62, 0x72, 0xD1, 0x0A, 0x16, 0x24, - 0x34, 0xE1, 0x25, 0xF1, 0x17, 0x18, 0x19, 0x1A, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x35, 0x36, 0x37, - 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, - 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, - 0x78, 0x79, 0x7A, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, - 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, - 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, - 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, - 0xE9, 0xEA, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFF, 0xDA, 0x00, 0x0C, 0x03, - 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3F, 0x00, 0xF4, 0xEA, 0x28, 0xA2, 0xBF, 0x62, 0x3F, - 0x6C, 0x0A, 0x2A, 0x1B, 0x89, 0xC4, 0x67, 0x68, 0x19, 0x35, 0x1F, 0xDA, 0x9B, 0xFB, 0x83, 0xF3, - 0xAF, 0x96, 0xC7, 0xF1, 0x9E, 0x49, 0x81, 0xC4, 0x4B, 0x0F, 0x5A, 0xB7, 0xBF, 0x1D, 0xEC, 0x9B, - 0xB7, 0x95, 0xD2, 0xB1, 0xF9, 0xBE, 0x77, 0xE2, 0xCF, 0x08, 0x64, 0xB8, 0xE9, 0xE0, 0x71, 0x78, - 0xAF, 0xDE, 0xC3, 0x49, 0x28, 0xC6, 0x52, 0xB3, 0xEC, 0xDA, 0x4D, 0x5F, 0xBA, 0xBE, 0x9D, 0x4B, - 0x54, 0x55, 0x5F, 0xB5, 0x37, 0xF7, 0x07, 0xE7, 0x40, 0xBB, 0x3D, 0xD3, 0xF5, 0xAE, 0x48, 0xF8, - 0x81, 0xC3, 0xCD, 0xDB, 0xDB, 0x35, 0xFF, 0x00, 0x6E, 0xCB, 0xFC, 0x8F, 0x32, 0x9F, 0x8E, 0x1C, - 0x0B, 0x29, 0x24, 0xF1, 0x6D, 0x7A, 0xD3, 0xA9, 0xFF, 0x00, 0xC8, 0x96, 0xA8, 0xA8, 0xE1, 0x95, - 0x24, 0xE9, 0xD7, 0xD0, 0xD4, 0x95, 0xF5, 0x78, 0x4C, 0x66, 0x1F, 0x19, 0x45, 0x56, 0xC3, 0xCD, - 0x4A, 0x0F, 0xAA, 0x3F, 0x4B, 0xCA, 0xF3, 0x6C, 0x0E, 0x6B, 0x85, 0x8E, 0x2F, 0x03, 0x55, 0x54, - 0xA7, 0x2D, 0x9C, 0x5D, 0xD7, 0xFC, 0x07, 0xDD, 0x3D, 0x57, 0x50, 0xA2, 0x8A, 0x2B, 0xA4, 0xF4, - 0x02, 0x8A, 0x2A, 0xB4, 0xB7, 0x24, 0x36, 0x10, 0x0C, 0x0F, 0x5A, 0xF1, 0xF3, 0x9C, 0xFB, 0x03, - 0x93, 0xD2, 0x8D, 0x4C, 0x5C, 0xAC, 0xA4, 0xEC, 0x92, 0x57, 0x6F, 0xFE, 0x18, 0xF9, 0x4E, 0x2C, - 0xE3, 0x4C, 0x9B, 0x85, 0x70, 0xD0, 0xC4, 0x66, 0x95, 0x1C, 0x54, 0xDD, 0xA2, 0x92, 0x6D, 0xB6, - 0xB7, 0xB2, 0x5D, 0x17, 0x57, 0xB6, 0xDD, 0xD1, 0x66, 0x8A, 0xA7, 0xF6, 0xA9, 0x3D, 0x17, 0xF2, - 0xA3, 0xED, 0x52, 0x7A, 0x2F, 0xE5, 0x5F, 0x37, 0xFF, 0x00, 0x11, 0x1F, 0x21, 0xFE, 0x69, 0x7F, - 0xE0, 0x27, 0xC0, 0x7F, 0xC4, 0x7C, 0xE0, 0xBF, 0xE7, 0xA9, 0xFF, 0x00, 0x80, 0x3F, 0xF3, 0x2E, - 0x51, 0x55, 0xE3, 0xBA, 0x07, 0x87, 0x5C, 0x7B, 0x8A, 0x9D, 0x48, 0x65, 0xC8, 0x39, 0x15, 0xF4, - 0x99, 0x56, 0x7D, 0x96, 0xE6, 0xB1, 0x6F, 0x09, 0x55, 0x49, 0xAD, 0xD6, 0xCD, 0x7C, 0x9E, 0xBF, - 0x3D, 0x8F, 0xD0, 0x38, 0x6B, 0x8D, 0xB2, 0x0E, 0x25, 0x83, 0x96, 0x57, 0x89, 0x53, 0x92, 0xD5, - 0xC7, 0x69, 0x2F, 0x58, 0xBB, 0x3B, 0x79, 0xED, 0xE6, 0x2D, 0x14, 0x51, 0x5E, 0xC1, 0xF5, 0x41, - 0x45, 0x14, 0x50, 0x01, 0x45, 0x41, 0x2D, 0xCA, 0xAB, 0x61, 0x46, 0xEA, 0x67, 0xDA, 0x9B, 0xFB, - 0x83, 0xF3, 0xAF, 0x93, 0xC5, 0x71, 0xC6, 0x43, 0x86, 0xAA, 0xE9, 0x4E, 0xBD, 0xDA, 0xDE, 0xC9, - 0xB5, 0xF7, 0xA5, 0x6F, 0xB8, 0xFC, 0xC7, 0x32, 0xF1, 0x8B, 0x82, 0xB2, 0xFC, 0x4C, 0xB0, 0xD5, - 0x71, 0x97, 0x94, 0x5D, 0x9F, 0x2C, 0x67, 0x24, 0x9F, 0xF8, 0x92, 0x69, 0xFC, 0x9B, 0x2D, 0x51, - 0x55, 0xD6, 0xE8, 0x7F, 0x12, 0xE3, 0xE9, 0x53, 0x47, 0x22, 0x3F, 0xDD, 0x6F, 0xC2, 0xBD, 0x0C, - 0xBB, 0x89, 0x32, 0x8C, 0xC5, 0xA8, 0xE1, 0xAB, 0xA7, 0x2E, 0xCF, 0x47, 0xF7, 0x3B, 0x33, 0xDD, - 0xC8, 0x7C, 0x40, 0xE1, 0x7C, 0xFA, 0x4A, 0x9E, 0x5F, 0x8C, 0x8C, 0xA6, 0xFE, 0xCB, 0xBC, 0x64, - 0xFD, 0x23, 0x24, 0x9B, 0xF9, 0x26, 0x3A, 0x8A, 0x28, 0xAF, 0x70, 0xFB, 0x10, 0xA2, 0x8A, 0x28, - 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, - 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, - 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA8, 0xE4, 0x99, 0x11, 0xB0, 0x4F, 0x3E, 0xD5, 0x25, 0x67, 0xCD, - 0xFE, 0xB9, 0xB3, 0xEA, 0x6B, 0xE2, 0xF8, 0xDB, 0x88, 0xB1, 0x59, 0x2E, 0x12, 0x9C, 0xF0, 0xD1, - 0x5C, 0xD3, 0x76, 0xBB, 0xD5, 0x2B, 0x2B, 0xFD, 0xE7, 0xE4, 0x7E, 0x2F, 0x71, 0xDE, 0x63, 0xC2, - 0x39, 0x5D, 0x0A, 0xB9, 0x7C, 0x22, 0xEA, 0x55, 0x93, 0x8D, 0xE4, 0xAE, 0x92, 0x4A, 0xFB, 0x5D, - 0x6A, 0xFA, 0x7C, 0xCB, 0x5F, 0x6A, 0x8F, 0xD1, 0xBF, 0x2A, 0x55, 0xB8, 0x89, 0xB8, 0xCE, 0x3E, - 0xB5, 0x51, 0x51, 0xD8, 0x65, 0x54, 0x9F, 0xA0, 0xA4, 0x65, 0x65, 0xFB, 0xC0, 0x8F, 0xAD, 0x7E, - 0x7B, 0x4F, 0xC4, 0x0E, 0x23, 0xA5, 0x15, 0x5A, 0xA5, 0x34, 0xE1, 0xE7, 0x06, 0x93, 0xF9, 0xA6, - 0x7E, 0x19, 0x47, 0xC7, 0x0E, 0x3D, 0xC3, 0xC2, 0x38, 0xBA, 0xF4, 0x23, 0x2A, 0x2F, 0xAB, 0xA5, - 0x25, 0x17, 0xE9, 0x24, 0xD7, 0xE6, 0x68, 0xF5, 0xE9, 0x45, 0x67, 0xC7, 0x23, 0xA7, 0xDD, 0x6F, - 0xC2, 0xAC, 0xDB, 0xDC, 0x07, 0x3B, 0x5F, 0x83, 0x5F, 0x77, 0x92, 0x71, 0xFE, 0x59, 0x98, 0xCE, - 0x34, 0x6A, 0xA7, 0x4A, 0xA3, 0xEF, 0xF0, 0xB7, 0xD9, 0x4B, 0xFC, 0xD2, 0x3F, 0x67, 0xE0, 0xFF, - 0x00, 0x1B, 0xF8, 0x7B, 0x3E, 0xAB, 0x4F, 0x09, 0x89, 0x4F, 0x0F, 0x5E, 0x5A, 0x5A, 0x56, 0x70, - 0x6F, 0xB2, 0x9F, 0x9F, 0x4E, 0x65, 0x1E, 0xDA, 0xB2, 0x7A, 0x28, 0xA2, 0xBE, 0xEC, 0xFD, 0xA0, - 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, - 0xA5, 0x79, 0xFF, 0x00, 0x1F, 0x0D, 0x49, 0x0C, 0x4D, 0x26, 0x76, 0x91, 0xC7, 0xAD, 0x3A, 0xFB, - 0xFD, 0x70, 0xFA, 0x53, 0xB4, 0xF3, 0xF3, 0x30, 0xF5, 0x15, 0xFC, 0xFA, 0xF2, 0xBC, 0x36, 0x2B, - 0x8C, 0xAA, 0x60, 0xF1, 0x5F, 0x04, 0xE7, 0x3E, 0xB6, 0xDD, 0x39, 0x2F, 0xC6, 0xC7, 0xF0, 0xDC, - 0xB8, 0x77, 0x01, 0x99, 0x78, 0xB1, 0x88, 0xCA, 0xB3, 0x2B, 0xBA, 0x55, 0x6B, 0x55, 0xBD, 0x9D, - 0x9F, 0xBC, 0xA5, 0x38, 0xD9, 0xFA, 0xD9, 0x79, 0x89, 0xF6, 0x59, 0x3D, 0x56, 0x9A, 0xD6, 0xF2, - 0x2A, 0xE7, 0x83, 0xF4, 0xAB, 0xB4, 0x31, 0x01, 0x72, 0x4E, 0x05, 0x7E, 0x83, 0x5F, 0xC3, 0x9C, - 0x8B, 0xD9, 0x4B, 0x59, 0x47, 0x4D, 0xF9, 0xB6, 0xF3, 0xD5, 0x58, 0xFD, 0xCB, 0x19, 0xE0, 0x1F, - 0x05, 0xFD, 0x5E, 0x6D, 0x3A, 0x94, 0xEC, 0x9F, 0xBD, 0xCF, 0xF0, 0xF9, 0xBB, 0xAB, 0x59, 0x75, - 0xB9, 0x9B, 0x92, 0x0E, 0x45, 0x5E, 0xB6, 0x72, 0xF0, 0x82, 0x7A, 0xD5, 0x39, 0x08, 0x32, 0x31, - 0x1D, 0xCD, 0x5A, 0xB1, 0xFF, 0x00, 0x51, 0xF8, 0xD7, 0xC7, 0x78, 0x73, 0x5A, 0xA5, 0x2C, 0xEA, - 0xAE, 0x1A, 0x9C, 0xEF, 0x4D, 0xC6, 0x5E, 0x8E, 0xCD, 0x59, 0xFF, 0x00, 0x5D, 0xCF, 0xCA, 0x3C, - 0x03, 0xC5, 0x57, 0xC3, 0x71, 0x76, 0x27, 0x01, 0x46, 0xAB, 0x95, 0x07, 0x09, 0xDF, 0xB4, 0xB9, - 0x65, 0x15, 0x19, 0x5B, 0xE7, 0xF7, 0x32, 0x6A, 0x28, 0xA2, 0xBF, 0x72, 0x3F, 0xB2, 0x42, 0xB3, - 0x4D, 0x69, 0x56, 0x6D, 0x7E, 0x43, 0xE2, 0xB6, 0xD8, 0x3F, 0xFB, 0x7F, 0xFF, 0x00, 0x6C, 0x3F, - 0x96, 0x7E, 0x92, 0xDF, 0x0E, 0x57, 0xFF, 0x00, 0x71, 0xBF, 0xF7, 0x19, 0x3C, 0x56, 0xFB, 0xE3, - 0x0D, 0xBF, 0x19, 0xF6, 0xA5, 0xFB, 0x21, 0xEC, 0xFF, 0x00, 0xA5, 0x4D, 0x6B, 0xFF, 0x00, 0x1E, - 0xEB, 0xF4, 0xA9, 0x2B, 0xDE, 0xCA, 0xF8, 0x1F, 0x20, 0xC4, 0x65, 0xD4, 0x2A, 0xD4, 0xA1, 0xEF, - 0x4A, 0x11, 0x6D, 0xF3, 0x4B, 0x76, 0x93, 0x6F, 0xE2, 0xB1, 0xF6, 0x9C, 0x39, 0xE0, 0xEF, 0x04, - 0xE3, 0xB2, 0x2C, 0x1E, 0x26, 0xBE, 0x0D, 0xBA, 0x95, 0x29, 0x42, 0x52, 0x7E, 0xD2, 0xA2, 0xF7, - 0xA5, 0x04, 0xDB, 0xB7, 0x3D, 0xB7, 0x7D, 0xAD, 0xE4, 0x50, 0x96, 0x37, 0x8F, 0xEF, 0x0F, 0xC6, - 0x96, 0xDE, 0x53, 0x1B, 0xFF, 0x00, 0xB3, 0xDC, 0x55, 0xD7, 0x50, 0xCA, 0x54, 0xF7, 0xAC, 0xF6, - 0x1B, 0x58, 0x8F, 0x43, 0x5F, 0x03, 0xC4, 0xD9, 0x0D, 0x5E, 0x16, 0xC7, 0x51, 0xC5, 0xE0, 0x6A, - 0x3E, 0x56, 0xF4, 0xBE, 0xE9, 0xAD, 0xD3, 0xEE, 0x9A, 0xFC, 0x2E, 0x99, 0xF8, 0xA7, 0x88, 0x5C, - 0x17, 0x88, 0xF0, 0xE3, 0x38, 0xC2, 0xE6, 0x99, 0x35, 0x79, 0x7B, 0x39, 0x36, 0xE2, 0xDF, 0xC5, - 0x19, 0x46, 0xD7, 0x8B, 0x6A, 0xDC, 0xD1, 0x69, 0xF6, 0xD5, 0x5D, 0x3E, 0xEF, 0x46, 0x8A, 0x8E, - 0xD4, 0xE6, 0xDD, 0x73, 0x52, 0x57, 0xEE, 0x59, 0x7E, 0x2D, 0x63, 0x30, 0x74, 0xB1, 0x29, 0x59, - 0x4E, 0x2A, 0x5F, 0x7A, 0xB9, 0xFD, 0x93, 0x91, 0xE6, 0x71, 0xCD, 0x72, 0xAC, 0x36, 0x61, 0x15, - 0x65, 0x56, 0x11, 0x9D, 0xBB, 0x73, 0x45, 0x3B, 0x7C, 0xAE, 0x15, 0x5E, 0xF2, 0x5C, 0x7E, 0xED, - 0x4F, 0xD6, 0xAC, 0x31, 0xC2, 0xE4, 0xF6, 0xAC, 0xE6, 0x25, 0x98, 0x93, 0xDE, 0xBE, 0x1F, 0xC4, - 0x4C, 0xF6, 0xAE, 0x03, 0x05, 0x0C, 0x2D, 0x07, 0x69, 0x55, 0xBD, 0xDF, 0x55, 0x15, 0xBF, 0xDF, - 0x7B, 0x7A, 0x5C, 0xFC, 0x6F, 0xC7, 0x8E, 0x33, 0xC4, 0xE4, 0xB9, 0x45, 0x2C, 0xB7, 0x07, 0x2E, - 0x5A, 0x98, 0x9E, 0x6B, 0xB5, 0xBA, 0x84, 0x6D, 0x7B, 0x76, 0x72, 0x6D, 0x2B, 0xF6, 0xE6, 0x05, - 0x52, 0xCD, 0xB5, 0x47, 0x35, 0x2F, 0xD9, 0x64, 0xF5, 0x5F, 0xCE, 0xA6, 0xB3, 0x8C, 0x2C, 0x7B, - 0xBB, 0xB5, 0x4D, 0x5E, 0x4F, 0x0E, 0x78, 0x79, 0x83, 0xAD, 0x80, 0x85, 0x7C, 0xC2, 0xEE, 0x73, - 0xD5, 0x24, 0xED, 0x64, 0xF6, 0xF9, 0xF5, 0x7D, 0xB6, 0x3E, 0x67, 0x80, 0xBC, 0x09, 0xCA, 0xB1, - 0x59, 0x2D, 0x3C, 0x6E, 0x7B, 0xCC, 0xEA, 0xD5, 0x4A, 0x4A, 0x31, 0x97, 0x2A, 0x8C, 0x5A, 0xBA, - 0x4F, 0xBC, 0x9A, 0xD5, 0xF6, 0xDB, 0xA3, 0x6E, 0x8C, 0x90, 0x48, 0xBD, 0x57, 0x3F, 0x4A, 0x8C, - 0x12, 0x0E, 0x47, 0x15, 0xA5, 0x50, 0xDC, 0x40, 0xAF, 0xC8, 0xE1, 0xBF, 0x9D, 0x73, 0xE7, 0x5E, - 0x1A, 0xBA, 0x50, 0x75, 0xB2, 0xBA, 0x8D, 0xB5, 0xAF, 0x2C, 0xB7, 0xF9, 0x4B, 0x4D, 0x7D, 0x7E, - 0xF3, 0x8B, 0x8B, 0xBE, 0x8F, 0xB3, 0xC2, 0xD1, 0x78, 0xBE, 0x1C, 0xAD, 0x29, 0x4E, 0x3A, 0xFB, - 0x39, 0xB5, 0x77, 0xFE, 0x09, 0xAB, 0x6B, 0xD9, 0x35, 0xFF, 0x00, 0x6F, 0x5C, 0x6D, 0xBD, 0xC6, - 0x7E, 0x57, 0xEB, 0xD8, 0xD5, 0x8A, 0xA4, 0xB6, 0xF2, 0x6E, 0xC1, 0x18, 0xF7, 0xAB, 0xB5, 0xF5, - 0x5C, 0x0F, 0x8C, 0xCE, 0x2B, 0x61, 0x27, 0x4B, 0x32, 0x83, 0x5C, 0x96, 0x51, 0x72, 0x4D, 0x37, - 0xDE, 0xF7, 0xDE, 0xDA, 0x6A, 0x7E, 0x93, 0xE0, 0xEE, 0x6B, 0xC5, 0x78, 0xBC, 0xB2, 0xAE, 0x1F, - 0x88, 0x28, 0xCA, 0x2E, 0x93, 0x51, 0x84, 0xA6, 0x9C, 0x67, 0x25, 0xAD, 0xD3, 0xBE, 0xAF, 0x97, - 0x4F, 0x7B, 0xAD, 0xF5, 0x6D, 0xAB, 0x85, 0x14, 0x51, 0x5F, 0x70, 0x7E, 0xC4, 0x23, 0x30, 0x55, - 0xDC, 0x7A, 0x0A, 0xAE, 0xD7, 0x47, 0x3C, 0x27, 0xE6, 0x68, 0xBF, 0x63, 0xF2, 0xA7, 0xE2, 0x6A, - 0x08, 0x91, 0xA4, 0x6D, 0xAB, 0x5F, 0x91, 0xF1, 0x6F, 0x15, 0xE6, 0x8B, 0x36, 0x59, 0x66, 0x56, - 0xEC, 0xD5, 0x93, 0xB2, 0x4D, 0xCA, 0x4F, 0x5B, 0x6B, 0x7D, 0x17, 0xE7, 0x73, 0xF9, 0x7B, 0xC4, - 0xFF, 0x00, 0x12, 0xF8, 0x92, 0x3C, 0x4C, 0xB8, 0x7B, 0x86, 0xE4, 0xE3, 0x28, 0x59, 0x3E, 0x54, - 0xA5, 0x29, 0xCE, 0x49, 0x3B, 0x2B, 0xA7, 0x64, 0x93, 0x4B, 0xA6, 0xB7, 0xBE, 0x84, 0xDF, 0x6A, - 0x6F, 0xEE, 0x0A, 0x72, 0xDD, 0x0F, 0xE2, 0x4F, 0xC8, 0xD3, 0x3E, 0xCB, 0x27, 0xAA, 0xD3, 0x1A, - 0x09, 0x57, 0xF8, 0x73, 0xF4, 0xAF, 0x25, 0xE6, 0x7C, 0x77, 0x84, 0xFD, 0xE5, 0x48, 0xCD, 0xAF, - 0xF0, 0x27, 0xF9, 0x2B, 0x9F, 0x2F, 0x2E, 0x22, 0xF1, 0x9F, 0x2C, 0x5E, 0xDE, 0xB5, 0x3A, 0xB2, - 0x8A, 0xDE, 0xF4, 0xA3, 0x25, 0xF3, 0xE5, 0x8B, 0x6B, 0xD7, 0x42, 0xD4, 0x73, 0x46, 0xFD, 0x1B, - 0xF0, 0x35, 0x25, 0x66, 0x90, 0x47, 0x04, 0x54, 0x91, 0xCC, 0xE9, 0xD0, 0xE4, 0x7A, 0x1A, 0xF5, - 0x32, 0xBF, 0x13, 0xA4, 0xA4, 0xA9, 0xE6, 0x54, 0x6D, 0xFD, 0xE8, 0xFE, 0xB1, 0x7F, 0xA3, 0xF9, - 0x1F, 0x47, 0xC3, 0x9F, 0x48, 0x99, 0xC6, 0x6A, 0x86, 0x7F, 0x85, 0xB3, 0xBD, 0x9C, 0xE9, 0xDF, - 0x4F, 0x58, 0x49, 0xFD, 0xF6, 0x97, 0xC8, 0xBD, 0x45, 0x47, 0x0C, 0xCB, 0x27, 0x4E, 0x0F, 0xA5, - 0x49, 0x5F, 0xAA, 0x60, 0xB1, 0xD8, 0x6C, 0x6D, 0x15, 0x5F, 0x0D, 0x35, 0x28, 0x3E, 0xAB, 0xFA, - 0xD1, 0xF9, 0x1F, 0xD2, 0x99, 0x4E, 0x71, 0x97, 0xE7, 0x18, 0x48, 0xE3, 0x32, 0xFA, 0xAA, 0xA5, - 0x29, 0x6C, 0xD7, 0xE4, 0xFA, 0xA6, 0xBA, 0xA7, 0x66, 0xBA, 0x85, 0x14, 0x51, 0x5D, 0x47, 0xA4, - 0x14, 0x51, 0x51, 0xC9, 0x34, 0x69, 0xC1, 0x3C, 0xFA, 0x0A, 0xE5, 0xC5, 0xE3, 0x70, 0xD8, 0x3A, - 0x5E, 0xD7, 0x13, 0x51, 0x42, 0x3D, 0xDB, 0xB1, 0xE7, 0x66, 0x99, 0xBE, 0x5F, 0x95, 0x61, 0xDE, - 0x27, 0x1F, 0x5A, 0x34, 0xA9, 0xAE, 0xB2, 0x69, 0x2B, 0xF6, 0xD7, 0x77, 0xE4, 0xB5, 0x24, 0xA2, - 0xAB, 0x35, 0xD0, 0xFE, 0x14, 0xFC, 0xCD, 0x33, 0xED, 0x52, 0x7A, 0x2F, 0xE5, 0x5F, 0x29, 0x88, - 0xF1, 0x03, 0x20, 0xA3, 0x2B, 0x2A, 0xAE, 0x5E, 0x91, 0x7F, 0xAD, 0x8F, 0xCC, 0xF1, 0xDE, 0x38, - 0xF0, 0x46, 0x16, 0x7C, 0xB1, 0xC4, 0x4A, 0xA3, 0xFE, 0xE4, 0x24, 0xD7, 0xDE, 0xD4, 0x53, 0xF9, - 0x5C, 0xB9, 0x45, 0x53, 0xFB, 0x54, 0x9E, 0x8B, 0xF9, 0x51, 0xF6, 0xA9, 0x3D, 0x17, 0xF2, 0xAE, - 0x75, 0xE2, 0x46, 0x44, 0xDE, 0xF2, 0xFF, 0x00, 0xC0, 0x7F, 0xE0, 0x9C, 0x31, 0xF1, 0xFB, 0x83, - 0x1B, 0x49, 0xCA, 0xA2, 0xFF, 0x00, 0xB7, 0x3F, 0xE0, 0x97, 0x28, 0xA6, 0x43, 0x22, 0xC8, 0xB9, - 0x1D, 0x7B, 0x8A, 0x7D, 0x7D, 0xAE, 0x17, 0x17, 0x43, 0x17, 0x42, 0x35, 0xE8, 0x4B, 0x9A, 0x12, - 0xD5, 0x34, 0x7E, 0xBB, 0x96, 0xE6, 0x78, 0x3C, 0xCF, 0x07, 0x4F, 0x19, 0x82, 0xA8, 0xA7, 0x4A, - 0x6A, 0xEA, 0x4B, 0x66, 0xBF, 0xAD, 0x1A, 0x7A, 0xA7, 0xA3, 0xD4, 0x2A, 0x8D, 0xD7, 0x17, 0x0D, - 0xF5, 0xAB, 0xD5, 0x4E, 0xF8, 0x62, 0x6C, 0xFA, 0x8A, 0xF8, 0x3F, 0x13, 0x30, 0xEE, 0xA6, 0x4D, - 0x1A, 0x8B, 0xEC, 0x4D, 0x3F, 0x93, 0x4D, 0x7E, 0x6D, 0x1F, 0x8A, 0xFD, 0x21, 0xB0, 0x32, 0xAF, - 0xC2, 0x94, 0xEB, 0xC7, 0xFE, 0x5D, 0x55, 0x8B, 0x7E, 0x8D, 0x4A, 0x3F, 0x9B, 0x44, 0xBA, 0x79, - 0xFD, 0xDB, 0x0F, 0x7A, 0x99, 0x94, 0x32, 0xE1, 0x86, 0x6A, 0xB6, 0x9E, 0x7F, 0x78, 0xCB, 0xEA, - 0x2A, 0xD5, 0x7A, 0x7C, 0x13, 0x56, 0x9E, 0x2F, 0x87, 0x68, 0xC6, 0x69, 0x34, 0x93, 0x8B, 0x5D, - 0x34, 0x6F, 0xF4, 0xB1, 0xF4, 0x5E, 0x0F, 0xE2, 0x68, 0x66, 0x7C, 0x07, 0x84, 0xA7, 0x56, 0x2A, - 0x4A, 0x2A, 0x50, 0x92, 0x6A, 0xEB, 0x49, 0x3D, 0x1A, 0x7B, 0xDE, 0x36, 0xFB, 0xCA, 0xF2, 0x5A, - 0x8D, 0xB9, 0x42, 0x73, 0xE8, 0x6A, 0xAF, 0x4A, 0xD2, 0xAA, 0x97, 0xCA, 0x04, 0x81, 0x87, 0xF1, - 0x75, 0xAF, 0x90, 0xE3, 0xCE, 0x11, 0xC1, 0xE1, 0x70, 0x9F, 0xDA, 0x38, 0x28, 0xA8, 0x72, 0xB5, - 0xCC, 0x96, 0xCE, 0xED, 0x24, 0xD7, 0x6B, 0x37, 0xB2, 0xFD, 0x0F, 0xCB, 0x3C, 0x68, 0xF0, 0xBF, - 0x2B, 0xCB, 0xB2, 0xCF, 0xED, 0xDC, 0xA2, 0x9A, 0xA2, 0xA9, 0xB4, 0xAA, 0x41, 0x5E, 0xCD, 0x4A, - 0x49, 0x29, 0x45, 0x6C, 0x9A, 0x6D, 0x26, 0x96, 0x8D, 0x3B, 0xE9, 0x6D, 0x66, 0xB5, 0x93, 0xCC, - 0x4C, 0x1F, 0xBC, 0xB5, 0x2D, 0x53, 0xB1, 0x3F, 0xBE, 0x23, 0xDA, 0xAE, 0x57, 0xDA, 0x70, 0x56, - 0x65, 0x5B, 0x30, 0xC9, 0x69, 0xD4, 0xAE, 0xEF, 0x35, 0x78, 0xB7, 0xDE, 0xDB, 0x3F, 0xBA, 0xC7, - 0xEB, 0x9E, 0x10, 0xF1, 0x06, 0x2F, 0x3C, 0xE1, 0x1C, 0x3E, 0x23, 0x19, 0x2E, 0x6A, 0x90, 0x72, - 0x83, 0x93, 0xDE, 0x5C, 0xAF, 0x46, 0xFC, 0xED, 0x6B, 0xBE, 0xAF, 0x50, 0xA2, 0x8A, 0x2B, 0xEB, - 0x0F, 0xD3, 0x42, 0x8A, 0x28, 0xA0, 0x02, 0x8A, 0x28, 0xA0, 0x02, 0x8A, 0x28, 0xA0, 0x0A, 0xFA, - 0x80, 0xF9, 0x55, 0xBD, 0xF1, 0x50, 0xDA, 0x1C, 0x5C, 0x2D, 0x59, 0xBC, 0x5D, 0xD0, 0x9F, 0x6E, - 0x6A, 0x9A, 0x92, 0xAC, 0x08, 0xEC, 0x6B, 0xF0, 0xCE, 0x34, 0xBE, 0x5B, 0xC5, 0x54, 0xB1, 0xB6, - 0xD1, 0xF2, 0x4F, 0xD7, 0x95, 0xD9, 0xFE, 0x47, 0xF1, 0xAF, 0x8B, 0x49, 0xF0, 0xFF, 0x00, 0x89, - 0x38, 0x6C, 0xDF, 0x97, 0xDD, 0x6E, 0x95, 0x5D, 0x3A, 0xF2, 0x35, 0x19, 0x2F, 0x5B, 0x47, 0xF1, - 0x2F, 0xC8, 0xEB, 0x1A, 0xE5, 0x8D, 0x53, 0x9A, 0x56, 0x90, 0xF3, 0xC0, 0xF4, 0xA6, 0xC8, 0xEC, - 0xED, 0x96, 0x35, 0x25, 0xBC, 0x06, 0x4E, 0x4F, 0x0B, 0xFC, 0xEB, 0x3C, 0xE3, 0x88, 0xB3, 0x3E, - 0x29, 0xC5, 0xFD, 0x43, 0x2E, 0x8B, 0x54, 0x9F, 0x4E, 0xFE, 0x73, 0x7D, 0x17, 0x96, 0xDE, 0xAE, - 0xC6, 0x1C, 0x55, 0xC7, 0x9C, 0x45, 0xE2, 0x3E, 0x66, 0xB2, 0x4C, 0x86, 0x9C, 0xA3, 0x87, 0x7F, - 0x67, 0x67, 0x24, 0xBE, 0xD5, 0x47, 0xB2, 0x8A, 0xFE, 0x5B, 0xD9, 0x79, 0xBB, 0x0C, 0x86, 0x36, - 0x91, 0xB0, 0x3A, 0x77, 0x35, 0x7A, 0x35, 0x08, 0xA1, 0x47, 0x6A, 0x15, 0x42, 0xAE, 0x14, 0x60, - 0x52, 0xD7, 0xE9, 0x3C, 0x2B, 0xC2, 0x94, 0x32, 0x3A, 0x4E, 0x4D, 0xF3, 0x56, 0x97, 0xC4, 0xFA, - 0x7A, 0x2F, 0x2F, 0xCF, 0xF0, 0x3F, 0xA0, 0x7C, 0x35, 0xF0, 0xCF, 0x07, 0xC1, 0xB8, 0x69, 0x4E, - 0x52, 0xF6, 0x98, 0xAA, 0x8A, 0xD3, 0x9F, 0x44, 0xB7, 0xE5, 0x8F, 0x95, 0xF7, 0x6F, 0x59, 0x3D, - 0x5D, 0xB4, 0x48, 0xA2, 0x8A, 0x2B, 0xEB, 0x8F, 0xD4, 0x02, 0xB3, 0x6B, 0x4A, 0xB3, 0x6B, 0xF2, - 0x1F, 0x15, 0xB6, 0xC1, 0xFF, 0x00, 0xDB, 0xFF, 0x00, 0xFB, 0x61, 0xFC, 0xB3, 0xF4, 0x96, 0xDB, - 0x2B, 0xFF, 0x00, 0xB8, 0xDF, 0xFB, 0x88, 0xBD, 0x6B, 0xFF, 0x00, 0x1E, 0xEB, 0xF4, 0xA7, 0x96, - 0x03, 0xA9, 0x15, 0x9F, 0xB9, 0xB6, 0xE3, 0x71, 0xC7, 0xA6, 0x69, 0x2B, 0x97, 0x0D, 0xE2, 0x62, - 0xC2, 0xE0, 0xE9, 0x61, 0xE9, 0xE1, 0xAE, 0xE1, 0x18, 0xC6, 0xEE, 0x56, 0xD9, 0x25, 0xB5, 0x9F, - 0xE6, 0x79, 0xD9, 0x7F, 0xD2, 0x12, 0x39, 0x6E, 0x55, 0x86, 0xC0, 0xE1, 0xF2, 0xFE, 0x67, 0x4A, - 0x9C, 0x21, 0xCD, 0x2A, 0x96, 0x4D, 0xC6, 0x29, 0x37, 0x65, 0x17, 0xDB, 0xB9, 0x76, 0x4B, 0x88, - 0xD7, 0xA1, 0xDC, 0x7D, 0xAA, 0x9B, 0x1D, 0xCC, 0x4F, 0xA9, 0xA1, 0x41, 0x63, 0x80, 0x09, 0xFA, - 0x54, 0xF0, 0xDB, 0x13, 0xCC, 0x9F, 0x95, 0x78, 0xB8, 0xAC, 0x5E, 0x7D, 0xC6, 0x35, 0xE1, 0x4E, - 0x14, 0xED, 0x08, 0xBD, 0x2C, 0xAD, 0x15, 0xE6, 0xE4, 0xFA, 0xFF, 0x00, 0x49, 0x1F, 0x25, 0x99, - 0x66, 0x7C, 0x69, 0xE2, 0xBE, 0x36, 0x95, 0x0A, 0x58, 0x74, 0xA8, 0xC1, 0xBB, 0x72, 0xA6, 0xA9, - 0xC2, 0xFB, 0xB9, 0x4D, 0xDE, 0xEE, 0xDD, 0x2F, 0xFE, 0x18, 0xDC, 0x9A, 0xCC, 0x11, 0x6E, 0xB9, - 0xA9, 0x28, 0xE9, 0xC5, 0x15, 0xFB, 0xB6, 0x5D, 0x84, 0x58, 0x3C, 0x1D, 0x2C, 0x32, 0x77, 0xE4, - 0x8A, 0x8D, 0xFB, 0xD9, 0x58, 0xFE, 0xCE, 0xC8, 0x72, 0xB5, 0x94, 0xE5, 0x38, 0x6C, 0xBD, 0x4B, - 0x9B, 0xD8, 0xC2, 0x30, 0xBF, 0x7E, 0x58, 0xA5, 0x7F, 0x9D, 0x86, 0xCD, 0xCC, 0x4C, 0x3D, 0x8D, - 0x67, 0xD6, 0x95, 0x54, 0xBA, 0x80, 0xAB, 0x6E, 0x51, 0x95, 0xFE, 0x55, 0xF9, 0xCF, 0x89, 0x59, - 0x2E, 0x2B, 0x15, 0x4E, 0x96, 0x32, 0x84, 0x79, 0x94, 0x13, 0x52, 0x4B, 0x74, 0x9F, 0x5F, 0x4E, - 0xFF, 0x00, 0xF0, 0xE7, 0xE0, 0xBF, 0x48, 0x1E, 0x11, 0xCC, 0x73, 0x2A, 0x18, 0x6C, 0xDB, 0x05, - 0x07, 0x38, 0xD1, 0x52, 0x8C, 0xD2, 0xD5, 0xA4, 0xDA, 0x6A, 0x56, 0xEA, 0x96, 0xBC, 0xDD, 0xB4, - 0x7B, 0x5D, 0xA9, 0xED, 0xE4, 0x56, 0x8C, 0x00, 0x79, 0x03, 0xA5, 0x49, 0x59, 0xBD, 0x2A, 0x45, - 0x9E, 0x55, 0xFE, 0x2C, 0xFD, 0x6B, 0x93, 0x27, 0xF1, 0x32, 0x8C, 0x28, 0xC6, 0x96, 0x3A, 0x8B, - 0x4D, 0x24, 0xAF, 0x1D, 0x6F, 0x6F, 0x27, 0x6B, 0x7D, 0xEC, 0xF3, 0x38, 0x57, 0xE9, 0x0D, 0x84, - 0xA3, 0x85, 0xA7, 0x85, 0xCE, 0x70, 0xB2, 0x4E, 0x09, 0x47, 0x9E, 0x9D, 0x9A, 0x76, 0x56, 0xBB, - 0x8C, 0x9A, 0xB7, 0x9D, 0x9B, 0xF4, 0x2F, 0x51, 0x55, 0x56, 0xE9, 0x87, 0xDE, 0x50, 0x7E, 0x95, - 0x2A, 0x5C, 0x46, 0xDD, 0x4E, 0xDF, 0xAD, 0x7D, 0x9E, 0x03, 0x8D, 0x32, 0x3C, 0x6D, 0x94, 0x6B, - 0xA8, 0xBE, 0xD2, 0xF7, 0x7F, 0x17, 0xA7, 0xE2, 0x7E, 0xB5, 0x92, 0xF8, 0xB9, 0xC1, 0xB9, 0xBB, - 0x51, 0xA7, 0x8C, 0x54, 0xE4, 0xFA, 0x54, 0xF7, 0x3F, 0x19, 0x7B, 0xAF, 0xE4, 0xC9, 0x68, 0xA4, - 0x52, 0x18, 0x64, 0x1C, 0xFD, 0x29, 0x6B, 0xEA, 0x23, 0x38, 0xCE, 0x2A, 0x51, 0x77, 0x4C, 0xFD, - 0x1A, 0x9D, 0x48, 0x55, 0x82, 0x9D, 0x39, 0x27, 0x17, 0xB3, 0x5A, 0xA7, 0xE8, 0xC2, 0x8A, 0x28, - 0xAA, 0x2C, 0xA7, 0x7D, 0xFE, 0xB8, 0x7F, 0xBB, 0x4E, 0xD3, 0xFE, 0xF3, 0x7E, 0x14, 0x97, 0xE0, - 0xF9, 0xA0, 0xFB, 0x53, 0x2D, 0xA5, 0xF2, 0xD8, 0xE4, 0x64, 0x1E, 0xB5, 0xF8, 0x3D, 0x4C, 0x5D, - 0x0C, 0x0F, 0x1C, 0x4E, 0xBE, 0x25, 0xF2, 0xC1, 0x4D, 0xDD, 0xF6, 0xBC, 0x6C, 0x9F, 0xE2, 0x8F, - 0xE2, 0xBA, 0xF9, 0xA6, 0x0F, 0x26, 0xF1, 0x8E, 0xA6, 0x37, 0x30, 0x97, 0x25, 0x28, 0xD5, 0x95, - 0xDB, 0x4F, 0x4E, 0x6A, 0x6D, 0x45, 0xFA, 0x5D, 0xA7, 0x7E, 0xDA, 0x97, 0xA8, 0xA4, 0x56, 0x0C, - 0xB9, 0x53, 0x91, 0x4B, 0x5F, 0xBA, 0xD3, 0xA9, 0x0A, 0x90, 0x53, 0x83, 0xBA, 0x7A, 0xA6, 0xB6, - 0x67, 0xF6, 0x7D, 0x0A, 0xF4, 0xAB, 0xD2, 0x8D, 0x5A, 0x32, 0x52, 0x84, 0x95, 0xD3, 0x4E, 0xE9, - 0xA7, 0xB3, 0x4D, 0x6E, 0x86, 0xC8, 0x8A, 0xFF, 0x00, 0x79, 0x73, 0x55, 0xE6, 0xB6, 0x23, 0x98, - 0xF9, 0x1E, 0x95, 0x6A, 0x8A, 0xF0, 0xF3, 0x9E, 0x19, 0xCB, 0x73, 0x6A, 0x6D, 0x62, 0x29, 0xA5, - 0x3F, 0xE6, 0x5A, 0x49, 0x7C, 0xFA, 0xFA, 0x3B, 0xA3, 0xE3, 0x78, 0xB3, 0xC3, 0xDE, 0x1F, 0xE2, - 0x7A, 0x32, 0x8E, 0x36, 0x82, 0x55, 0x5E, 0xD5, 0x22, 0x92, 0x9A, 0x7E, 0xBD, 0x57, 0x94, 0xAE, - 0x8C, 0xDE, 0x41, 0xAB, 0xB6, 0x8E, 0x5E, 0x2C, 0x9E, 0xA0, 0xE2, 0x96, 0x58, 0x51, 0xDB, 0x24, - 0x60, 0xFB, 0x53, 0xA3, 0x50, 0x8B, 0xB5, 0x45, 0x7C, 0xA7, 0x0A, 0x70, 0x96, 0x67, 0x92, 0xE6, - 0x75, 0x27, 0x2A, 0xA9, 0xD0, 0x69, 0xAD, 0x3E, 0xD7, 0x6B, 0xAE, 0x8D, 0x7F, 0xC0, 0x47, 0xE6, - 0x9E, 0x19, 0xF8, 0x61, 0xC4, 0x3C, 0x23, 0xC4, 0x55, 0xAA, 0xD4, 0xC4, 0x46, 0x58, 0x47, 0x16, - 0xB4, 0x6F, 0xDF, 0x7A, 0x72, 0xB7, 0x0D, 0xA2, 0xE3, 0xDE, 0xEF, 0xB2, 0x6D, 0x36, 0x3A, 0x8A, - 0x28, 0xAF, 0xD2, 0x8F, 0xE8, 0x20, 0xAC, 0xE6, 0x24, 0xB1, 0x26, 0xB4, 0x6B, 0x36, 0xBF, 0x22, - 0xF1, 0x56, 0x4F, 0x97, 0x08, 0xBA, 0x7B, 0xFF, 0x00, 0xFB, 0x69, 0xFC, 0xB9, 0xF4, 0x96, 0x9C, - 0x95, 0x3C, 0xAE, 0x37, 0xD1, 0xBA, 0xBF, 0x87, 0xB3, 0xFF, 0x00, 0x36, 0x49, 0x1C, 0x32, 0x38, - 0xC8, 0x1C, 0x1E, 0xE4, 0xD4, 0x8B, 0x6A, 0xDD, 0xDC, 0x7E, 0x02, 0xA6, 0xB5, 0xFF, 0x00, 0x8F, - 0x75, 0xFA, 0x54, 0x95, 0xE9, 0xE4, 0x9C, 0x03, 0x92, 0xD4, 0xC1, 0x51, 0xC4, 0x57, 0x8C, 0xA7, - 0x29, 0xC6, 0x2D, 0xDE, 0x4D, 0x2B, 0xB4, 0x9E, 0x9C, 0xB6, 0xFC, 0xCF, 0xA2, 0xE0, 0xFF, 0x00, - 0x04, 0x78, 0x46, 0xBE, 0x51, 0x85, 0xC6, 0xE3, 0x21, 0x3A, 0xB3, 0xAB, 0x4E, 0x13, 0x69, 0xCD, - 0xA4, 0x9C, 0xA2, 0x9B, 0xB7, 0x27, 0x2B, 0xB6, 0xBA, 0x5D, 0xB2, 0xB7, 0xD9, 0x3F, 0xE9, 0xA7, - 0xE9, 0x4C, 0x9E, 0x03, 0x1A, 0xEE, 0xCE, 0xE1, 0xDF, 0x8A, 0xB9, 0x48, 0xEA, 0x19, 0x4A, 0x9E, - 0xF5, 0xDD, 0x98, 0x78, 0x7D, 0x92, 0xD4, 0xC2, 0x54, 0x8E, 0x1A, 0x97, 0x2D, 0x4B, 0x3E, 0x57, - 0xCD, 0x27, 0xAF, 0x4D, 0xDB, 0x5E, 0xA7, 0xAF, 0x9E, 0x78, 0x1B, 0xC2, 0x35, 0xB2, 0xCA, 0xF0, - 0xCB, 0xB0, 0xDE, 0xCE, 0xBB, 0x8B, 0xE4, 0x97, 0x3D, 0x47, 0x69, 0x74, 0xBA, 0x94, 0x9A, 0xB3, - 0x7A, 0x3D, 0x36, 0x7A, 0x6A, 0x51, 0x81, 0xF6, 0x48, 0x1B, 0xB7, 0x7A, 0xBF, 0x59, 0xA4, 0x60, - 0xE0, 0xD5, 0xEB, 0x53, 0x98, 0x14, 0xD7, 0xCD, 0xF8, 0x63, 0x99, 0x54, 0x55, 0x2B, 0xE5, 0xD3, - 0xD9, 0x7B, 0xCB, 0xCB, 0x54, 0xA4, 0xBF, 0x14, 0x7C, 0x07, 0xD1, 0xD7, 0x88, 0x2B, 0xC6, 0xBE, - 0x2F, 0x21, 0xAD, 0xB2, 0x5E, 0xD2, 0x3E, 0x4D, 0x35, 0x19, 0xAF, 0x9D, 0xE2, 0xFD, 0x53, 0xEE, - 0x49, 0x55, 0xF5, 0x01, 0xC2, 0x9A, 0xB1, 0x50, 0x6A, 0x1F, 0xEA, 0xD7, 0xEB, 0x5F, 0x6D, 0xC6, - 0xD4, 0xD5, 0x4E, 0x1F, 0xC4, 0xA7, 0xD9, 0x3F, 0xBA, 0x49, 0x9F, 0xB0, 0x78, 0xBF, 0x42, 0x35, - 0xB8, 0x1B, 0x30, 0x8C, 0xBA, 0x46, 0x2F, 0xE7, 0x19, 0xC5, 0xAF, 0xC8, 0x86, 0xCC, 0xE2, 0xE0, - 0x7B, 0xD5, 0xDA, 0xCF, 0x8D, 0x8A, 0x38, 0x61, 0xD4, 0x53, 0xDA, 0xE2, 0x53, 0xDF, 0x1F, 0x41, - 0x5F, 0x9C, 0xF0, 0x7F, 0x18, 0xE0, 0x32, 0x6C, 0xAE, 0x74, 0x31, 0x2A, 0x4E, 0x7C, 0xCD, 0xA4, - 0x97, 0x46, 0xA3, 0xD5, 0xB5, 0xD6, 0xE7, 0xE0, 0x9E, 0x15, 0xF8, 0xAD, 0x92, 0xF0, 0x9F, 0x0E, - 0x55, 0xC1, 0xE3, 0xD4, 0xE7, 0x53, 0xDA, 0x4A, 0x51, 0x8C, 0x52, 0x7E, 0xEB, 0x8C, 0x16, 0xED, - 0xA4, 0xB5, 0x4C, 0xB6, 0xEE, 0xA8, 0x32, 0xC7, 0x15, 0x4A, 0xE2, 0x43, 0x24, 0x99, 0xED, 0xD8, - 0x53, 0x09, 0x24, 0xE4, 0x9A, 0x92, 0x08, 0x5A, 0x46, 0xF4, 0x5E, 0xE6, 0xB8, 0xB3, 0x9E, 0x26, - 0xCC, 0xB8, 0xA6, 0xA4, 0x30, 0x18, 0x4A, 0x5C, 0xB0, 0x6F, 0xE1, 0x5A, 0xB7, 0xE7, 0x27, 0xA5, - 0x92, 0xDF, 0xB2, 0xDD, 0xF4, 0x3C, 0x7E, 0x2C, 0xF1, 0x0F, 0x3F, 0xF1, 0x22, 0xBD, 0x2C, 0x97, - 0x2C, 0xC3, 0x38, 0x53, 0x94, 0xAF, 0xC8, 0x9F, 0x33, 0x93, 0x5B, 0x39, 0xCA, 0xC9, 0x28, 0xC7, - 0x7E, 0x89, 0x6E, 0xDB, 0xB2, 0xB4, 0x96, 0x0A, 0x77, 0x17, 0xED, 0x8C, 0x55, 0xAA, 0x45, 0x50, - 0xAB, 0xB4, 0x0E, 0x05, 0x2D, 0x7E, 0xC3, 0xC3, 0x99, 0x32, 0xC9, 0xF2, 0xDA, 0x78, 0x4B, 0xDE, - 0x4B, 0x56, 0xFC, 0xDE, 0xF6, 0xF2, 0xE8, 0x8F, 0xEA, 0xAE, 0x01, 0xE1, 0x38, 0xF0, 0xAE, 0x41, - 0x47, 0x2C, 0xE7, 0xE6, 0x9A, 0xBB, 0x93, 0xE8, 0xE5, 0x2D, 0x5D, 0xBC, 0x96, 0xCB, 0xC9, 0x5C, - 0x28, 0xA2, 0x8A, 0xF7, 0x0F, 0xB2, 0x0A, 0x28, 0xA2, 0x80, 0x0A, 0x28, 0xA2, 0x80, 0x0A, 0x28, - 0xA2, 0x80, 0x1B, 0x20, 0x26, 0x36, 0x03, 0xB8, 0xAC, 0xF2, 0x08, 0x38, 0x35, 0xA5, 0x54, 0x6E, - 0x86, 0x2E, 0x1A, 0xBF, 0x27, 0xF1, 0x4B, 0x00, 0x9D, 0x0A, 0x18, 0xDB, 0xEA, 0x9F, 0x27, 0xDE, - 0x9B, 0xFD, 0x0F, 0xE6, 0x4F, 0xA4, 0x7E, 0x4B, 0x19, 0x60, 0xB0, 0x79, 0xB2, 0x6E, 0xF1, 0x93, - 0xA6, 0xD7, 0x4B, 0x49, 0x39, 0x27, 0xFF, 0x00, 0x92, 0xBF, 0xBC, 0x7D, 0xAC, 0x2A, 0xE3, 0x7B, - 0x1C, 0x8C, 0xF4, 0xAB, 0x7D, 0x3A, 0x55, 0x7D, 0x3C, 0xFE, 0xED, 0x87, 0xBD, 0x58, 0xAF, 0xA8, - 0xE0, 0x6C, 0x1E, 0x12, 0x8E, 0x4B, 0x46, 0xB5, 0x08, 0x25, 0x29, 0xAB, 0xC9, 0xF5, 0x6D, 0x36, - 0xB7, 0xFD, 0x36, 0x47, 0xE8, 0xDE, 0x0D, 0xE5, 0x59, 0x5E, 0x17, 0x84, 0x70, 0xB8, 0xAC, 0x1D, - 0x25, 0x19, 0xD5, 0x8D, 0xE7, 0x2E, 0xB2, 0x92, 0x6D, 0x3B, 0xB7, 0xAD, 0x93, 0x4E, 0xCB, 0x65, - 0xD1, 0x05, 0x14, 0x51, 0x5F, 0x60, 0x7E, 0xAA, 0x14, 0x51, 0x45, 0x00, 0x15, 0x9B, 0x5A, 0x55, - 0x9B, 0x5F, 0x90, 0xF8, 0xAD, 0xB6, 0x0F, 0xFE, 0xDF, 0xFF, 0x00, 0xDB, 0x0F, 0xE5, 0x9F, 0xA4, - 0xB6, 0xD9, 0x5F, 0xFD, 0xC6, 0xFF, 0x00, 0xDC, 0x65, 0xA8, 0x20, 0x8D, 0xA3, 0x56, 0x39, 0xC9, - 0xF7, 0xA9, 0x16, 0x18, 0x87, 0xF0, 0x0F, 0xC6, 0x8B, 0x5F, 0xF8, 0xF7, 0x5F, 0xA5, 0x49, 0x5F, - 0x6B, 0x90, 0xE4, 0x79, 0x5C, 0x72, 0xEC, 0x3D, 0x5F, 0xAB, 0xC3, 0x99, 0xC2, 0x2D, 0xBE, 0x54, - 0xDD, 0xF9, 0x56, 0xB7, 0xB1, 0xFA, 0xEF, 0x05, 0xF0, 0x6F, 0x0D, 0xC3, 0x22, 0xC0, 0xE2, 0x56, - 0x02, 0x97, 0xB4, 0x95, 0x2A, 0x72, 0x72, 0x70, 0x8B, 0x6D, 0xB8, 0x26, 0xDD, 0xDA, 0xBD, 0xDB, - 0x11, 0x40, 0x5E, 0x80, 0x0F, 0xA5, 0x2D, 0x14, 0x57, 0xD3, 0xC2, 0x11, 0x84, 0x54, 0x62, 0xAC, - 0x8F, 0xD1, 0x69, 0x52, 0xA7, 0x4A, 0x0A, 0x14, 0xE2, 0x94, 0x57, 0x44, 0xAC, 0x82, 0x8A, 0x28, - 0xAA, 0x34, 0x0A, 0x28, 0xA2, 0x80, 0x19, 0x24, 0x51, 0xBF, 0xDE, 0x5F, 0xC4, 0x54, 0x4D, 0x6A, - 0x3F, 0x85, 0xCF, 0xE2, 0x2A, 0xC5, 0x15, 0xE0, 0xE3, 0xF8, 0x63, 0x27, 0xC7, 0xC9, 0xCB, 0x11, - 0x87, 0x8B, 0x93, 0xEA, 0xB4, 0x7F, 0x7C, 0x6C, 0xCF, 0x8B, 0xCE, 0xFC, 0x3B, 0xE1, 0x4C, 0xEE, - 0xA3, 0xA9, 0x8E, 0xC0, 0xC2, 0x53, 0x7B, 0xC9, 0x5E, 0x12, 0x7E, 0xB2, 0x83, 0x8B, 0x7F, 0x36, - 0x53, 0x7B, 0x69, 0x07, 0x4C, 0x35, 0x44, 0xCA, 0xCB, 0xF7, 0x94, 0x8A, 0xD1, 0xA4, 0x60, 0x0A, - 0xE0, 0x8E, 0x2B, 0xE4, 0x33, 0x1F, 0x0C, 0x72, 0xFA, 0xA9, 0xCB, 0x07, 0x52, 0x54, 0xDF, 0x67, - 0xEF, 0x2F, 0xF3, 0xFC, 0x59, 0xF9, 0x66, 0x7D, 0xF4, 0x77, 0xC8, 0xF1, 0x2A, 0x53, 0xCA, 0xAB, - 0xCE, 0x8C, 0xBA, 0x29, 0x7B, 0xF0, 0xF2, 0x5A, 0xDA, 0x4B, 0xD7, 0x99, 0xFA, 0x14, 0x61, 0x91, - 0xA3, 0x6C, 0x8F, 0xC4, 0x55, 0xE5, 0x60, 0xCA, 0x18, 0x74, 0x35, 0x42, 0x64, 0xD9, 0x21, 0x5A, - 0xB1, 0x60, 0xD9, 0x8C, 0xAF, 0xA1, 0xAF, 0x17, 0xC3, 0xFC, 0xD3, 0x13, 0x82, 0xCC, 0xAA, 0x64, - 0xD8, 0x97, 0xA7, 0xBD, 0x65, 0xDA, 0x51, 0xDE, 0xDE, 0x4D, 0x27, 0xF3, 0x47, 0xC8, 0x78, 0x1F, - 0xC4, 0x79, 0x8E, 0x51, 0xC4, 0x15, 0xB8, 0x53, 0x1E, 0xDF, 0x2B, 0x73, 0x4A, 0x2D, 0xDF, 0x92, - 0xA4, 0x2F, 0xCC, 0x97, 0x94, 0x92, 0x77, 0xF3, 0x49, 0xAD, 0xD9, 0x62, 0x8A, 0x28, 0xAF, 0xD9, - 0xCF, 0xEB, 0x61, 0xB2, 0xA2, 0xC8, 0xB8, 0x6A, 0xA7, 0x3C, 0x4D, 0x19, 0xE7, 0x91, 0xD8, 0xD5, - 0xEA, 0x46, 0x01, 0x97, 0x04, 0x64, 0x57, 0xC9, 0x71, 0x37, 0x08, 0xE0, 0xF3, 0xAA, 0x6E, 0x7F, - 0x0D, 0x64, 0xB4, 0x97, 0xE9, 0x2E, 0xEB, 0xF1, 0x5D, 0x3B, 0x1F, 0x98, 0x78, 0x85, 0xE1, 0x7E, - 0x55, 0xC5, 0xD4, 0x5D, 0x5B, 0x7B, 0x3C, 0x5A, 0x56, 0x8D, 0x45, 0xD6, 0xDB, 0x29, 0xAF, 0xB4, - 0xBC, 0xF7, 0x5D, 0x3B, 0x3A, 0x30, 0xC8, 0xD1, 0xB6, 0x47, 0xE2, 0x2A, 0xE4, 0x32, 0xAC, 0x8B, - 0xC7, 0x5E, 0xE2, 0xAB, 0xDC, 0x5B, 0x95, 0xF9, 0x93, 0x95, 0xF4, 0xF4, 0xA8, 0x55, 0x8A, 0xB6, - 0x54, 0xE0, 0xD7, 0xE6, 0x59, 0x6E, 0x77, 0x9B, 0xF0, 0x96, 0x2F, 0xEA, 0x78, 0xD8, 0x37, 0x4B, - 0xB7, 0x97, 0x78, 0x3F, 0xD3, 0x6E, 0xF6, 0x67, 0xF3, 0xBE, 0x41, 0xC5, 0xFC, 0x53, 0xE1, 0x7E, - 0x67, 0xFD, 0x93, 0x9B, 0xD3, 0x73, 0xC3, 0x5F, 0xE0, 0x6E, 0xEA, 0xDF, 0xCD, 0x4A, 0x5B, 0x79, - 0xDB, 0x66, 0xF4, 0x6A, 0x32, 0xD5, 0x68, 0xD1, 0x50, 0xC1, 0x70, 0xAD, 0x85, 0x6E, 0x1B, 0xF9, - 0xD4, 0xD5, 0xFB, 0x4E, 0x59, 0x9A, 0xE0, 0xF3, 0x3A, 0x0A, 0xBE, 0x16, 0x6A, 0x51, 0xFC, 0x57, - 0x93, 0x5D, 0x19, 0xFD, 0x73, 0xC3, 0xBC, 0x4B, 0x95, 0x71, 0x0E, 0x09, 0x63, 0x32, 0xDA, 0xCA, - 0x70, 0x7B, 0xF7, 0x8B, 0xED, 0x25, 0xBA, 0x7E, 0xBF, 0x2D, 0x02, 0x8A, 0x28, 0xAF, 0x44, 0xF7, - 0x82, 0x8A, 0x28, 0xA0, 0x02, 0xB3, 0x4F, 0x5A, 0xD2, 0xAC, 0xF9, 0x86, 0x26, 0x61, 0xEF, 0x5F, - 0x93, 0xF8, 0xA9, 0x45, 0xBA, 0x18, 0x5A, 0xBD, 0x9C, 0x97, 0xDE, 0x93, 0xFD, 0x0F, 0xE6, 0x4F, - 0xA4, 0xA6, 0x15, 0xCB, 0x05, 0x97, 0x62, 0x7A, 0x46, 0x55, 0x23, 0xFF, 0x00, 0x81, 0x28, 0xBF, - 0xFD, 0xB4, 0xB9, 0x6B, 0xFF, 0x00, 0x1E, 0xEB, 0x52, 0x55, 0x04, 0x96, 0x45, 0x5D, 0xAA, 0xD8, - 0x1F, 0x4A, 0x5F, 0x3E, 0x5F, 0xEF, 0xFE, 0x82, 0xAB, 0x2C, 0xF1, 0x1F, 0x2B, 0xC3, 0x60, 0x68, - 0xD0, 0xA9, 0x4E, 0x7C, 0xD0, 0x8C, 0x62, 0xEC, 0xA3, 0x6B, 0xA4, 0x96, 0x9E, 0xF1, 0xA7, 0x0E, - 0xF8, 0xFB, 0xC3, 0x99, 0x76, 0x4D, 0x84, 0xC1, 0x62, 0x30, 0xF5, 0x9C, 0xE9, 0x53, 0x84, 0x1F, - 0x2C, 0x61, 0x6B, 0xC6, 0x29, 0x3B, 0x5E, 0xA2, 0x76, 0xD3, 0x4B, 0xA4, 0x5E, 0xA8, 0x66, 0xB8, - 0x44, 0xE0, 0x7C, 0xCD, 0x55, 0x5A, 0x59, 0x1B, 0xAB, 0x9A, 0x45, 0x05, 0x8E, 0x00, 0xC9, 0xAE, - 0x0C, 0xD7, 0xC4, 0xBA, 0xD8, 0x88, 0xFB, 0x1C, 0xB2, 0x93, 0x8C, 0x9E, 0x97, 0x7A, 0xBF, 0x94, - 0x55, 0xD5, 0xFE, 0xFF, 0x00, 0x43, 0xC5, 0xE2, 0x5F, 0xA4, 0x26, 0x2F, 0x1B, 0x4F, 0xEA, 0xBC, - 0x3D, 0x86, 0x70, 0x9C, 0xB4, 0xE7, 0x9D, 0xA5, 0x2D, 0x76, 0xE5, 0x82, 0xBA, 0xBF, 0xAB, 0x97, - 0xA0, 0x31, 0x25, 0x89, 0x3D, 0xCD, 0x5D, 0xB5, 0x04, 0x40, 0xA0, 0xD4, 0x70, 0x5B, 0x63, 0xE6, - 0x93, 0xF2, 0xAB, 0x15, 0xEB, 0x70, 0x0F, 0x0C, 0xE3, 0x70, 0x35, 0x67, 0x98, 0x63, 0x57, 0x2C, - 0xA6, 0xAC, 0xA2, 0xF7, 0xD5, 0xA6, 0xDB, 0xED, 0xB6, 0xDB, 0xF7, 0x3E, 0x9B, 0xC1, 0x3F, 0x0F, - 0x33, 0x7C, 0x9F, 0x13, 0x5B, 0x3D, 0xCD, 0xD3, 0x85, 0x4A, 0xB1, 0x71, 0x8C, 0x1F, 0xC5, 0x69, - 0x35, 0x27, 0x29, 0x76, 0x6D, 0xA5, 0x64, 0xF5, 0xDE, 0xF6, 0x0A, 0x83, 0x50, 0xFF, 0x00, 0x56, - 0xBF, 0x5A, 0x9E, 0xA0, 0xD4, 0x3F, 0xD5, 0xAF, 0xD6, 0xBE, 0xA7, 0x8C, 0xBF, 0xE4, 0x41, 0x8A, - 0xFF, 0x00, 0x0F, 0xEA, 0x8F, 0xD2, 0x3C, 0x58, 0xFF, 0x00, 0x92, 0x23, 0x31, 0xFF, 0x00, 0x07, - 0xFE, 0xDC, 0x8A, 0xD1, 0xA9, 0x77, 0x0A, 0x3B, 0xD4, 0xEB, 0x6B, 0xFD, 0xE7, 0xFC, 0x85, 0x45, - 0x69, 0xFF, 0x00, 0x1F, 0x0B, 0xFE, 0x7B, 0x55, 0xEA, 0xFC, 0xFB, 0x80, 0xB8, 0x67, 0x2B, 0xCC, - 0xF0, 0x13, 0xC4, 0xE3, 0x29, 0xF3, 0x49, 0x4D, 0xA5, 0xAB, 0x4A, 0xC9, 0x45, 0xEC, 0x9A, 0xEE, - 0x7E, 0x1B, 0xE0, 0xA7, 0x87, 0x7C, 0x39, 0xC4, 0x59, 0x35, 0x5C, 0xC3, 0x34, 0xA1, 0xED, 0x2A, - 0x46, 0xAB, 0x8A, 0xF7, 0xA4, 0x95, 0x94, 0x61, 0x25, 0xA4, 0x5A, 0xBE, 0xAD, 0xEE, 0x47, 0x1C, - 0x11, 0xAF, 0xF0, 0xE7, 0xEB, 0x52, 0x51, 0x45, 0x7E, 0xB9, 0x83, 0xC0, 0x61, 0x70, 0x70, 0xF6, - 0x78, 0x6A, 0x6A, 0x0B, 0xC9, 0x24, 0x7F, 0x51, 0x65, 0x79, 0x2E, 0x5B, 0x94, 0xD1, 0xF6, 0x39, - 0x7D, 0x08, 0xD2, 0x8F, 0x68, 0xC5, 0x2F, 0xBE, 0xDB, 0xFC, 0xC2, 0x8A, 0x28, 0xAE, 0xB3, 0xD3, - 0x0A, 0x28, 0xA2, 0x80, 0x0A, 0x28, 0xA2, 0x80, 0x0A, 0x28, 0xA2, 0x80, 0x0A, 0x28, 0xA2, 0x80, - 0x0A, 0xA7, 0x7C, 0x31, 0x3F, 0xD4, 0x55, 0xCA, 0xA9, 0x7E, 0x3F, 0x7C, 0x0F, 0xB5, 0x7C, 0x17, - 0x89, 0x10, 0xE6, 0xC8, 0x9B, 0xB6, 0xD2, 0x8B, 0xFC, 0xD7, 0xEA, 0x7E, 0x29, 0xE3, 0xF5, 0x15, - 0x3E, 0x0C, 0x94, 0xAD, 0xF0, 0xD4, 0x83, 0xF4, 0xDD, 0x7E, 0xB6, 0x1D, 0xA7, 0xFF, 0x00, 0x1F, - 0xE1, 0x56, 0x6A, 0xAE, 0x9E, 0x7F, 0x78, 0xC3, 0xDA, 0xAD, 0x57, 0x57, 0x87, 0xF5, 0x14, 0xB8, - 0x7A, 0x82, 0x5D, 0x1C, 0x97, 0xFE, 0x4C, 0xDF, 0xEA, 0x7A, 0x3E, 0x07, 0xD7, 0x8D, 0x4E, 0x04, - 0xC2, 0x45, 0x3D, 0x62, 0xEA, 0x27, 0xEB, 0xED, 0x24, 0xFF, 0x00, 0x26, 0x82, 0x8A, 0x28, 0xAF, - 0xB3, 0x3F, 0x5A, 0x0A, 0x28, 0xA2, 0x80, 0x0A, 0xCD, 0xAD, 0x2A, 0xCD, 0xAF, 0xC8, 0x7C, 0x56, - 0xDB, 0x07, 0xFF, 0x00, 0x6F, 0xFF, 0x00, 0xED, 0x87, 0xF2, 0xCF, 0xD2, 0x5B, 0x6C, 0xAF, 0xFE, - 0xE3, 0x7F, 0xEE, 0x22, 0xF5, 0xAF, 0xFC, 0x7B, 0xAF, 0xD2, 0xA4, 0xA8, 0xED, 0x7F, 0xE3, 0xDD, - 0x7E, 0x95, 0x25, 0x7E, 0x95, 0x91, 0xFF, 0x00, 0xC8, 0xAB, 0x0D, 0xFF, 0x00, 0x5E, 0xE1, 0xFF, - 0x00, 0xA4, 0xA3, 0xFA, 0x0B, 0x83, 0x7F, 0xE4, 0x9A, 0xCB, 0xBF, 0xEB, 0xCD, 0x2F, 0xFD, 0x22, - 0x21, 0x45, 0x14, 0x57, 0xA8, 0x7D, 0x20, 0x54, 0x73, 0x4C, 0xB1, 0xF0, 0x79, 0x3E, 0x95, 0x25, - 0x56, 0xBE, 0x43, 0x9F, 0x30, 0x7E, 0x35, 0xF3, 0x9C, 0x55, 0x8F, 0xC7, 0x60, 0x32, 0xAA, 0x98, - 0x8C, 0x14, 0x6F, 0x38, 0xDB, 0xA5, 0xEC, 0xBA, 0xBB, 0x79, 0x7F, 0xC1, 0x3E, 0x0B, 0xC4, 0xBC, - 0xEB, 0x39, 0xC9, 0x78, 0x6A, 0xB6, 0x3B, 0x27, 0x82, 0x95, 0x58, 0x5A, 0xF7, 0x57, 0xE5, 0x8D, - 0xFD, 0xE9, 0x5B, 0xAD, 0xBF, 0x05, 0x77, 0xD0, 0x9A, 0x19, 0x16, 0x45, 0xCA, 0xFE, 0x22, 0x9F, - 0x59, 0xF1, 0xBB, 0x23, 0x6E, 0x53, 0x56, 0xA2, 0xB8, 0x46, 0xE0, 0xFC, 0xA7, 0xDE, 0xBC, 0x2E, - 0x17, 0xE3, 0xAC, 0x26, 0x61, 0x49, 0x51, 0xC7, 0x4D, 0x42, 0xB2, 0xEF, 0xA4, 0x65, 0xE9, 0xD9, - 0xF7, 0x5F, 0x77, 0x97, 0xC5, 0x78, 0x73, 0xE3, 0x36, 0x5B, 0x9E, 0x61, 0xA3, 0x85, 0xCE, 0x2A, - 0xC6, 0x8E, 0x2D, 0x69, 0x77, 0xEE, 0xC2, 0x7D, 0x9C, 0x5B, 0xD1, 0x3E, 0xF1, 0x7D, 0x7E, 0x1D, - 0x34, 0x53, 0x51, 0x46, 0x68, 0xCD, 0x7D, 0xFF, 0x00, 0x32, 0x67, 0xEE, 0x0A, 0x51, 0x6A, 0xE9, - 0x85, 0x14, 0x64, 0x7A, 0xD4, 0x32, 0x5C, 0x46, 0xBD, 0x3E, 0x63, 0xED, 0x5C, 0x18, 0xFC, 0xD7, - 0x03, 0x97, 0xD3, 0xE7, 0xC5, 0x55, 0x50, 0x5E, 0x6F, 0x57, 0xE8, 0xB7, 0x7F, 0x23, 0xC5, 0xCE, - 0xF8, 0x9B, 0x26, 0xC8, 0xE8, 0xFB, 0x6C, 0xCB, 0x13, 0x1A, 0x51, 0xE9, 0x77, 0xAB, 0xF4, 0x8E, - 0xEF, 0xE4, 0x99, 0x0D, 0xF7, 0xFA, 0xE1, 0xF4, 0xA7, 0x69, 0xFF, 0x00, 0x79, 0xBE, 0x95, 0x14, - 0xD2, 0x19, 0x1F, 0x71, 0x18, 0xA9, 0x74, 0xF1, 0xF3, 0x31, 0xAF, 0xC5, 0x72, 0x7C, 0x5D, 0x2C, - 0x77, 0x1B, 0x47, 0x11, 0x87, 0xD6, 0x12, 0x94, 0x9A, 0xD2, 0xDA, 0x72, 0xB3, 0xF9, 0x13, 0x85, - 0xB3, 0x3C, 0x36, 0x71, 0xE2, 0xFC, 0x31, 0xF8, 0x1B, 0xBA, 0x55, 0x2A, 0x4E, 0x49, 0xDA, 0xDA, - 0x7B, 0x39, 0x6B, 0x6E, 0x9F, 0x32, 0xD5, 0x14, 0x53, 0x65, 0x71, 0x1A, 0xEE, 0x6A, 0xFD, 0xD6, - 0xBD, 0x7A, 0x54, 0x29, 0x4A, 0xAD, 0x59, 0x72, 0xC6, 0x3A, 0xB6, 0xFA, 0x23, 0xFB, 0x3B, 0x19, - 0x8C, 0xC3, 0xE0, 0xB0, 0xF3, 0xC4, 0xE2, 0x66, 0xA1, 0x4E, 0x0A, 0xF2, 0x6F, 0x44, 0x92, 0xEA, - 0xC7, 0x51, 0x51, 0x43, 0x3A, 0x48, 0xD8, 0xE8, 0x7D, 0xEA, 0x5A, 0xC3, 0x03, 0x98, 0x61, 0x31, - 0xF4, 0x7D, 0xB6, 0x16, 0xA2, 0x9C, 0x76, 0xBA, 0x38, 0xB2, 0x5C, 0xF7, 0x2D, 0xCE, 0xF0, 0xAB, - 0x17, 0x96, 0xD6, 0x55, 0x69, 0xDE, 0xD7, 0x5D, 0xD7, 0x47, 0xD5, 0x3F, 0x26, 0x15, 0x05, 0xC5, - 0xBE, 0xEF, 0x99, 0x38, 0x3E, 0x95, 0x3D, 0x15, 0x86, 0x6D, 0x93, 0xE0, 0xF3, 0x5C, 0x3B, 0xC3, - 0xE2, 0xA1, 0x75, 0xD1, 0xF5, 0x4F, 0xBA, 0x7D, 0x3F, 0xAB, 0x9C, 0x7C, 0x4D, 0xC2, 0xB9, 0x57, - 0x12, 0xE0, 0x1E, 0x0B, 0x32, 0xA7, 0xCD, 0x1D, 0xD3, 0xDA, 0x51, 0x7D, 0xE2, 0xFA, 0x3F, 0xC1, - 0xEC, 0xD3, 0x46, 0x73, 0x02, 0xAD, 0x82, 0x30, 0x6A, 0x58, 0x2E, 0x19, 0x78, 0x7F, 0x99, 0x6A, - 0xCC, 0xB1, 0x24, 0x83, 0xE6, 0x1C, 0xFA, 0xD5, 0x49, 0xA2, 0x68, 0xCF, 0x3C, 0x8F, 0x5A, 0xFC, - 0x63, 0x31, 0xC8, 0x33, 0xAE, 0x14, 0xC4, 0x3C, 0x66, 0x0A, 0x6E, 0x54, 0xBB, 0xAE, 0xDD, 0xA7, - 0x1F, 0xD7, 0x6F, 0x46, 0x7F, 0x25, 0xE7, 0xDC, 0x13, 0xC5, 0xDE, 0x1A, 0x63, 0x9E, 0x6B, 0x93, - 0xD6, 0x73, 0xC3, 0xA7, 0xF1, 0x47, 0xB7, 0x6A, 0xB0, 0xDA, 0xDE, 0x7A, 0xC7, 0xAD, 0xE2, 0xEC, - 0x8B, 0xA8, 0xCA, 0xCB, 0x95, 0x39, 0xA5, 0xAA, 0x10, 0x48, 0x63, 0x7C, 0xF6, 0xEE, 0x2A, 0xF2, - 0x90, 0xCA, 0x08, 0xE8, 0x6B, 0xF4, 0xEE, 0x14, 0xE2, 0x8A, 0x59, 0xE6, 0x1D, 0xDD, 0x72, 0xD5, - 0x8F, 0xC5, 0x1F, 0xD5, 0x79, 0x3F, 0xC3, 0xEE, 0x3F, 0xA2, 0x7C, 0x33, 0xF1, 0x1F, 0x0D, 0xC6, - 0x58, 0x09, 0x39, 0x45, 0x43, 0x13, 0x4F, 0xE3, 0x82, 0xDA, 0xCF, 0x69, 0x46, 0xFF, 0x00, 0x65, - 0xFD, 0xE9, 0xE8, 0xFA, 0x36, 0xB4, 0x51, 0x45, 0x7D, 0x61, 0xFA, 0x68, 0x55, 0x1B, 0xBF, 0xF8, - 0xF8, 0x6A, 0xBD, 0x54, 0x6E, 0xFF, 0x00, 0xE3, 0xE1, 0xBF, 0xCF, 0x6A, 0xFC, 0xDB, 0xC5, 0x0F, - 0xF9, 0x14, 0x52, 0xFF, 0x00, 0xAF, 0x8B, 0xFF, 0x00, 0x49, 0x91, 0xFC, 0xFF, 0x00, 0xF4, 0x8C, - 0xFF, 0x00, 0x92, 0x5B, 0x0D, 0xFF, 0x00, 0x5F, 0xE3, 0xFF, 0x00, 0xA6, 0xEA, 0x0E, 0xB6, 0x85, - 0x64, 0x42, 0x49, 0x23, 0x9E, 0xD5, 0x2F, 0xD9, 0x53, 0xFB, 0xCD, 0x45, 0x87, 0xFA, 0xA3, 0xFE, - 0xF5, 0x4F, 0x5B, 0x70, 0xCF, 0x0B, 0xE4, 0xD8, 0xAC, 0x9F, 0x0F, 0x5A, 0xB6, 0x1D, 0x4A, 0x72, - 0x8E, 0xAF, 0x5D, 0x75, 0xF5, 0x3A, 0xFC, 0x3B, 0xF0, 0xE7, 0x84, 0xF3, 0x1E, 0x14, 0xC0, 0xE2, - 0xF1, 0x78, 0x18, 0x4E, 0xAC, 0xE1, 0x79, 0x49, 0xDE, 0xED, 0xDD, 0xF9, 0x90, 0xAD, 0xAC, 0x63, - 0xA9, 0x63, 0xF8, 0xD4, 0x91, 0xA2, 0xA7, 0x0A, 0xB8, 0xA7, 0x51, 0x5F, 0x5D, 0x81, 0xC8, 0xF2, - 0xCC, 0x0C, 0xB9, 0xB0, 0xD4, 0x23, 0x19, 0x77, 0x4B, 0x5F, 0xBF, 0x73, 0xF4, 0xFC, 0x9F, 0x83, - 0x78, 0x77, 0x26, 0x9F, 0xB4, 0xCB, 0xF0, 0x54, 0xE9, 0xCF, 0xF9, 0x94, 0x57, 0x37, 0xFE, 0x04, - 0xEE, 0xFF, 0x00, 0x10, 0xA2, 0x8A, 0x2B, 0xD5, 0x3E, 0x98, 0x2A, 0x0D, 0x43, 0xFD, 0x5A, 0xFD, - 0x6A, 0x7A, 0x83, 0x50, 0xFF, 0x00, 0x56, 0xBF, 0x5A, 0xF9, 0x9E, 0x32, 0xFF, 0x00, 0x91, 0x06, - 0x2B, 0xFC, 0x3F, 0xAA, 0x3F, 0x3C, 0xF1, 0x63, 0xFE, 0x48, 0x8C, 0xC7, 0xFC, 0x1F, 0xFB, 0x72, - 0x20, 0xB4, 0xFF, 0x00, 0x8F, 0x85, 0xFF, 0x00, 0x3D, 0xAA, 0xF5, 0x51, 0xB4, 0xFF, 0x00, 0x8F, - 0x85, 0xFF, 0x00, 0x3D, 0xAA, 0xF5, 0x7C, 0xEF, 0x85, 0xFF, 0x00, 0xF2, 0x27, 0xAB, 0xFF, 0x00, - 0x5F, 0x1F, 0xFE, 0x93, 0x03, 0xE1, 0x3E, 0x8E, 0x7F, 0xF2, 0x4A, 0xE2, 0x3F, 0xEB, 0xFC, 0xBF, - 0xF4, 0xDD, 0x30, 0xA2, 0x8A, 0x2B, 0xF4, 0x83, 0xF7, 0xE0, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, - 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xAA, 0xBA, - 0x80, 0xF9, 0x94, 0xD5, 0xAA, 0x86, 0xF5, 0x0B, 0x28, 0x23, 0xF8, 0x7B, 0x57, 0xCA, 0xF1, 0xB6, - 0x16, 0xA6, 0x27, 0x21, 0xAF, 0x0A, 0x71, 0x72, 0x96, 0x8E, 0xCB, 0xCA, 0x49, 0xBF, 0xC2, 0xE7, - 0xE6, 0xBE, 0x2F, 0xE5, 0xB5, 0xF3, 0x0E, 0x0A, 0xC6, 0xD1, 0xA1, 0x07, 0x39, 0xA5, 0x19, 0x24, - 0xB5, 0x7E, 0xEC, 0xE2, 0xDE, 0x9E, 0x49, 0x36, 0x43, 0x62, 0x71, 0x36, 0x3D, 0x45, 0x5C, 0xAA, - 0x96, 0x71, 0xB7, 0x9B, 0xB8, 0x8C, 0x01, 0xEB, 0x56, 0xEB, 0xCC, 0xF0, 0xE6, 0x95, 0x7A, 0x79, - 0x2B, 0x55, 0xA2, 0xD7, 0xBE, 0xED, 0x7E, 0xD6, 0x5F, 0xAD, 0xCF, 0x9D, 0xF0, 0x0F, 0x0F, 0x8D, - 0xA1, 0xC2, 0x2E, 0x38, 0xAA, 0x6E, 0x0B, 0xDA, 0xCD, 0xC6, 0xEA, 0xD7, 0x8D, 0xA3, 0xAF, 0xA7, - 0x37, 0x30, 0x51, 0x45, 0x15, 0xF7, 0xA7, 0xED, 0x81, 0x45, 0x14, 0x50, 0x01, 0x59, 0xB5, 0xA5, - 0x54, 0x9E, 0x09, 0x04, 0x98, 0x0A, 0x4F, 0xA1, 0xAF, 0xCB, 0x7C, 0x4C, 0xCB, 0xF1, 0x58, 0xAA, - 0x78, 0x59, 0xD0, 0xA6, 0xE4, 0xA2, 0xE4, 0x9D, 0x95, 0xF7, 0xE5, 0xB6, 0xDE, 0x8C, 0xFE, 0x6E, - 0xFA, 0x43, 0x64, 0x79, 0x96, 0x63, 0x47, 0x2E, 0xAB, 0x82, 0xA1, 0x2A, 0x8A, 0x0E, 0xA2, 0x7C, - 0xA9, 0xC9, 0xA7, 0x2E, 0x4E, 0x5D, 0x16, 0xBA, 0xF2, 0xB2, 0xCD, 0xAF, 0xFC, 0x7B, 0xAF, 0xD2, - 0xA4, 0xA6, 0xC2, 0xBB, 0x23, 0x55, 0xF4, 0x14, 0xEA, 0xFD, 0x0B, 0x29, 0xA3, 0x3A, 0x39, 0x7D, - 0x0A, 0x55, 0x15, 0xA5, 0x18, 0x45, 0x3F, 0x54, 0x92, 0x67, 0xEE, 0xBC, 0x33, 0x85, 0xAD, 0x84, - 0xC8, 0xB0, 0x58, 0x6A, 0xCA, 0xD3, 0x85, 0x2A, 0x71, 0x6B, 0xB3, 0x50, 0x49, 0xAF, 0xBC, 0x28, - 0xA2, 0x8A, 0xF4, 0x0F, 0x70, 0x28, 0xEB, 0xC1, 0xA2, 0x8A, 0x4D, 0x26, 0xAC, 0xC5, 0x28, 0xA9, - 0x26, 0x9A, 0xBA, 0x65, 0x6B, 0x8B, 0x6F, 0xE2, 0x8F, 0xF2, 0xAA, 0xEC, 0xAC, 0xA7, 0x0C, 0x31, - 0x5A, 0x34, 0x8C, 0xA1, 0xB8, 0x60, 0x0F, 0xD6, 0xBF, 0x39, 0xCE, 0xFC, 0x38, 0xC0, 0xE3, 0x27, - 0x2A, 0xD8, 0x39, 0x7B, 0x29, 0x3E, 0x96, 0xBC, 0x7E, 0xED, 0xD7, 0xCB, 0x4F, 0x23, 0xF0, 0x2E, - 0x2F, 0xF0, 0x0F, 0x27, 0xCD, 0x6B, 0x4F, 0x15, 0x95, 0x54, 0xFA, 0xBD, 0x49, 0x6B, 0xCB, 0x6B, - 0xD3, 0xBF, 0x92, 0xD1, 0xC6, 0xFE, 0x4D, 0xAE, 0xD1, 0x33, 0xA8, 0xCD, 0x5E, 0xF2, 0x62, 0xFE, - 0xE0, 0xA3, 0xC9, 0x8B, 0xFB, 0x82, 0xBE, 0x53, 0xFE, 0x21, 0x86, 0x6B, 0xD2, 0xB4, 0x3E, 0xF9, - 0x7F, 0xF2, 0x27, 0xE6, 0xAF, 0xE8, 0xE9, 0xC4, 0xC9, 0xE9, 0x8B, 0xA3, 0x6F, 0x59, 0xFF, 0x00, - 0xF2, 0x05, 0x1A, 0x55, 0x56, 0x63, 0x85, 0x19, 0xAB, 0xBE, 0x4C, 0x5F, 0xDC, 0x14, 0xF5, 0x01, - 0x46, 0x00, 0xC5, 0x75, 0xE0, 0xFC, 0x2E, 0xC4, 0xBA, 0x9F, 0xED, 0x78, 0x84, 0xA3, 0xFD, 0xDB, - 0xB7, 0xF8, 0xA5, 0x6F, 0xC4, 0xF4, 0xF2, 0xAF, 0xA3, 0x86, 0x61, 0x3C, 0x42, 0x79, 0xA6, 0x36, - 0x2A, 0x9F, 0xFD, 0x3B, 0x4E, 0x52, 0x7F, 0x39, 0x28, 0xA5, 0xEB, 0x67, 0xE8, 0x56, 0x8E, 0xD5, - 0x8F, 0x2E, 0x71, 0xEC, 0x2A, 0xC4, 0x6A, 0xA8, 0xB8, 0x51, 0x4E, 0xA2, 0xBF, 0x48, 0xC9, 0xB8, - 0x67, 0x2C, 0xCA, 0x15, 0xF0, 0xB4, 0xFD, 0xEE, 0xB2, 0x7A, 0xCB, 0xEF, 0xE9, 0xF2, 0xB1, 0xFB, - 0xFF, 0x00, 0x09, 0xF8, 0x7B, 0xC3, 0xDC, 0x2C, 0x9C, 0xB2, 0xEA, 0x3F, 0xBC, 0x6A, 0xCE, 0x72, - 0x7C, 0xD3, 0x6B, 0xD7, 0xA2, 0xEE, 0xA2, 0x92, 0xF2, 0x0A, 0x8A, 0xF1, 0x0B, 0xC3, 0xC7, 0xF0, - 0xF3, 0x52, 0xD1, 0x5E, 0x8E, 0x65, 0x81, 0xA7, 0x8F, 0xC1, 0xD4, 0xC2, 0xD4, 0xF8, 0x66, 0xAD, - 0xFF, 0x00, 0x07, 0xE4, 0x7B, 0xDC, 0x41, 0x92, 0xD0, 0xCE, 0xF2, 0xAC, 0x46, 0x5B, 0x88, 0x76, - 0x85, 0x58, 0xB8, 0xB6, 0xB7, 0x57, 0xD9, 0xAF, 0x47, 0xA9, 0x9C, 0xA4, 0x86, 0x04, 0x75, 0x15, - 0x7A, 0x19, 0x04, 0x89, 0x91, 0xD7, 0xB8, 0xAA, 0xF7, 0x50, 0x95, 0x6D, 0xEA, 0x3E, 0x53, 0xD7, - 0xDA, 0xA2, 0x8D, 0xD9, 0x1B, 0x72, 0x9A, 0xFC, 0x3F, 0x27, 0xCC, 0xF1, 0xBC, 0x1F, 0x9A, 0x4F, - 0x0B, 0x8B, 0x8B, 0x74, 0xE5, 0xBA, 0xEE, 0xBA, 0x4E, 0x3F, 0xD6, 0xBB, 0x3D, 0x56, 0x9F, 0xC7, - 0x3C, 0x2D, 0xC4, 0x39, 0xBF, 0x85, 0x7C, 0x47, 0x57, 0x2E, 0xCD, 0x29, 0xB7, 0x87, 0x9B, 0xF7, - 0x92, 0xEA, 0xBE, 0xCD, 0x5A, 0x7D, 0x1E, 0x9D, 0x3A, 0xAF, 0x75, 0xD9, 0xAD, 0x34, 0x28, 0xA8, - 0x05, 0xD2, 0x63, 0x95, 0x6A, 0x3E, 0xD4, 0x9F, 0xDD, 0x6A, 0xFD, 0x65, 0x71, 0x8E, 0x40, 0xD5, - 0xFE, 0xB5, 0x1F, 0xC7, 0xFC, 0x8F, 0xE9, 0xC8, 0xF8, 0xAD, 0xC1, 0x32, 0x8A, 0x97, 0xF6, 0x8C, - 0x35, 0xF5, 0xFC, 0xAC, 0x4F, 0x4C, 0x98, 0xA0, 0x8C, 0xEF, 0xE9, 0x50, 0x3D, 0xD1, 0xFE, 0x15, - 0xFC, 0x4D, 0x40, 0xEC, 0xCE, 0xD9, 0x63, 0x9A, 0xF9, 0x8C, 0xFB, 0xC4, 0x5C, 0xBA, 0x9D, 0x09, - 0x52, 0xC1, 0x2F, 0x6B, 0x26, 0xAD, 0xAA, 0x6A, 0x3F, 0x3B, 0xD9, 0xBF, 0x4D, 0xBC, 0xCF, 0xCE, - 0xF8, 0xD3, 0xC7, 0x8C, 0x82, 0x86, 0x0E, 0x78, 0x6C, 0x9E, 0x3F, 0x59, 0xA9, 0x34, 0xD5, 0xE5, - 0x16, 0xA9, 0xA4, 0xF4, 0x77, 0x52, 0x49, 0xCB, 0xD1, 0x2B, 0x3F, 0xE6, 0x10, 0xE3, 0x3C, 0x55, - 0xDB, 0x3C, 0xFD, 0x9C, 0x66, 0xAB, 0xC1, 0x0B, 0x49, 0xC9, 0xE1, 0x7D, 0x6A, 0xE2, 0x80, 0xAB, - 0x81, 0xD0, 0x57, 0x97, 0xE1, 0xBE, 0x47, 0x8C, 0xA7, 0x89, 0x9E, 0x63, 0x56, 0x3C, 0xB0, 0x71, - 0xB4, 0x7A, 0x73, 0x5D, 0xA7, 0x74, 0xBB, 0x2B, 0x7C, 0xFA, 0x1F, 0x37, 0xE0, 0x0F, 0x07, 0xE6, - 0xB4, 0x31, 0xF5, 0x73, 0xEC, 0x55, 0x37, 0x4E, 0x8C, 0xA0, 0xE3, 0x04, 0xD5, 0xB9, 0xF9, 0x9A, - 0x77, 0x4B, 0xF9, 0x55, 0xB4, 0x7D, 0x6E, 0xAD, 0xB0, 0xB4, 0x51, 0x45, 0x7E, 0xC0, 0x7F, 0x55, - 0x05, 0x52, 0xBC, 0x18, 0xB8, 0x6A, 0xBB, 0x54, 0xEF, 0x7F, 0xD7, 0x9F, 0xA5, 0x7E, 0x77, 0xE2, - 0x6C, 0x14, 0xB2, 0x58, 0x3E, 0xD5, 0x17, 0xE5, 0x23, 0xF0, 0x7F, 0xA4, 0x3D, 0x25, 0x3E, 0x12, - 0xA5, 0x27, 0xF6, 0x6B, 0x41, 0xFF, 0x00, 0xE4, 0xB3, 0x5F, 0xA9, 0x26, 0x9E, 0x7E, 0x56, 0x5F, - 0x7C, 0xD5, 0x8A, 0xAD, 0xA7, 0xFF, 0x00, 0x17, 0xE1, 0x56, 0x6B, 0xD7, 0xE0, 0x5A, 0x92, 0x9F, - 0x0F, 0x61, 0x9C, 0xBF, 0xBC, 0xBE, 0xE9, 0x49, 0x1F, 0x53, 0xE0, 0xD5, 0x69, 0xD5, 0xE0, 0x4C, - 0x04, 0xA6, 0xF5, 0x4A, 0x6B, 0xE4, 0xAA, 0x4D, 0x2F, 0xC1, 0x05, 0x14, 0x51, 0x5F, 0x5A, 0x7E, - 0x9E, 0x14, 0x51, 0x45, 0x00, 0x15, 0x06, 0xA1, 0xFE, 0xA9, 0x7E, 0xB5, 0x3D, 0x36, 0x44, 0x12, - 0x2E, 0xD6, 0xAF, 0x1F, 0x88, 0x30, 0x15, 0x73, 0x0C, 0xAE, 0xBE, 0x16, 0x97, 0xC5, 0x25, 0xA5, - 0xFB, 0xEE, 0x7C, 0xAF, 0x1C, 0x64, 0xB8, 0x8C, 0xF3, 0x86, 0xF1, 0x99, 0x76, 0x19, 0xA5, 0x52, - 0xA4, 0x2D, 0x1B, 0xED, 0x7B, 0xA6, 0x97, 0xCE, 0xD6, 0x29, 0xDA, 0x7F, 0xC7, 0xC2, 0xD5, 0xEA, - 0x8A, 0x18, 0x16, 0x36, 0xDD, 0x9C, 0x9A, 0x96, 0xBC, 0x6E, 0x08, 0xC9, 0x71, 0x59, 0x46, 0x59, - 0x2A, 0x38, 0xA4, 0x94, 0xE5, 0x27, 0x2B, 0x27, 0x7B, 0x68, 0x97, 0xE8, 0x7C, 0x9F, 0x83, 0xDC, - 0x23, 0x99, 0x70, 0xBF, 0x0F, 0x4F, 0x09, 0x99, 0x24, 0xAA, 0xCE, 0xA4, 0xA7, 0x64, 0xD3, 0xB2, - 0x71, 0x8C, 0x52, 0x6D, 0x69, 0x7F, 0x76, 0xFA, 0x77, 0x0A, 0x28, 0xA2, 0xBE, 0xC4, 0xFD, 0x58, - 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, - 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, - 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, - 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, - 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x2A, 0xB5, 0xC5, 0xB9, 0xDD, 0xBA, 0x3F, 0xCA, - 0xAC, 0xD1, 0x5E, 0x3E, 0x75, 0x91, 0x60, 0x73, 0x8A, 0x1E, 0xC7, 0x15, 0x1D, 0xB6, 0x6B, 0x46, - 0xBD, 0x1F, 0xE9, 0xB1, 0xF2, 0x9C, 0x5D, 0xC1, 0x79, 0x3F, 0x15, 0xE0, 0x96, 0x17, 0x32, 0x85, - 0xED, 0xAC, 0x64, 0xB4, 0x94, 0x5F, 0xF7, 0x5E, 0xBB, 0xF5, 0x4D, 0x34, 0xFA, 0xAD, 0x11, 0x47, - 0xC9, 0x97, 0xFB, 0x86, 0x8F, 0x26, 0x5F, 0xEE, 0x1A, 0xBD, 0x45, 0x7C, 0x67, 0xFC, 0x42, 0xFC, - 0xAB, 0xFE, 0x7F, 0x54, 0xFB, 0xE3, 0xFF, 0x00, 0xC8, 0x9F, 0x93, 0x7F, 0xC4, 0xB8, 0xF0, 0xD7, - 0xFD, 0x05, 0x56, 0xFB, 0xE1, 0xFF, 0x00, 0xC8, 0x14, 0xD6, 0xDA, 0x53, 0xD4, 0x01, 0xF5, 0x35, - 0x34, 0x56, 0xC8, 0xBC, 0xB7, 0xCC, 0x6A, 0x6A, 0x2B, 0xD9, 0xCB, 0x78, 0x0F, 0x24, 0xC0, 0xCD, - 0x54, 0xE4, 0x73, 0x92, 0xDB, 0x9D, 0xDF, 0xF0, 0x49, 0x2F, 0xBD, 0x1F, 0x57, 0xC3, 0xFE, 0x0A, - 0xF0, 0x86, 0x4D, 0x5A, 0x35, 0xD5, 0x17, 0x5A, 0x6B, 0x67, 0x55, 0xF3, 0x25, 0xFF, 0x00, 0x6E, - 0xA5, 0x18, 0xFD, 0xE9, 0x85, 0x14, 0x51, 0x5F, 0x64, 0x7E, 0xB2, 0x95, 0x95, 0x90, 0x51, 0x45, - 0x14, 0x00, 0x55, 0x5B, 0xE4, 0x6F, 0x33, 0x70, 0x1C, 0x62, 0xAD, 0x51, 0x5E, 0x27, 0x10, 0x64, - 0x94, 0xB3, 0x9C, 0x0B, 0xC2, 0xD4, 0x93, 0x8E, 0xA9, 0xA6, 0xBA, 0x35, 0xE4, 0x7C, 0x7F, 0x1C, - 0x70, 0x7E, 0x1B, 0x8B, 0x72, 0x79, 0x65, 0xB5, 0xEA, 0x38, 0x6A, 0xA4, 0xA4, 0x95, 0xEC, 0xD7, - 0x97, 0x55, 0x66, 0xD6, 0xE8, 0x82, 0xC5, 0x19, 0x55, 0x89, 0x18, 0xCF, 0x4A, 0x9E, 0x8A, 0x2B, - 0xA3, 0x27, 0xCA, 0xE9, 0xE5, 0x78, 0x0A, 0x78, 0x3A, 0x6E, 0xEA, 0x1D, 0x5F, 0x5B, 0xB6, 0xDF, - 0xE2, 0xCE, 0xFE, 0x14, 0xE1, 0xDC, 0x3F, 0x0E, 0x64, 0xB4, 0x32, 0xAC, 0x3C, 0x9C, 0xA3, 0x4D, - 0x3D, 0x5E, 0xED, 0xB6, 0xE4, 0xDF, 0xCD, 0xB7, 0xA7, 0x40, 0xA2, 0x8A, 0x2B, 0xD3, 0x3E, 0x84, - 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, - 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, - 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, - 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, - 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, - 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, - 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, - 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, - 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, - 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, - 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, - 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, - 0x28, 0xA2, 0x8C, 0xE3, 0xAD, 0x26, 0xD2, 0x57, 0x62, 0x94, 0x94, 0x55, 0xDB, 0xD0, 0x28, 0xA2, - 0x8A, 0x63, 0xDC, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, - 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x98, 0xD3, 0x44, - 0x0E, 0x0B, 0x8A, 0xE6, 0xC4, 0xE3, 0x30, 0xD8, 0x58, 0xA9, 0x62, 0x2A, 0x46, 0x09, 0xFF, 0x00, - 0x33, 0x4B, 0xF3, 0x3C, 0xFC, 0xC3, 0x36, 0xCB, 0xF2, 0xE8, 0x29, 0xE3, 0xAB, 0xC2, 0x92, 0x7B, - 0x39, 0xC9, 0x45, 0x3F, 0x4B, 0xB4, 0x3E, 0x8A, 0x62, 0xCB, 0x1B, 0x1C, 0x2B, 0x0C, 0xD3, 0xEA, - 0xB0, 0xF8, 0xAC, 0x3E, 0x26, 0x1C, 0xF4, 0x26, 0xA7, 0x1E, 0xE9, 0xA6, 0xBF, 0x02, 0xF0, 0x39, - 0x96, 0x0B, 0x30, 0xA5, 0xED, 0x70, 0x75, 0xA3, 0x52, 0x1B, 0x5E, 0x32, 0x52, 0x57, 0xF5, 0x4D, - 0xA0, 0xA2, 0x8A, 0x2B, 0x73, 0xB4, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, - 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xA2, 0x8A, 0x00, 0x28, 0xAA, 0x6D, 0x71, 0x27, 0x99, - 0x90, 0x78, 0xF4, 0xAB, 0x50, 0xB8, 0x78, 0xC3, 0x0A, 0xF9, 0xAC, 0x97, 0x8A, 0xF2, 0xEC, 0xDF, - 0x11, 0x53, 0x0F, 0x87, 0x6F, 0x9A, 0x3D, 0xD5, 0xAE, 0xB6, 0xBA, 0xFE, 0x93, 0x3F, 0x3D, 0xE1, - 0x1F, 0x13, 0x32, 0x1E, 0x29, 0xC7, 0x57, 0xC1, 0x60, 0x1C, 0x95, 0x4A, 0x7A, 0xFB, 0xCA, 0xDC, - 0xD1, 0x4E, 0xDC, 0xD1, 0xD5, 0xE9, 0x7B, 0x6F, 0x67, 0xAA, 0xD0, 0x75, 0x14, 0x51, 0x5F, 0x4A, - 0x7E, 0x84, 0x14, 0x51, 0x45, 0x00, 0x14, 0x51, 0x45, 0x00, 0x14, 0x51, 0x45, 0x00, 0x14, 0x51, - 0x45, 0x00, 0x14, 0x52, 0x48, 0xC1, 0x14, 0xB1, 0xED, 0x54, 0x66, 0x95, 0xA4, 0x6C, 0x93, 0xC7, - 0x61, 0x5F, 0x29, 0xC4, 0xDC, 0x59, 0x85, 0xC8, 0xA1, 0x18, 0xCA, 0x3C, 0xF5, 0x25, 0xB4, 0x53, - 0xB6, 0x9D, 0xDB, 0xD6, 0xCB, 0xE5, 0xA9, 0xF9, 0xA7, 0x88, 0x5E, 0x27, 0x65, 0xBC, 0x1B, 0x4E, - 0x10, 0xA9, 0x0F, 0x6B, 0x5E, 0x7A, 0xC6, 0x9A, 0x76, 0xD3, 0xF9, 0xA4, 0xEC, 0xEC, 0xBA, 0x2D, - 0x1B, 0x6F, 0x65, 0xA3, 0x6A, 0xFE, 0x68, 0xAC, 0xDA, 0x55, 0x62, 0x3A, 0x12, 0x3E, 0x95, 0xF1, - 0xB0, 0xF1, 0x56, 0x2E, 0x4B, 0x9F, 0x09, 0x65, 0xE5, 0x3B, 0xFF, 0x00, 0xED, 0xA8, 0xFC, 0x9A, - 0x8F, 0xD2, 0x5A, 0x0E, 0x69, 0x55, 0xCA, 0xDA, 0x8F, 0x5B, 0x55, 0xBB, 0xFB, 0x9D, 0x35, 0xF9, - 0x9A, 0x34, 0x55, 0x48, 0x6E, 0x59, 0x78, 0x7F, 0x98, 0x55, 0x98, 0xDD, 0x5D, 0x72, 0xA7, 0x35, - 0xF7, 0x79, 0x1F, 0x14, 0x65, 0xB9, 0xC4, 0x7F, 0xD9, 0xE7, 0x69, 0xF5, 0x8B, 0xD2, 0x5F, 0xF0, - 0x7D, 0x55, 0xCF, 0xDA, 0x78, 0x3F, 0xC4, 0x6E, 0x1F, 0xE2, 0xB8, 0x5B, 0x03, 0x56, 0xD5, 0x56, - 0xF4, 0xE5, 0xA4, 0xD7, 0xCA, 0xED, 0x49, 0x79, 0xC5, 0xBF, 0x3B, 0x0E, 0xA0, 0x90, 0x06, 0x4D, - 0x15, 0x5A, 0xF9, 0xCE, 0x42, 0x0F, 0xA9, 0xAE, 0x8C, 0xFF, 0x00, 0x39, 0xA7, 0x93, 0xE5, 0xF3, - 0xC5, 0xCD, 0x5D, 0xAD, 0x12, 0xEE, 0xDE, 0xCB, 0xFC, 0xFC, 0x8E, 0xEE, 0x37, 0xE2, 0xBA, 0x1C, - 0x2D, 0x91, 0xD5, 0xCC, 0xEA, 0xC7, 0x99, 0xC6, 0xCA, 0x31, 0xDB, 0x9A, 0x4F, 0x44, 0xAF, 0xD1, - 0x75, 0x7E, 0x49, 0x8B, 0x2D, 0xC8, 0x07, 0x08, 0x33, 0xEE, 0x6A, 0x3F, 0xB5, 0x49, 0xE8, 0xBF, - 0x95, 0x47, 0x1A, 0xB3, 0xB6, 0x14, 0x54, 0xDF, 0x65, 0x7F, 0xEF, 0x2D, 0x7E, 0x3F, 0x47, 0x38, - 0xE3, 0x1C, 0xE6, 0x52, 0xC4, 0x61, 0x1C, 0xB9, 0x2F, 0xF6, 0x52, 0x51, 0x5E, 0x4A, 0xFB, 0xFD, - 0xED, 0x9F, 0xCA, 0xF8, 0x5E, 0x2B, 0xF1, 0x5F, 0x8B, 0x27, 0x53, 0x1D, 0x95, 0xB9, 0xFB, 0x2B, - 0xDA, 0xD4, 0xD4, 0x63, 0x05, 0xE4, 0x9C, 0xB7, 0xB7, 0x5D, 0x5B, 0xEE, 0x20, 0xBA, 0x7E, 0xEA, - 0xB5, 0x3C, 0x33, 0x2C, 0x9C, 0x74, 0x6F, 0x4A, 0xAF, 0x25, 0xBB, 0xAA, 0x96, 0xC8, 0x38, 0xA8, - 0x94, 0x95, 0x60, 0x47, 0x51, 0x5A, 0xD1, 0xE2, 0x9E, 0x24, 0xC8, 0xF1, 0x90, 0x8E, 0x69, 0x77, - 0x07, 0xBA, 0x95, 0xB6, 0xEF, 0x16, 0xBA, 0xAF, 0x5B, 0x77, 0x37, 0xC2, 0x78, 0x91, 0xC7, 0xFC, - 0x1D, 0x9B, 0x52, 0xA7, 0xC4, 0x6A, 0x53, 0xA7, 0x3D, 0x5C, 0x66, 0xA3, 0x77, 0x1B, 0xD9, 0xB8, - 0x49, 0x75, 0x5D, 0xAE, 0xD7, 0x74, 0x68, 0xD1, 0x48, 0xA7, 0x2A, 0x0F, 0xA8, 0xA5, 0xAF, 0xDC, - 0xE1, 0x38, 0xCE, 0x2A, 0x51, 0xD9, 0x9F, 0xD9, 0x54, 0xAA, 0xC2, 0xAD, 0x38, 0xD4, 0x83, 0xBC, - 0x5A, 0x4D, 0x7A, 0x30, 0xA2, 0xAA, 0xC9, 0x72, 0xDB, 0xFE, 0x50, 0x31, 0x53, 0x5B, 0xCA, 0x24, - 0x5F, 0x42, 0x3A, 0x8A, 0xF9, 0xBC, 0xB7, 0x8B, 0xF2, 0x8C, 0xC7, 0x18, 0xF0, 0x94, 0x2A, 0x7B, - 0xFD, 0x2E, 0xAC, 0xA5, 0x6E, 0xCF, 0xAF, 0xE0, 0x7E, 0x7F, 0x90, 0x78, 0xA5, 0xC2, 0xF9, 0xEE, - 0x69, 0x2C, 0xAF, 0x07, 0x59, 0xFB, 0x55, 0x7B, 0x5D, 0x59, 0x4E, 0xDB, 0xF2, 0x3E, 0xBD, 0xF5, - 0xB3, 0x6B, 0x54, 0x89, 0x2A, 0x95, 0xD4, 0x85, 0xE4, 0x38, 0x3F, 0x28, 0xE9, 0x53, 0x5E, 0x4B, - 0xB5, 0x76, 0x29, 0xE4, 0xF5, 0xAA, 0x95, 0xF0, 0x1E, 0x23, 0x71, 0x1C, 0x6A, 0x49, 0x65, 0x78, - 0x77, 0xA4, 0x5D, 0xE6, 0xD7, 0x7E, 0x91, 0xF9, 0x6E, 0xFC, 0xED, 0xD8, 0xFC, 0x47, 0xC7, 0xCE, - 0x3E, 0x86, 0x22, 0xA2, 0xE1, 0xCC, 0x0C, 0xF4, 0x83, 0xE6, 0xAA, 0xD3, 0xD1, 0xBB, 0x69, 0x0F, - 0x3B, 0x5E, 0xF2, 0xF3, 0xB2, 0xDD, 0x32, 0xD5, 0x8B, 0x12, 0xAC, 0xA4, 0xF4, 0xE9, 0x56, 0x2A, - 0xBE, 0x9F, 0xD1, 0xBF, 0x0A, 0x96, 0xE2, 0x4F, 0x2E, 0x3C, 0xF7, 0xED, 0x5F, 0x65, 0xC2, 0x98, - 0xF8, 0x61, 0xF8, 0x5E, 0x8E, 0x2B, 0x15, 0x3F, 0x76, 0x2A, 0x57, 0x6F, 0xB2, 0x93, 0x4B, 0xFC, - 0x91, 0xFA, 0xC7, 0x86, 0x79, 0xD5, 0x2C, 0x0F, 0x87, 0x38, 0x5C, 0xC7, 0x31, 0xAB, 0xEE, 0x53, - 0x84, 0xDC, 0xA4, 0xF5, 0xF7, 0x63, 0x39, 0xA4, 0xBC, 0xF4, 0x49, 0x25, 0xE8, 0x90, 0xE6, 0x65, - 0x5F, 0xBC, 0xC0, 0x7D, 0x4D, 0x27, 0x99, 0x1F, 0xF7, 0xD7, 0xF3, 0xAA, 0x0C, 0x4B, 0x36, 0x49, - 0xC9, 0xA4, 0xAF, 0x8C, 0xAD, 0xE2, 0x9D, 0x7F, 0x68, 0xFD, 0x96, 0x19, 0x72, 0xF4, 0xBB, 0x77, - 0xF9, 0xD8, 0xFC, 0x9B, 0x17, 0xF4, 0x92, 0xC6, 0x7B, 0x79, 0x7D, 0x5B, 0x01, 0x1F, 0x67, 0x7D, - 0x39, 0xA4, 0xF9, 0xAD, 0xE7, 0x65, 0x6B, 0xF9, 0x2D, 0xBB, 0xB3, 0x43, 0xCC, 0x8F, 0xFB, 0xEB, - 0xF9, 0xD2, 0xAB, 0x2B, 0x7D, 0xD6, 0x07, 0xE8, 0x6B, 0x3A, 0xA4, 0xB7, 0xDD, 0xE7, 0x2E, 0xDF, - 0x5A, 0xDF, 0x2F, 0xF1, 0x2F, 0x15, 0x88, 0xC5, 0xD3, 0xA3, 0x2C, 0x32, 0xB4, 0x9A, 0x5A, 0x37, - 0x7D, 0x7B, 0x1D, 0xB9, 0x1F, 0xD2, 0x13, 0x31, 0xC7, 0xE6, 0x54, 0x30, 0x95, 0x32, 0xF8, 0xB5, - 0x52, 0x4A, 0x3E, 0xEC, 0xA4, 0xE5, 0xAB, 0xB6, 0x89, 0xAD, 0x6D, 0xD8, 0xBD, 0x45, 0x14, 0xD9, - 0x18, 0x24, 0x65, 0x8F, 0x6A, 0xFD, 0x6E, 0xB5, 0x68, 0x51, 0xA5, 0x2A, 0xB5, 0x1D, 0xA3, 0x14, - 0xDB, 0x7D, 0x92, 0xD5, 0x9F, 0xD3, 0xD8, 0xAC, 0x55, 0x1C, 0x26, 0x1E, 0xA6, 0x22, 0xBC, 0xB9, - 0x61, 0x04, 0xE5, 0x27, 0xD9, 0x25, 0x76, 0xFE, 0x48, 0x73, 0x10, 0x39, 0x27, 0x15, 0x1B, 0x4F, - 0x10, 0xFE, 0x2C, 0xFD, 0x2A, 0x9C, 0x92, 0x33, 0xB6, 0x58, 0xFE, 0x14, 0x8A, 0x09, 0xE8, 0x09, - 0xFA, 0x57, 0xE3, 0xF9, 0x87, 0x89, 0xB8, 0x9A, 0x95, 0x5D, 0x3C, 0xBA, 0x8A, 0xB7, 0x47, 0x2B, - 0xB6, 0xFE, 0x4A, 0xD6, 0xFC, 0x4F, 0xE5, 0x7C, 0xF7, 0xE9, 0x0F, 0x98, 0x57, 0xC4, 0xBA, 0x19, - 0x0E, 0x11, 0x72, 0xDE, 0xC9, 0xCD, 0x39, 0x4A, 0x5E, 0x6A, 0x31, 0x6A, 0xDE, 0x97, 0x65, 0xBF, - 0xB4, 0xC5, 0xEF, 0xF9, 0x53, 0xE3, 0x95, 0x1C, 0xE1, 0x4F, 0x3E, 0x86, 0xA9, 0x79, 0x6F, 0xFD, - 0xC6, 0xFC, 0xA9, 0xAA, 0x48, 0x39, 0x15, 0xC7, 0x4B, 0xC4, 0x5C, 0xEF, 0x0F, 0x5A, 0x3F, 0x5C, - 0xA2, 0xB9, 0x3A, 0xAE, 0x57, 0x17, 0x6F, 0x26, 0xD9, 0xE4, 0xE1, 0xFC, 0x79, 0xE2, 0xFC, 0x06, - 0x2E, 0x9B, 0xCD, 0x70, 0xB1, 0xF6, 0x4D, 0xEA, 0xB9, 0x25, 0x09, 0x35, 0xD7, 0x95, 0xB7, 0xBA, - 0xF4, 0x68, 0xD2, 0xA2, 0x92, 0x33, 0xBA, 0x35, 0x3E, 0xA2, 0x96, 0xBF, 0x6C, 0xA5, 0x52, 0x35, - 0x69, 0xC6, 0xA4, 0x76, 0x6A, 0xFF, 0x00, 0x79, 0xFD, 0x81, 0x87, 0xAF, 0x0C, 0x45, 0x18, 0x56, - 0xA7, 0xF0, 0xC9, 0x26, 0xBD, 0x1A, 0xBA, 0x21, 0xBC, 0x72, 0xB1, 0x60, 0x7F, 0x11, 0xAA, 0x75, - 0x63, 0x50, 0x3F, 0x3A, 0x8F, 0x6A, 0x6D, 0x88, 0xCC, 0xD9, 0xF4, 0x15, 0xF8, 0x5F, 0x15, 0x46, - 0xA6, 0x73, 0xC5, 0x6B, 0x02, 0xA5, 0x64, 0x9C, 0x60, 0xBA, 0xDB, 0x4B, 0xB7, 0x6F, 0x9B, 0x3F, - 0x8C, 0x7C, 0x4A, 0xA7, 0x88, 0xE2, 0xCF, 0x13, 0x16, 0x4C, 0xAA, 0x72, 0xC5, 0x38, 0x52, 0x8B, - 0xDF, 0x95, 0x59, 0x4A, 0x6E, 0xD7, 0x57, 0x77, 0x6F, 0xAA, 0xBD, 0x96, 0xA4, 0x3D, 0x2A, 0xF5, - 0xAC, 0x9B, 0xE3, 0xE7, 0xA8, 0xE0, 0xD4, 0x17, 0xC9, 0x87, 0x0E, 0x3F, 0x8B, 0xAD, 0x25, 0x9B, - 0xED, 0x9B, 0x07, 0xA3, 0x71, 0x47, 0x0F, 0xD5, 0xAD, 0xC3, 0x5C, 0x4C, 0xF0, 0x15, 0xA5, 0xEE, - 0x49, 0xF2, 0xBE, 0xCE, 0xFF, 0x00, 0x04, 0xAD, 0xF7, 0x7A, 0x5D, 0x8F, 0x81, 0xB1, 0x18, 0xBF, - 0x0F, 0xBC, 0x42, 0x96, 0x4B, 0x8B, 0xA9, 0x7A, 0x53, 0x97, 0xB3, 0x93, 0xD9, 0x49, 0x4B, 0x5A, - 0x73, 0xB6, 0xB6, 0xD5, 0xAE, 0xAE, 0xC9, 0xC9, 0x5C, 0xB9, 0x45, 0x14, 0x57, 0xEF, 0x07, 0xF6, - 0x98, 0x51, 0x45, 0x14, 0x00, 0x51, 0x45, 0x14, 0x00, 0x51, 0x45, 0x14, 0x00, 0x51, 0x45, 0x14, - 0x00, 0xC9, 0x25, 0x44, 0x38, 0x63, 0x4E, 0x52, 0x0A, 0xE4, 0x1A, 0xAD, 0x75, 0x13, 0x99, 0x37, - 0x01, 0x90, 0x6A, 0x6B, 0x74, 0x29, 0x10, 0x53, 0x5F, 0x25, 0x95, 0xE7, 0x19, 0xAE, 0x27, 0x39, - 0xAF, 0x84, 0xC4, 0x61, 0xF9, 0x68, 0xC2, 0xFC, 0xB2, 0xB3, 0xEF, 0xA6, 0xBB, 0x3B, 0xAD, 0x74, - 0xD8, 0xFC, 0xBF, 0x86, 0xF8, 0xAB, 0x89, 0x71, 0xFC, 0x57, 0x8D, 0xCB, 0x31, 0xD8, 0x1F, 0x67, - 0x85, 0xA7, 0x7E, 0x49, 0xDA, 0x4A, 0xF6, 0x69, 0x2D, 0x5E, 0x92, 0xE6, 0x5A, 0xE9, 0xB1, 0x5A, - 0xEA, 0x32, 0x92, 0x64, 0x7D, 0xD6, 0xA4, 0xB6, 0x94, 0xC6, 0xFC, 0xFD, 0xD3, 0xD6, 0xAE, 0x48, - 0xA1, 0xD0, 0xA9, 0xEF, 0x54, 0x18, 0x15, 0x62, 0xA7, 0xB1, 0xAF, 0xCD, 0xB8, 0xB3, 0x2A, 0xAD, - 0xC3, 0x79, 0xB5, 0x3C, 0xC7, 0x02, 0xED, 0x19, 0x3B, 0xAF, 0x27, 0xD6, 0x3E, 0x8F, 0xF2, 0xBA, - 0xE8, 0x7F, 0x3F, 0xF8, 0x99, 0xC3, 0x58, 0xAE, 0x01, 0xE2, 0x7A, 0x39, 0xF6, 0x4F, 0x2E, 0x5A, - 0x75, 0x24, 0xE5, 0x1F, 0xEE, 0xCF, 0x79, 0x41, 0xF7, 0x8C, 0x93, 0xD1, 0x76, 0x6E, 0x3D, 0x2E, - 0x68, 0xD1, 0x50, 0xD9, 0xB1, 0x68, 0x79, 0xEC, 0x71, 0x53, 0x57, 0xED, 0x19, 0x5E, 0x3A, 0x38, - 0xFC, 0x15, 0x2C, 0x54, 0x55, 0x94, 0xD2, 0x76, 0xED, 0xE5, 0xF2, 0x3F, 0xAD, 0xB8, 0x77, 0x39, - 0xA7, 0x9D, 0xE5, 0x18, 0x6C, 0xCA, 0x9A, 0xB2, 0xAB, 0x05, 0x2B, 0x76, 0x6D, 0x6A, 0xBE, 0x4E, - 0xE8, 0x28, 0xA2, 0x8A, 0xEE, 0x3D, 0xA0, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, - 0x8A, 0x28, 0x02, 0xA5, 0xF4, 0x9B, 0x9F, 0x60, 0xE8, 0xBD, 0x69, 0x96, 0xF1, 0xF9, 0x92, 0x63, - 0xB0, 0xEB, 0x4D, 0x9B, 0xFD, 0x73, 0x7D, 0x4D, 0x5A, 0xB1, 0x03, 0xC9, 0xCF, 0xBD, 0x7E, 0x05, - 0x97, 0xE1, 0xFF, 0x00, 0xD6, 0x1E, 0x2C, 0x9F, 0xD6, 0xDD, 0xE2, 0x9C, 0x9B, 0x5E, 0x51, 0xD1, - 0x47, 0xD3, 0x6B, 0xFC, 0xCF, 0xE2, 0x4C, 0x8F, 0x01, 0xFE, 0xBD, 0xF8, 0x9B, 0x5B, 0xFB, 0x4D, - 0xDE, 0x11, 0x9C, 0xE4, 0xE3, 0xDE, 0x14, 0xDD, 0xA3, 0x0F, 0x4D, 0x93, 0xEE, 0xAF, 0xD5, 0x8F, - 0xF2, 0xA3, 0xFE, 0xE2, 0xFE, 0x54, 0xD7, 0xB7, 0x8D, 0xBA, 0x0D, 0xBF, 0x4A, 0x96, 0x8A, 0xFD, - 0xA2, 0xB6, 0x47, 0x95, 0xD6, 0x83, 0x85, 0x4C, 0x34, 0x1A, 0xFF, 0x00, 0x0A, 0xFF, 0x00, 0x2D, - 0x0F, 0xEB, 0x6C, 0x57, 0x06, 0xF0, 0xDE, 0x2A, 0x8B, 0xA3, 0x5B, 0x01, 0x49, 0xC5, 0xFF, 0x00, - 0xD3, 0xB8, 0xAF, 0xB9, 0xA5, 0x75, 0xEA, 0xB5, 0x28, 0xCD, 0x0B, 0xC7, 0xEE, 0x3D, 0x45, 0x49, - 0x62, 0x1B, 0xCC, 0x27, 0xB6, 0x39, 0xAB, 0x54, 0x57, 0xC9, 0xE0, 0xFC, 0x3F, 0xC2, 0xE0, 0xB3, - 0x68, 0x63, 0xB0, 0xF5, 0x5A, 0x84, 0x5D, 0xF9, 0x77, 0xD7, 0xB7, 0x35, 0xF6, 0xF9, 0x5F, 0xCC, - 0xFC, 0xC7, 0x2B, 0xF0, 0x37, 0x2D, 0xCA, 0x78, 0x9A, 0x8E, 0x71, 0x81, 0xC4, 0xCA, 0x34, 0xA9, - 0xBE, 0x65, 0x4E, 0xD7, 0x77, 0xED, 0xCF, 0x7B, 0xF2, 0xF9, 0x34, 0xDD, 0xB4, 0xB8, 0x55, 0x3B, - 0xDF, 0xF5, 0xFF, 0x00, 0x85, 0x5C, 0xAA, 0x37, 0x2D, 0xBA, 0x66, 0x3F, 0x85, 0x65, 0xE2, 0x6D, - 0x68, 0x47, 0x28, 0x85, 0x36, 0xFD, 0xE9, 0x4D, 0x5B, 0xE4, 0x9D, 0xFF, 0x00, 0x33, 0x9B, 0xE9, - 0x11, 0x8B, 0xA3, 0x4F, 0x85, 0xA8, 0xD0, 0x93, 0xF7, 0xE7, 0x56, 0x2D, 0x2E, 0xEA, 0x31, 0x95, - 0xFE, 0xEB, 0xAF, 0xBC, 0x97, 0x4F, 0xFE, 0x2F, 0xC2, 0xA6, 0x69, 0x63, 0x5E, 0xAE, 0x2A, 0x86, - 0x4D, 0x15, 0xF1, 0x19, 0x4F, 0x1F, 0x57, 0xCA, 0xF2, 0xBA, 0x58, 0x1C, 0x3D, 0x04, 0xE5, 0x1B, - 0xFB, 0xCD, 0xBB, 0x6B, 0x26, 0xF6, 0x56, 0xEF, 0xDC, 0xFC, 0x7F, 0x86, 0x3C, 0x6C, 0xC6, 0x70, - 0xE7, 0x0E, 0x61, 0xF2, 0x7C, 0x0E, 0x0E, 0x32, 0x9D, 0x3E, 0x6F, 0x7E, 0x72, 0x6D, 0x3E, 0x69, - 0xCA, 0x5F, 0x0A, 0xB3, 0xEB, 0x6F, 0x88, 0xBA, 0xD3, 0xC4, 0x54, 0x8D, 0xDD, 0x7D, 0xAA, 0x95, - 0x18, 0x3E, 0x94, 0x57, 0x89, 0xC4, 0x5C, 0x49, 0x8C, 0xCE, 0xFD, 0x9C, 0xB1, 0x34, 0xE3, 0x17, - 0x0B, 0xD9, 0xC5, 0x35, 0xBD, 0xB7, 0xBB, 0x7D, 0x8F, 0x8F, 0xE3, 0xBE, 0x3F, 0xCD, 0x38, 0xBD, - 0xD0, 0x9E, 0x63, 0x42, 0x10, 0x74, 0xAE, 0x93, 0x82, 0x92, 0xBA, 0x95, 0xAE, 0x9F, 0x34, 0xA5, - 0xDB, 0x4F, 0x99, 0x7A, 0xCD, 0xB3, 0x00, 0xF6, 0xE2, 0xA4, 0xA8, 0x2C, 0x3F, 0xD5, 0x1F, 0xF7, - 0xAA, 0x7A, 0xFD, 0xEB, 0x85, 0xEB, 0x4A, 0xBE, 0x49, 0x85, 0x9C, 0xF7, 0xE4, 0x4B, 0xEE, 0xD3, - 0xF4, 0x3F, 0xB5, 0xFC, 0x39, 0xC5, 0xD4, 0xC6, 0x70, 0x86, 0x5D, 0x56, 0xAF, 0xC4, 0xE9, 0x45, - 0x7F, 0xE0, 0x2B, 0x96, 0xFF, 0x00, 0x3B, 0x19, 0xCC, 0x30, 0xC4, 0x7A, 0x1A, 0x58, 0xDC, 0xA3, - 0x6E, 0x14, 0xEB, 0xA1, 0x8B, 0x86, 0xFC, 0xEA, 0x3A, 0xFE, 0x71, 0xC6, 0x42, 0xA6, 0x5F, 0x98, - 0xD4, 0x85, 0x36, 0xE3, 0x2A, 0x73, 0x69, 0x3E, 0xD6, 0x7A, 0x1F, 0xC0, 0xB9, 0xAD, 0x1C, 0x46, - 0x45, 0x9F, 0xD7, 0xA5, 0x42, 0x4E, 0x13, 0xA1, 0x56, 0x4A, 0x2D, 0x6E, 0x9C, 0x24, 0xEC, 0xD7, - 0xDC, 0x2B, 0xB1, 0x66, 0x2C, 0x7B, 0xD0, 0xC0, 0xAF, 0x5E, 0xE3, 0x34, 0xB1, 0xAE, 0xE9, 0x15, - 0x7D, 0x4D, 0x4B, 0x7C, 0x31, 0x22, 0xFD, 0x2B, 0xAA, 0x96, 0x5B, 0x57, 0x15, 0x96, 0xE2, 0x73, - 0x3A, 0x8D, 0xBE, 0x59, 0x45, 0x7A, 0xB9, 0x37, 0x76, 0xFF, 0x00, 0x0F, 0xBC, 0xF4, 0xB0, 0xF9, - 0x06, 0x23, 0x31, 0xC8, 0x31, 0xFC, 0x43, 0x5A, 0x4D, 0xBA, 0x75, 0x20, 0xAF, 0xFC, 0xD2, 0x9B, - 0x6E, 0x4D, 0xFA, 0x7B, 0xBF, 0xF8, 0x10, 0xED, 0x3C, 0xFD, 0xE1, 0xF4, 0xA6, 0x5F, 0x36, 0x65, - 0xDB, 0xFD, 0xD1, 0x4B, 0x60, 0x7F, 0x78, 0xC3, 0xDA, 0x9B, 0x79, 0xFF, 0x00, 0x1F, 0x0D, 0x5F, - 0x5B, 0x8A, 0xC6, 0x4B, 0xFD, 0x44, 0xA1, 0x08, 0xBF, 0xF9, 0x78, 0xE2, 0xFE, 0xF9, 0x4B, 0xFC, - 0x8F, 0xD3, 0xF3, 0x2C, 0xD2, 0xA7, 0xFC, 0x41, 0x7C, 0x1D, 0x1A, 0x6E, 0xC9, 0xD7, 0x70, 0x97, - 0x9A, 0xE6, 0xA9, 0x51, 0x2F, 0xBF, 0x95, 0xFC, 0x85, 0xB3, 0x8C, 0x3C, 0x99, 0x3D, 0x16, 0xAD, - 0xED, 0x5F, 0xEE, 0x8F, 0xCA, 0xA9, 0xDB, 0xCA, 0x22, 0x56, 0xE3, 0x24, 0xD4, 0x9F, 0x6B, 0xFF, - 0x00, 0x63, 0xF5, 0xAF, 0x67, 0x84, 0xF3, 0xCE, 0x1C, 0xCB, 0x32, 0x9A, 0x74, 0xF1, 0x33, 0x4A, - 0xAC, 0xAE, 0xE5, 0xEE, 0xB9, 0x75, 0x69, 0x6A, 0xA2, 0xFA, 0x25, 0xA1, 0xF5, 0x7E, 0x18, 0xF1, - 0x97, 0x01, 0x70, 0xF7, 0x0C, 0xD0, 0xC3, 0xE6, 0x15, 0xA2, 0xB1, 0x13, 0x72, 0x94, 0xEF, 0x4E, - 0x73, 0x77, 0x72, 0x6A, 0x37, 0x71, 0x84, 0x97, 0xC2, 0x96, 0x97, 0xF3, 0xEA, 0x58, 0xDA, 0xBF, - 0xDD, 0x1F, 0x95, 0x00, 0x01, 0xD0, 0x0A, 0x8A, 0x1B, 0x80, 0xED, 0xB4, 0xAE, 0x0F, 0x6E, 0x6A, - 0x6A, 0xFD, 0x1B, 0x2B, 0xC6, 0xE5, 0xB9, 0x85, 0x2F, 0xAC, 0x60, 0x9A, 0x94, 0x76, 0xBA, 0x56, - 0x69, 0xFC, 0xD2, 0x68, 0xFD, 0xF7, 0x86, 0xF3, 0x7E, 0x1F, 0xCF, 0x30, 0xDF, 0x5E, 0xC9, 0xE5, - 0x19, 0xC1, 0x3B, 0x5D, 0x47, 0x95, 0xA7, 0xD9, 0xA6, 0x94, 0x97, 0xCD, 0x05, 0x45, 0x79, 0xFF, - 0x00, 0x1E, 0xED, 0xF8, 0x54, 0xB5, 0x15, 0xE7, 0xFC, 0x7B, 0xB7, 0xE1, 0x53, 0xC4, 0x3F, 0xF2, - 0x27, 0xC5, 0xFF, 0x00, 0xD7, 0xB9, 0xFF, 0x00, 0xE9, 0x2C, 0xCF, 0x8E, 0xBF, 0xE4, 0x94, 0xCC, - 0xFF, 0x00, 0xEB, 0xC5, 0x5F, 0xFD, 0x37, 0x22, 0x9A, 0xF2, 0xC0, 0x7B, 0xD6, 0x82, 0x80, 0x06, - 0x00, 0xC5, 0x67, 0x53, 0xB7, 0xBF, 0xF7, 0x9B, 0xF3, 0xAF, 0xC3, 0x78, 0x4B, 0x8A, 0x68, 0xE4, - 0x4A, 0xAF, 0x3D, 0x1E, 0x77, 0x3B, 0x6B, 0x74, 0xAD, 0x6B, 0xF9, 0x3E, 0xE7, 0xF1, 0xB7, 0x86, - 0x1E, 0x24, 0x61, 0x38, 0x2E, 0x38, 0x97, 0x57, 0x09, 0xED, 0x65, 0x57, 0x96, 0xCD, 0x34, 0x9A, - 0x4A, 0xF7, 0x57, 0x69, 0xEF, 0x7F, 0xC0, 0xB5, 0x75, 0x2E, 0xC5, 0xDA, 0x3E, 0xF1, 0xFD, 0x2A, - 0x9D, 0x1C, 0x9A, 0xB5, 0x69, 0x0E, 0x3E, 0x77, 0x1C, 0xF6, 0x15, 0xD1, 0x5A, 0xA6, 0x61, 0xC6, - 0x99, 0xBA, 0x50, 0x8F, 0x2C, 0x17, 0xCD, 0x42, 0x3D, 0x5B, 0x7D, 0x5B, 0xFC, 0x5E, 0x9B, 0x23, - 0xBB, 0x15, 0x88, 0xCF, 0x3C, 0x5C, 0xE2, 0x98, 0xC6, 0x94, 0x3D, 0x9D, 0x18, 0x2B, 0x2E, 0xB1, - 0xA5, 0x0E, 0xAD, 0xBB, 0x2B, 0xCA, 0x4F, 0xD2, 0xEE, 0xCB, 0x44, 0xAE, 0xA6, 0x8C, 0x62, 0x35, - 0x07, 0xB0, 0xA7, 0x51, 0x45, 0x7E, 0xFD, 0x46, 0x92, 0xA5, 0x4A, 0x34, 0xE3, 0xB4, 0x52, 0x5F, - 0x71, 0xFD, 0xBB, 0x85, 0xC3, 0xC7, 0x0D, 0x87, 0xA7, 0x42, 0x1B, 0x41, 0x24, 0xBD, 0x12, 0xB1, - 0x4E, 0xF8, 0xE6, 0x7F, 0xA0, 0xA7, 0x69, 0xFD, 0x58, 0xFD, 0x2A, 0x19, 0x49, 0x32, 0x31, 0x3D, - 0x73, 0x4D, 0xAF, 0xE7, 0x08, 0xE7, 0xCB, 0x0B, 0xC4, 0x95, 0x33, 0x4F, 0x67, 0xCC, 0x94, 0xE6, - 0xD4, 0x5B, 0xB6, 0xF7, 0x4B, 0x5B, 0x3D, 0xAF, 0xD8, 0xFE, 0x05, 0x8F, 0x1B, 0x47, 0x2E, 0xE3, - 0xFA, 0xDC, 0x46, 0xE8, 0x7B, 0x44, 0xAA, 0xD4, 0x6A, 0x0D, 0xF2, 0xBB, 0x35, 0x28, 0xAB, 0xBB, - 0x4A, 0xCD, 0x26, 0x9E, 0xCF, 0x54, 0x5E, 0xB8, 0x50, 0xF0, 0x91, 0xDF, 0xA8, 0xAA, 0x3D, 0x39, - 0x14, 0x51, 0x59, 0xF1, 0x37, 0x11, 0x43, 0x3B, 0xC4, 0xC3, 0x13, 0x1A, 0x3E, 0xCE, 0x51, 0x56, - 0x7E, 0xF5, 0xEF, 0xAD, 0xD7, 0x45, 0x6B, 0x6A, 0x73, 0xF8, 0x89, 0xC7, 0x74, 0xB8, 0xC3, 0x31, - 0xA3, 0x98, 0x43, 0x09, 0xEC, 0x2A, 0x42, 0x3C, 0xAE, 0xD3, 0xE6, 0xE6, 0xB3, 0xBC, 0x5F, 0xC3, - 0x1B, 0x35, 0x77, 0xDE, 0xFA, 0x76, 0x34, 0x22, 0x6D, 0xD1, 0xAB, 0x7A, 0x8A, 0x75, 0x43, 0x62, - 0x73, 0x0E, 0x3D, 0x0D, 0x4D, 0x5F, 0xD0, 0x39, 0x16, 0x36, 0x58, 0xEC, 0xAE, 0x86, 0x26, 0x5B, - 0xCA, 0x2A, 0xFE, 0xBD, 0x7F, 0x13, 0xFB, 0x93, 0x83, 0x33, 0x79, 0xE7, 0x1C, 0x3B, 0x82, 0xC7, - 0xD4, 0xF8, 0xAA, 0x53, 0x8B, 0x7F, 0xE2, 0xB5, 0x9F, 0xE2, 0x98, 0x51, 0x45, 0x15, 0xEB, 0x1F, - 0x4C, 0x14, 0x51, 0x45, 0x00, 0x14, 0x51, 0x45, 0x00, 0x14, 0x51, 0x45, 0x00, 0x14, 0x50, 0xC4, - 0x01, 0x92, 0x69, 0xAA, 0xEA, 0xDF, 0x74, 0xE6, 0xB2, 0x95, 0x7A, 0x51, 0xA8, 0xA9, 0x4A, 0x49, - 0x49, 0xEC, 0xAF, 0xAB, 0xF4, 0x47, 0x34, 0xF1, 0xB8, 0x5A, 0x75, 0xE3, 0x87, 0x9D, 0x48, 0xAA, - 0x92, 0xD5, 0x45, 0xB5, 0xCC, 0xFD, 0x16, 0xEC, 0x75, 0x52, 0xBC, 0x18, 0x9C, 0xFB, 0x8C, 0xD5, - 0xDA, 0xA5, 0x78, 0xDB, 0xA7, 0x3E, 0xDC, 0x57, 0xC0, 0x78, 0x98, 0xE9, 0x2C, 0x9A, 0x0A, 0x7F, - 0x17, 0x3A, 0xB7, 0xDC, 0xEF, 0xF8, 0x1F, 0x88, 0x7D, 0x21, 0xA5, 0x86, 0x5C, 0x27, 0x49, 0x54, - 0xF8, 0xDD, 0x58, 0xF2, 0xFA, 0xF2, 0xCA, 0xFF, 0x00, 0x2B, 0x5F, 0xF0, 0x24, 0xD3, 0xCF, 0xDE, - 0x15, 0x66, 0xAB, 0x69, 0xE3, 0xEF, 0x35, 0x59, 0xAF, 0x53, 0x80, 0xD4, 0xD7, 0x0F, 0x50, 0xE7, - 0xFE, 0xF5, 0xBD, 0x39, 0x99, 0xF4, 0x9E, 0x0A, 0xC6, 0xB2, 0xE0, 0x5C, 0x17, 0xB5, 0xFE, 0xFD, - 0xBD, 0x3D, 0xA4, 0xAD, 0xFF, 0x00, 0x03, 0xC8, 0x28, 0xA2, 0x8A, 0xFB, 0x03, 0xF5, 0x40, 0xA2, - 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x02, 0x8D, 0xD2, 0x95, 0x99, 0xB3, - 0xDC, 0xE4, 0x52, 0xDB, 0xCC, 0x63, 0xE3, 0x19, 0x15, 0x6E, 0x58, 0xD6, 0x45, 0xC3, 0x0F, 0xA1, - 0xAA, 0xCF, 0x6A, 0xE3, 0xEE, 0x90, 0xD5, 0xF8, 0xA6, 0x73, 0xC2, 0xF9, 0xDE, 0x53, 0x99, 0xCF, - 0x30, 0xCA, 0x6F, 0x28, 0xB6, 0xDA, 0xE5, 0xD5, 0xAB, 0xEE, 0x9C, 0x7A, 0xAF, 0x93, 0xF3, 0x3F, - 0x90, 0xF8, 0xB3, 0xC3, 0x8E, 0x30, 0xE1, 0x8E, 0x22, 0xAB, 0x9E, 0x70, 0xC2, 0x94, 0xE1, 0x39, - 0x4A, 0x49, 0xC2, 0xCE, 0x71, 0xE6, 0xD5, 0xC6, 0x50, 0x77, 0xE6, 0x57, 0xDA, 0xCA, 0x49, 0xAB, - 0x5E, 0xCC, 0x7F, 0xDA, 0x97, 0xFB, 0x86, 0x8F, 0xB5, 0x2F, 0xF7, 0x4D, 0x43, 0xF6, 0x79, 0x7F, - 0xBB, 0xFA, 0xD1, 0xE4, 0x4B, 0xFD, 0xDF, 0xD6, 0xB1, 0xFF, 0x00, 0x58, 0x78, 0xE7, 0xFE, 0x7C, - 0xCF, 0xFF, 0x00, 0x05, 0x7F, 0xF6, 0xA7, 0x2F, 0xFA, 0xF5, 0xE3, 0x1F, 0xFD, 0x02, 0xD5, 0xFF, - 0x00, 0xC2, 0x67, 0xFF, 0x00, 0xC8, 0x13, 0x7D, 0xA9, 0x3F, 0xBA, 0x6A, 0x75, 0x21, 0x97, 0x23, - 0xA1, 0xAA, 0x7F, 0x67, 0x97, 0xFB, 0xBF, 0xAD, 0x5B, 0x85, 0x76, 0x46, 0x17, 0xD2, 0xBE, 0xCF, - 0x83, 0xF3, 0x3E, 0x22, 0xC5, 0xD6, 0xA9, 0x1C, 0xD2, 0x93, 0x8C, 0x12, 0xD1, 0xB8, 0xF2, 0xBB, - 0xDF, 0x6E, 0x97, 0xD2, 0xFD, 0x0F, 0xD6, 0x3C, 0x2A, 0xE2, 0x2E, 0x3C, 0xCD, 0x31, 0x75, 0xE1, - 0xC4, 0x78, 0x69, 0x42, 0x92, 0x8D, 0xE3, 0x29, 0x53, 0xF6, 0x6F, 0x9A, 0xEB, 0x44, 0xAC, 0xAE, - 0x9A, 0xBB, 0x6E, 0xDA, 0x59, 0x6B, 0xA8, 0xEA, 0xCF, 0x9B, 0xFD, 0x73, 0x7D, 0x4D, 0x68, 0x55, - 0x39, 0x20, 0x93, 0xCC, 0x38, 0x5C, 0xE4, 0xF5, 0xAE, 0x3F, 0x12, 0xB0, 0x58, 0xBC, 0x56, 0x12, - 0x84, 0x70, 0xF4, 0xDC, 0xED, 0x27, 0x7B, 0x2B, 0xDB, 0x4D, 0x36, 0x3C, 0xBF, 0xA4, 0x0E, 0x53, - 0x9A, 0x66, 0x59, 0x66, 0x0A, 0x18, 0x1C, 0x3C, 0xAA, 0xA5, 0x39, 0x5F, 0x95, 0x39, 0x35, 0x75, - 0x65, 0xA2, 0xD7, 0x5D, 0x75, 0xD8, 0x75, 0xAC, 0x28, 0xF1, 0xEE, 0x61, 0x9E, 0x6A, 0xC2, 0xA2, - 0xAF, 0xDD, 0x50, 0x3F, 0x0A, 0x48, 0x53, 0x64, 0x61, 0x69, 0xF5, 0xF4, 0xDC, 0x3B, 0x91, 0xE1, - 0xB0, 0x18, 0x0A, 0x09, 0xD1, 0x8C, 0x6A, 0xA8, 0xAE, 0x67, 0x65, 0x7B, 0xDB, 0x5B, 0xBD, 0xCF, - 0xD1, 0x38, 0x0B, 0x83, 0xB2, 0xFC, 0x93, 0x24, 0xC2, 0x29, 0xE1, 0x21, 0x0C, 0x4A, 0x84, 0x79, - 0xE5, 0xCB, 0x1E, 0x6E, 0x6B, 0x7B, 0xD7, 0x95, 0xAE, 0xF5, 0xF3, 0x0A, 0xA3, 0x75, 0xC5, 0xC3, - 0x7D, 0x6A, 0xF5, 0x32, 0x48, 0x91, 0xCE, 0x58, 0x57, 0x37, 0x18, 0xF0, 0xFD, 0x7C, 0xEB, 0x01, - 0x1A, 0x34, 0x24, 0x94, 0xA3, 0x25, 0x2D, 0x76, 0x7A, 0x35, 0xFA, 0x9E, 0x6F, 0x8A, 0xDC, 0x0F, - 0x8C, 0xE2, 0xEC, 0x96, 0x9E, 0x13, 0x05, 0x38, 0xC6, 0xA4, 0x26, 0xA6, 0xB9, 0xAE, 0x93, 0xD1, - 0xC5, 0xAB, 0xA4, 0xED, 0xBD, 0xF6, 0xE8, 0x47, 0x61, 0xFE, 0xA4, 0xFF, 0x00, 0xBD, 0x53, 0xD2, - 0x28, 0x0A, 0xB8, 0x03, 0x02, 0x96, 0xBD, 0x9C, 0x97, 0x2F, 0x96, 0x5F, 0x97, 0x51, 0xC2, 0x49, - 0xDD, 0xC1, 0x24, 0xDF, 0x99, 0xF5, 0xBC, 0x23, 0x92, 0x4F, 0x22, 0xC8, 0x70, 0xB9, 0x65, 0x49, - 0x73, 0x4A, 0x94, 0x12, 0x6D, 0x6C, 0xDF, 0x5B, 0x79, 0x5F, 0x62, 0xAE, 0xA0, 0x31, 0x22, 0xB7, - 0xA8, 0xA8, 0xED, 0xD0, 0x48, 0xE5, 0x0F, 0xA5, 0x59, 0xBA, 0x88, 0xC8, 0xA3, 0x1D, 0x45, 0x36, - 0xD6, 0x16, 0x46, 0xDC, 0xDD, 0x7D, 0x2B, 0xF3, 0x3C, 0xCB, 0x85, 0xF1, 0x55, 0xF8, 0xBB, 0xDA, - 0x3A, 0x17, 0xC3, 0xCD, 0xA6, 0xDF, 0xD9, 0xB7, 0x2F, 0xBD, 0x77, 0xDE, 0xF7, 0xF3, 0x3F, 0x9E, - 0x33, 0xFF, 0x00, 0x0E, 0x33, 0x2C, 0x6F, 0x8A, 0x3E, 0xDD, 0xE1, 0x1C, 0xF0, 0x55, 0x65, 0x19, - 0x4A, 0x5F, 0x63, 0x95, 0xC1, 0x2A, 0x89, 0xBE, 0x8D, 0xBE, 0x6B, 0x2D, 0xF5, 0x4D, 0x0B, 0x0C, - 0x02, 0x36, 0xDD, 0x9C, 0x9E, 0xD4, 0xDD, 0x40, 0x7C, 0xAA, 0xD5, 0x62, 0x91, 0xD4, 0x3A, 0xED, - 0x61, 0xC5, 0x7D, 0xC6, 0x61, 0xC3, 0x18, 0x59, 0x64, 0xB5, 0x72, 0xCC, 0x14, 0x54, 0x14, 0xB5, - 0x5B, 0xFC, 0x49, 0xA6, 0x9B, 0x7A, 0xBE, 0x8B, 0xE4, 0x7E, 0xC9, 0x9D, 0xF8, 0x77, 0x96, 0xD4, - 0xE1, 0x0C, 0x4F, 0x0F, 0x65, 0x14, 0xE3, 0x46, 0x35, 0x35, 0x5B, 0xB5, 0xCE, 0x9A, 0x92, 0x72, - 0x6E, 0xF2, 0x77, 0x71, 0x49, 0xB7, 0x7B, 0x2F, 0x4B, 0x14, 0xEC, 0xC9, 0xFB, 0x40, 0xC5, 0x17, - 0x80, 0xFD, 0xA0, 0xD5, 0xA8, 0xA2, 0x48, 0xCE, 0x54, 0x73, 0xEF, 0x44, 0xD1, 0x2C, 0x8B, 0xCF, - 0x5E, 0xC6, 0xBE, 0x4B, 0xFD, 0x48, 0xC7, 0xFF, 0x00, 0xAB, 0x2F, 0x01, 0x29, 0xA7, 0x55, 0x4F, - 0xDA, 0x25, 0xD3, 0x6B, 0x72, 0xDF, 0xEF, 0x7D, 0xAF, 0xF7, 0x9F, 0x97, 0xBF, 0x07, 0xF3, 0xA5, - 0xE1, 0xEC, 0xB2, 0x49, 0xD4, 0x8B, 0xC4, 0xAA, 0xDE, 0xDA, 0x29, 0x3F, 0x77, 0xE1, 0xE5, 0xE4, - 0xE6, 0x69, 0x6A, 0xD5, 0xDD, 0xED, 0x6B, 0xBB, 0x3D, 0x35, 0x28, 0x0E, 0x4E, 0x33, 0x8A, 0x9B, - 0xEC, 0xD2, 0x1E, 0x41, 0x5F, 0xCE, 0xA3, 0x91, 0x19, 0x1B, 0x6B, 0x0A, 0x92, 0x19, 0xDA, 0x35, - 0xDB, 0x8D, 0xC3, 0xB7, 0x35, 0xF9, 0xE6, 0x4D, 0x4B, 0x29, 0xA5, 0x8A, 0x9E, 0x1B, 0x3B, 0x84, - 0xA3, 0x6E, 0xAA, 0xF7, 0x4D, 0x74, 0x69, 0x27, 0xF8, 0x7E, 0x5B, 0x7E, 0x13, 0xC2, 0x78, 0x6E, - 0x18, 0xC3, 0x66, 0x55, 0x70, 0x1C, 0x5F, 0x4A, 0x74, 0xD4, 0x6E, 0xB9, 0xA2, 0xE4, 0x9C, 0x24, - 0xB7, 0x8C, 0xA2, 0x93, 0x6D, 0x3E, 0x8D, 0x2B, 0xA7, 0xBD, 0xD3, 0xBA, 0x92, 0xDE, 0xDC, 0xA4, - 0x9B, 0x98, 0x8E, 0x3B, 0x0A, 0xB1, 0x54, 0x66, 0x95, 0xA4, 0x6C, 0xF4, 0x1D, 0x85, 0x58, 0xB2, - 0x66, 0x68, 0xB9, 0xEC, 0x78, 0xAF, 0xD3, 0x78, 0x3F, 0x3D, 0xCA, 0x16, 0x32, 0x59, 0x56, 0x5B, - 0x46, 0x4A, 0x1A, 0xC9, 0x4D, 0xBB, 0xF3, 0x35, 0xD5, 0xAE, 0x9E, 0x5F, 0x92, 0x3F, 0xA2, 0x3C, - 0x2B, 0xE3, 0x3E, 0x17, 0x8E, 0x6B, 0x53, 0x86, 0xB2, 0x0C, 0x2C, 0xE3, 0x45, 0xF3, 0x4D, 0x54, - 0x94, 0xAE, 0xE6, 0xD2, 0x57, 0x6D, 0x35, 0x78, 0xAB, 0x7C, 0x3A, 0xF6, 0xBA, 0x4D, 0x93, 0x54, - 0x57, 0x9F, 0xF1, 0xEE, 0xDF, 0x85, 0x4B, 0x51, 0x5E, 0x7F, 0xC7, 0xBB, 0x7E, 0x15, 0xF6, 0x7C, - 0x43, 0xFF, 0x00, 0x22, 0x7C, 0x5F, 0xFD, 0x7B, 0x9F, 0xFE, 0x92, 0xCF, 0xD6, 0xB8, 0xEB, 0xFE, - 0x49, 0x4C, 0xCF, 0xFE, 0xBC, 0x55, 0xFF, 0x00, 0xD3, 0x72, 0x29, 0x01, 0x93, 0x8A, 0x74, 0x8A, - 0xC8, 0xD8, 0x61, 0x8A, 0x44, 0xFB, 0xC3, 0xEB, 0x57, 0xE5, 0x45, 0x91, 0x70, 0xC3, 0xF1, 0xAF, - 0xC2, 0xB8, 0x6F, 0x85, 0x5E, 0x79, 0x83, 0xC4, 0x4E, 0x94, 0xED, 0x56, 0x0D, 0x5B, 0xB3, 0xBA, - 0x7A, 0x3E, 0xDB, 0x68, 0xCF, 0xE3, 0x1E, 0x00, 0xF0, 0xD9, 0xF1, 0x8E, 0x53, 0x8E, 0xAB, 0x87, - 0xAB, 0xC9, 0x88, 0xA2, 0xE3, 0xC8, 0x9F, 0xC3, 0x2B, 0xA9, 0x5D, 0x3E, 0xA9, 0xE8, 0xAC, 0xFA, - 0x75, 0x5D, 0xA9, 0xDB, 0xBA, 0xA4, 0x99, 0x65, 0xC8, 0xFE, 0x55, 0x78, 0x10, 0x46, 0x45, 0x67, - 0xCA, 0x86, 0x36, 0xDA, 0x6A, 0x4B, 0x79, 0x8A, 0x61, 0x4F, 0x2B, 0x5E, 0xE7, 0x07, 0xF1, 0x33, - 0xC9, 0x2B, 0xCF, 0x2C, 0xCC, 0x63, 0xCB, 0x1B, 0xEF, 0x6D, 0x63, 0x2F, 0x3E, 0xEB, 0xCF, 0x5B, - 0x7A, 0x1F, 0x65, 0xE1, 0x4F, 0x88, 0x4F, 0x83, 0xF1, 0x95, 0x78, 0x7B, 0x3D, 0xA7, 0xEC, 0xE9, - 0xF3, 0x35, 0x7E, 0x5F, 0x7A, 0x13, 0xBD, 0x9A, 0x95, 0xB5, 0x71, 0x7D, 0xF5, 0xB7, 0x4F, 0x75, - 0xE9, 0x72, 0x8A, 0x29, 0x18, 0xE1, 0x49, 0xF4, 0x15, 0xFB, 0x7C, 0xE4, 0xA3, 0x17, 0x26, 0xF4, - 0x47, 0xF6, 0x15, 0x5A, 0x91, 0xA7, 0x4E, 0x55, 0x24, 0xEC, 0x92, 0xBB, 0xF4, 0x45, 0x09, 0x0E, - 0x64, 0x63, 0xEA, 0x6A, 0xCD, 0xA4, 0x68, 0x61, 0x05, 0x94, 0x12, 0x7D, 0x45, 0x54, 0xAB, 0xD6, - 0xC3, 0x10, 0x2F, 0xD2, 0xBF, 0x0B, 0xF0, 0xEF, 0x0F, 0x4F, 0x1B, 0x9C, 0xD6, 0xAB, 0x5A, 0x0A, - 0x4B, 0x95, 0xBD, 0x55, 0xD5, 0xDC, 0x97, 0xFC, 0x13, 0xF8, 0xCF, 0xC0, 0x9C, 0x0D, 0x1C, 0xDB, - 0x8A, 0xF1, 0x78, 0x9C, 0x5D, 0x35, 0x52, 0x2A, 0x9C, 0x9F, 0xBC, 0x93, 0x5C, 0xD2, 0x9C, 0x7A, - 0x3E, 0xB6, 0xB8, 0xEF, 0x2A, 0x3F, 0xEE, 0x2F, 0xE5, 0x54, 0xEE, 0x80, 0x13, 0xB0, 0x03, 0x02, - 0xAF, 0x55, 0x1B, 0xCF, 0xF8, 0xF8, 0x6A, 0xFA, 0x6F, 0x12, 0xB0, 0x58, 0x7A, 0x39, 0x4D, 0x39, - 0x52, 0xA6, 0xA2, 0xFD, 0xA2, 0xD5, 0x24, 0xBE, 0xCC, 0xBB, 0x1F, 0xA1, 0x7D, 0x20, 0x72, 0x9C, - 0x06, 0x13, 0x86, 0x30, 0xF5, 0x30, 0xB8, 0x78, 0x41, 0xFB, 0x68, 0xA6, 0xE3, 0x14, 0x9D, 0xB9, - 0x2A, 0x75, 0x4B, 0x6B, 0xD8, 0x9E, 0xC3, 0xFD, 0x51, 0xFF, 0x00, 0x7A, 0xA7, 0xA8, 0x2C, 0x3F, - 0xD4, 0x9F, 0xF7, 0xAA, 0x7A, 0xFA, 0xCE, 0x10, 0x4D, 0x64, 0x38, 0x5B, 0xFF, 0x00, 0x2F, 0xEA, - 0xCF, 0xD3, 0xFC, 0x2C, 0x8B, 0x5C, 0x15, 0x97, 0x5D, 0x7F, 0xCB, 0xB5, 0xF9, 0xB0, 0xA2, 0x8A, - 0x2B, 0xE9, 0x0F, 0xBF, 0x0A, 0x28, 0xA2, 0x80, 0x0A, 0x28, 0xA2, 0x80, 0x0A, 0x28, 0xA2, 0x80, - 0x22, 0xBB, 0x56, 0x68, 0xB0, 0xBF, 0x95, 0x45, 0x66, 0x8E, 0x24, 0xDC, 0x46, 0x05, 0x5A, 0xA2, - 0xBE, 0x5F, 0x1B, 0xC2, 0xD8, 0x7C, 0x5E, 0x71, 0x4B, 0x34, 0x95, 0x46, 0xA5, 0x0B, 0x69, 0xD1, - 0xDB, 0x6F, 0x4F, 0x3E, 0xE7, 0xE7, 0x39, 0xC7, 0x86, 0xF8, 0x1C, 0xCF, 0x8A, 0xF0, 0xDC, 0x47, - 0x52, 0xB4, 0xD4, 0xE9, 0x72, 0xFB, 0xAB, 0x66, 0xE3, 0x76, 0xB5, 0xDD, 0x2E, 0xEB, 0xA8, 0x56, - 0x7C, 0x87, 0x74, 0x8C, 0x7D, 0x4D, 0x5A, 0xBA, 0x98, 0x22, 0xED, 0x1F, 0x78, 0xFE, 0x95, 0x4C, - 0x0C, 0x9C, 0x0A, 0xFC, 0xF7, 0xC4, 0xBC, 0xDA, 0x8E, 0x26, 0xBD, 0x2C, 0x05, 0x07, 0xCC, 0xE0, - 0xDB, 0x95, 0xBB, 0xBB, 0x24, 0xBD, 0x77, 0xBF, 0xA9, 0xF8, 0x5F, 0xD2, 0x0F, 0x8A, 0x30, 0xB9, - 0x86, 0x37, 0x0D, 0x92, 0xE0, 0xE7, 0xCF, 0x2A, 0x2E, 0x4E, 0x76, 0xD6, 0xD2, 0x76, 0x51, 0x8F, - 0xAA, 0x57, 0xBA, 0xE9, 0x74, 0xB7, 0xB9, 0x72, 0xC4, 0x62, 0x0C, 0xFA, 0x9A, 0x9A, 0x9B, 0x1A, - 0xED, 0x8D, 0x57, 0xD0, 0x53, 0xAB, 0xF5, 0x7C, 0x97, 0x06, 0xF0, 0x59, 0x6D, 0x0C, 0x3B, 0xDE, - 0x31, 0x49, 0xFA, 0xDB, 0x5F, 0xC4, 0xFE, 0x98, 0xE1, 0x2C, 0xAA, 0x59, 0x4F, 0x0F, 0xE0, 0xB0, - 0x12, 0x5E, 0xF5, 0x3A, 0x70, 0x4F, 0xFC, 0x56, 0x5C, 0xDF, 0x8D, 0xC2, 0x8A, 0x28, 0xAF, 0x4C, - 0xFA, 0x20, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, - 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, - 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, - 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x01, 0x93, 0x46, 0x24, 0x5C, 0x1E, 0xBD, 0x8D, 0x57, 0xFB, 0x2C, - 0x9E, 0xAB, 0x56, 0xE8, 0xAF, 0x99, 0xCD, 0xF8, 0x47, 0x29, 0xCD, 0x6B, 0x7B, 0x7C, 0x44, 0x1F, - 0x3F, 0x74, 0xED, 0x7F, 0x5E, 0xE7, 0xE7, 0x9C, 0x53, 0xE1, 0x77, 0x0C, 0xF1, 0x36, 0x2D, 0x63, - 0x71, 0xF4, 0x5A, 0xAB, 0x6B, 0x39, 0x46, 0x4E, 0x2D, 0xA5, 0xB5, 0xFA, 0x3B, 0x77, 0xB5, 0xED, - 0xA5, 0xEC, 0x91, 0x55, 0x6D, 0x5B, 0x77, 0xCC, 0xC3, 0x1E, 0xD5, 0x65, 0x54, 0x2A, 0xED, 0x03, - 0x81, 0x4B, 0x45, 0x74, 0x64, 0xDC, 0x37, 0x96, 0xE4, 0xFC, 0xCF, 0x09, 0x0B, 0x4A, 0x5B, 0xB6, - 0xEE, 0xED, 0xDB, 0xD0, 0xEF, 0xE1, 0x3F, 0x0F, 0xF8, 0x7F, 0x85, 0x79, 0xE5, 0x96, 0x52, 0xB4, - 0xE7, 0xA3, 0x94, 0x9F, 0x34, 0xAD, 0xDA, 0xEF, 0x65, 0xE4, 0xAD, 0x7E, 0xBB, 0x20, 0xA6, 0xCA, - 0xBB, 0xE3, 0x2B, 0xEB, 0x4E, 0xA2, 0xBD, 0x9A, 0xF4, 0x69, 0xD7, 0xA5, 0x2A, 0x35, 0x15, 0xE3, - 0x24, 0xD3, 0x5E, 0x4F, 0x46, 0x7D, 0x5E, 0x33, 0x07, 0x47, 0x1B, 0x86, 0xA9, 0x85, 0xAF, 0x1E, - 0x6A, 0x75, 0x22, 0xE3, 0x25, 0xDD, 0x49, 0x59, 0xAF, 0xB9, 0x95, 0x63, 0xB6, 0x71, 0x20, 0x2D, - 0x8C, 0x0A, 0xB5, 0x45, 0x15, 0xE4, 0xE4, 0x7C, 0x3F, 0x82, 0xC9, 0x69, 0x4A, 0x9E, 0x15, 0x3F, - 0x79, 0xDD, 0xB6, 0xEE, 0xFF, 0x00, 0xA4, 0x7C, 0xC7, 0x07, 0xF0, 0x3E, 0x51, 0xC2, 0x58, 0x6A, - 0x94, 0x32, 0xD5, 0x2B, 0x54, 0x77, 0x93, 0x93, 0xBB, 0x7D, 0x96, 0xCB, 0x45, 0xD3, 0x41, 0x93, - 0x46, 0x24, 0x5C, 0x1E, 0xBD, 0x8D, 0x43, 0x1D, 0xA9, 0x0F, 0x96, 0x61, 0x81, 0xE9, 0x56, 0x68, - 0xAC, 0xB3, 0x0E, 0x17, 0xCA, 0xB3, 0x0C, 0x5C, 0x71, 0x78, 0x8A, 0x57, 0x9A, 0xF3, 0x6A, 0xF6, - 0xDA, 0xEB, 0xAF, 0xF4, 0xB6, 0x39, 0xF3, 0xCF, 0x0E, 0x78, 0x6B, 0x3C, 0xCC, 0xE9, 0xE6, 0x78, - 0xEC, 0x3F, 0x35, 0x68, 0x5B, 0x5B, 0xB4, 0xA5, 0x6D, 0xB9, 0x92, 0xD2, 0x56, 0xF3, 0xE9, 0xA3, - 0xBA, 0xD0, 0x29, 0x24, 0x1B, 0x90, 0xAF, 0xA8, 0xA5, 0xA2, 0xBD, 0xDA, 0x94, 0xE3, 0x56, 0x9C, - 0xA9, 0xCB, 0x66, 0xAC, 0xFE, 0x67, 0xD9, 0x62, 0x30, 0xF4, 0xF1, 0x14, 0x67, 0x46, 0xA2, 0xBC, - 0x64, 0x9A, 0x7E, 0x8D, 0x59, 0x94, 0x96, 0xDE, 0x42, 0xF8, 0x23, 0x8F, 0x5A, 0xBA, 0x06, 0x06, - 0x05, 0x14, 0x57, 0xCF, 0xF0, 0xFF, 0x00, 0x0C, 0x60, 0xB2, 0x35, 0x53, 0xEA, 0xED, 0xB7, 0x3D, - 0xDB, 0xEC, 0xB6, 0x5D, 0x3B, 0x9F, 0x11, 0xC0, 0xFE, 0x1D, 0xE5, 0x1C, 0x1D, 0x1A, 0xFF, 0x00, - 0x50, 0x72, 0x94, 0xAA, 0xB5, 0x77, 0x26, 0x9B, 0xB2, 0xBD, 0xA2, 0xAC, 0x96, 0x8A, 0xEF, 0xD7, - 0xA8, 0x53, 0x64, 0x8D, 0x1F, 0xEF, 0x2E, 0x69, 0xD4, 0x57, 0xBB, 0x88, 0xC3, 0xD1, 0xC4, 0x53, - 0x74, 0xEB, 0x41, 0x4A, 0x2F, 0xA3, 0x57, 0x5F, 0x73, 0x3E, 0xCF, 0x1B, 0x81, 0xC2, 0x63, 0xA8, - 0x3C, 0x3E, 0x2E, 0x94, 0x6A, 0x53, 0x7B, 0xC6, 0x49, 0x49, 0x3F, 0x93, 0xBA, 0x11, 0x40, 0x55, - 0xC0, 0x18, 0x14, 0xB4, 0x51, 0x5A, 0x46, 0x31, 0x84, 0x54, 0x62, 0xAC, 0x91, 0xBD, 0x3A, 0x70, - 0xA5, 0x05, 0x4E, 0x9A, 0x4A, 0x2B, 0x44, 0x96, 0x89, 0x25, 0xD1, 0x20, 0xA2, 0x8A, 0x2A, 0x8B, - 0x0A, 0x28, 0xA2, 0x80, 0x0A, 0x28, 0xA2, 0x80, 0x0A, 0x28, 0xA2, 0x80, 0x0A, 0x28, 0xA2, 0x80, - 0x28, 0xDC, 0x2B, 0x09, 0x9B, 0x20, 0xF2, 0x78, 0xA9, 0xAD, 0x61, 0xDB, 0xF3, 0xBF, 0x5E, 0xC3, - 0xD2, 0xAC, 0x51, 0x5F, 0x0D, 0x96, 0xF0, 0x26, 0x0B, 0x09, 0x99, 0xCF, 0x1F, 0x52, 0x6E, 0xA3, - 0x6D, 0xB8, 0xA6, 0xB6, 0x6D, 0xDE, 0xEF, 0x5D, 0x5A, 0xE9, 0xB7, 0x7D, 0xCF, 0xC6, 0xB8, 0x7F, - 0xC1, 0x8C, 0xA7, 0x2B, 0xE2, 0x2A, 0xB9, 0xD5, 0x7A, 0xAE, 0xBB, 0x72, 0x73, 0x84, 0x64, 0xB4, - 0x84, 0x9B, 0xBD, 0xDB, 0xBB, 0xE6, 0x6B, 0xA3, 0xB2, 0xB6, 0xF6, 0xBD, 0x9A, 0x28, 0xA2, 0x8A, - 0xFB, 0x93, 0xF6, 0x50, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, - 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, - 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, - 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, - 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, - 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, - 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, - 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, - 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, - 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, - 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, - 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, - 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, - 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, - 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, - 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, - 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, - 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, - 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, - 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, - 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, - 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, - 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, - 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, - 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, - 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, - 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, - 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, - 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, - 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, - 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x00, 0xA2, 0x8A, 0x28, 0x03, 0xFF, 0xD9 }; +typedef struct { + int size; + u32 unk; + int resolution; + int framerate; + u32 unk2; + u32 unk3; + int wb; + int saturation; + int brightness; + int contrast; + int sharpness; + u32 unk4; + u32 unk5; + u32 unk6[3]; + int effectmode; + u32 unk7; + u32 unk8; + u32 unk9; + u32 unk10; + u32 unk11; + int framesize; + u32 unk12; + int evlevel; +} PspUsbCamSetupVideoExParam; + +namespace Camera { + enum Mode { Unused, Still, Video }; + enum ConfigType { CfNone, CfStill, CfStillEx, CfVideo, CfVideoEx }; + + typedef struct { + Mode mode; + ConfigType type; + PspUsbCamSetupMicParam micParam; + union { + PspUsbCamSetupStillParam stillParam; + PspUsbCamSetupStillExParam stillExParam; + PspUsbCamSetupVideoParam videoParam; + PspUsbCamSetupVideoExParam videoExParam; + }; + } Config; + + std::vector getDeviceList(); + void onCameraDeviceChange(); + int startCapture(); + int stopCapture(); + void pushCameraImage(long long length, unsigned char *image); +} diff --git a/Core/HLE/sceUsbGps.cpp b/Core/HLE/sceUsbGps.cpp index 4213e7abd1..05b16e5b3d 100644 --- a/Core/HLE/sceUsbGps.cpp +++ b/Core/HLE/sceUsbGps.cpp @@ -43,6 +43,15 @@ void __UsbGpsDoState(PointerWrap &p) { p.Do(gpsStatus); } +void __UsbGpsShutdown() { + gpsStatus = GPS_STATE_OFF; + System_SendMessage("gps_command", "close"); +}; + +static int sceUsbGpsGetInitDataLocation(u32 addr) { + return 0; +} + static int sceUsbGpsGetState(u32 stateAddr) { if (Memory::IsValidAddress(stateAddr)) { Memory::Write_U32(gpsStatus, stateAddr); @@ -77,17 +86,17 @@ static int sceUsbGpsGetData(u32 gpsDataAddr, u32 satDataAddr) { const HLEFunction sceUsbGps[] = { - {0X268F95CA, nullptr, "sceUsbGpsSetInitDataLocation", '?', "" }, - {0X31F95CDE, nullptr, "sceUsbGpsGetPowerSaveMode", '?', "" }, - {0X54D26AA4, nullptr, "sceUsbGpsGetInitDataLocation", '?', "" }, - {0X63D1F89D, nullptr, "sceUsbGpsResetInitialPosition", '?', "" }, - {0X69E4AAA8, nullptr, "sceUsbGpsSaveInitData", '?', "" }, - {0X6EED4811, &WrapI_V, "sceUsbGpsClose", 'i', "" }, - {0X7C16AC3A, &WrapI_U, "sceUsbGpsGetState", 'i', "x"}, - {0X934EC2B2, &WrapI_UU, "sceUsbGpsGetData", 'i', "xx" }, - {0X9D8F99E8, nullptr, "sceUsbGpsSetPowerSaveMode", '?', "" }, - {0X9F267D34, &WrapI_V, "sceUsbGpsOpen", 'i', "" }, - {0XA259CD67, nullptr, "sceUsbGpsReset", '?', "" }, + {0X268F95CA, nullptr, "sceUsbGpsSetInitDataLocation", '?', "" }, + {0X31F95CDE, nullptr, "sceUsbGpsGetPowerSaveMode", '?', "" }, + {0X54D26AA4, &WrapI_U, "sceUsbGpsGetInitDataLocation", 'i', "x" }, + {0X63D1F89D, nullptr, "sceUsbGpsResetInitialPosition", '?', "" }, + {0X69E4AAA8, nullptr, "sceUsbGpsSaveInitData", '?', "" }, + {0X6EED4811, &WrapI_V, "sceUsbGpsClose", 'i', "" }, + {0X7C16AC3A, &WrapI_U, "sceUsbGpsGetState", 'i', "x"}, + {0X934EC2B2, &WrapI_UU, "sceUsbGpsGetData", 'i', "xx" }, + {0X9D8F99E8, nullptr, "sceUsbGpsSetPowerSaveMode", '?', "" }, + {0X9F267D34, &WrapI_V, "sceUsbGpsOpen", 'i', "" }, + {0XA259CD67, nullptr, "sceUsbGpsReset", '?', "" }, }; void Register_sceUsbGps() @@ -111,7 +120,7 @@ void GPS::init() { gpsData.bearing = 35.0f; satData.satellites_in_view = 6; - for (int i = 0; i < satData.satellites_in_view; i++) { + for (unsigned char i = 0; i < satData.satellites_in_view; i++) { satData.satInfo[i].id = i + 1; // 1 .. 32 satData.satInfo[i].elevation = i * 10; satData.satInfo[i].azimuth = i * 50; @@ -122,7 +131,7 @@ void GPS::init() { void GPS::setGpsTime(time_t *time) { struct tm *gpsTime; - gpsTime = localtime(time); + gpsTime = gmtime(time); gpsData.year = (short)(gpsTime->tm_year + 1900); gpsData.month = (short)(gpsTime->tm_mon + 1); @@ -132,9 +141,10 @@ void GPS::setGpsTime(time_t *time) { gpsData.second = (short)gpsTime->tm_sec; } -void GPS::setGpsData(float latitude, float longitude, float altitude, float speed, float bearing, long long gpsTime) { +void GPS::setGpsData(long long gpsTime, float hdop, float latitude, float longitude, float altitude, float speed, float bearing) { setGpsTime((time_t*)&gpsTime); + gpsData.hdop = hdop; gpsData.latitude = latitude; gpsData.longitude = longitude; gpsData.altitude = altitude; @@ -142,6 +152,15 @@ void GPS::setGpsData(float latitude, float longitude, float altitude, float spee gpsData.bearing = bearing; } +void GPS::setSatInfo(short index, unsigned char id, unsigned char elevation, short azimuth, unsigned char snr, unsigned char good) { + satData.satInfo[index].id = id; + satData.satInfo[index].elevation = elevation; + satData.satInfo[index].azimuth = azimuth; + satData.satInfo[index].snr = snr; + satData.satInfo[index].good = good; + satData.satellites_in_view = index + 1; +} + GpsData* GPS::getGpsData() { return &gpsData; } diff --git a/Core/HLE/sceUsbGps.h b/Core/HLE/sceUsbGps.h index c9f1b7e8f0..24c2f5ccd0 100644 --- a/Core/HLE/sceUsbGps.h +++ b/Core/HLE/sceUsbGps.h @@ -17,10 +17,13 @@ #pragma once +#include "Core/HLE/FunctionWrappers.h" + void Register_sceUsbGps(); void __UsbGpsInit(); void __UsbGpsDoState(PointerWrap &p); +void __UsbGpsShutdown(); #pragma pack(push, 1) @@ -62,7 +65,8 @@ typedef struct { namespace GPS { void init(); void setGpsTime(time_t *time); - void setGpsData(float latitude, float longitude, float altitude, float speed, float bearing, long long time); + void setGpsData(long long time, float hdop, float latitude, float longitude, float altitude, float speed, float bearing); + void setSatInfo(short index, unsigned char id, unsigned char elevation, short azimuth, unsigned char snr, unsigned char good); GpsData *getGpsData(); SatData *getSatData(); } diff --git a/Core/HW/Camera.cpp b/Core/HW/Camera.cpp new file mode 100644 index 0000000000..3c233d8931 --- /dev/null +++ b/Core/HW/Camera.cpp @@ -0,0 +1,447 @@ +// Copyright (c) 2020- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include "Camera.h" + +void convert_frame(int inw, int inh, unsigned char *inData, AVPixelFormat inFormat, + int outw, int outh, unsigned char **outData, int *outLen) { + + struct SwsContext *sws_context = sws_getContext( + inw, inh, inFormat, + outw, outh, AV_PIX_FMT_RGB24, + SWS_BICUBIC, NULL, NULL, NULL); + + // resize + uint8_t *src[4] = {0}; + uint8_t *dst[4] = {0}; + int srcStride[4], dstStride[4]; + + unsigned char *rgbData = (unsigned char*)malloc(outw * outh * 4); + + av_image_fill_linesizes(srcStride, inFormat, inw); + av_image_fill_linesizes(dstStride, AV_PIX_FMT_RGB24, outw); + + av_image_fill_pointers(src, inFormat, inh, inData, srcStride); + av_image_fill_pointers(dst, AV_PIX_FMT_RGB24, outh, rgbData, dstStride); + + sws_scale(sws_context, + src, srcStride, 0, inh, + dst, dstStride); + + // compress jpeg + *outLen = outw * outh * 2; + *outData = (unsigned char*)malloc(*outLen); + + jpge::params params; + params.m_quality = 60; + params.m_subsampling = jpge::H2V2; + params.m_two_pass_flag = false; + jpge::compress_image_to_jpeg_file_in_memory( + *outData, *outLen, outw, outh, 3, rgbData, params); + free(rgbData); +} + +void __cameraDummyImage(int width, int height, unsigned char** outData, int* outLen) { + unsigned char* rgbData = (unsigned char*)malloc(3 * width * height); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + rgbData[3 * (y * width + x) + 0] = x*255/width; + rgbData[3 * (y * width + x) + 1] = x*255/width; + rgbData[3 * (y * width + x) + 2] = y*255/height; + } + } + + *outLen = width * height * 2; + *outData = (unsigned char*)malloc(*outLen); + + jpge::params params; + params.m_quality = 60; + params.m_subsampling = jpge::H2V2; + params.m_two_pass_flag = false; + jpge::compress_image_to_jpeg_file_in_memory( + *outData, *outLen, width, height, 3, rgbData, params); + free(rgbData); +} + + +#if defined(USING_QT_UI) + +std::vector __qt_getDeviceList() { + std::vector deviceList; + const QList cameras = QCameraInfo::availableCameras(); + for (const QCameraInfo &cameraInfo : cameras) { + deviceList.push_back(cameraInfo.deviceName().toStdString() + + " (" + cameraInfo.description().toStdString() + ")"); + } + return deviceList; +} + +QList MyViewfinder::supportedPixelFormats(QAbstractVideoBuffer::HandleType handleType) const { + Q_UNUSED(handleType); + // Return the formats you will support + return QList() + << QVideoFrame::Format_RGB24 + << QVideoFrame::Format_YUYV + ; +} + +bool MyViewfinder::present(const QVideoFrame &frame) { + if (frame.isValid()) { + QVideoFrame cloneFrame(frame); + cloneFrame.map(QAbstractVideoBuffer::ReadOnly); + + unsigned char *jpegData = nullptr; + int jpegLen = 0; + + QVideoFrame::PixelFormat frameFormat = cloneFrame.pixelFormat(); + if (frameFormat == QVideoFrame::Format_RGB24) { + convert_frame(cloneFrame.size().width(), cloneFrame.size().height(), + (unsigned char*)cloneFrame.bits(), AV_PIX_FMT_RGB24, + qtc_ideal_width, qtc_ideal_height, &jpegData, &jpegLen); + + } else if (frameFormat == QVideoFrame::Format_YUYV) { + convert_frame(cloneFrame.size().width(), cloneFrame.size().height(), + (unsigned char*)cloneFrame.bits(), AV_PIX_FMT_YUYV422, + qtc_ideal_width, qtc_ideal_height, &jpegData, &jpegLen); + } + + if (jpegData) { + Camera::pushCameraImage(jpegLen, jpegData); + free(jpegData); + jpegData = nullptr; + } + + cloneFrame.unmap(); + return true; + } + return false; +} + +int __qt_startCapture(int width, int height) { + if (qt_camera != nullptr) { + ERROR_LOG(HLE, "camera already started"); + return -1; + } + + char selectedCamera[80]; + sscanf(g_Config.sCameraDevice.c_str(), "%80s ", &selectedCamera[0]); + + const QList availableCameras = QCameraInfo::availableCameras(); + if (availableCameras.size() < 1) { + delete qt_camera; + qt_camera = nullptr; + ERROR_LOG(HLE, "no camera found"); + return -1; + } + for (const QCameraInfo &cameraInfo : availableCameras) { + if (cameraInfo.deviceName() == selectedCamera) { + qt_camera = new QCamera(cameraInfo); + } + } + if (qt_camera == nullptr) { + qt_camera = new QCamera(); + if (qt_camera == nullptr) { + ERROR_LOG(HLE, "cannot open camera"); + return -1; + } + } + + qtc_ideal_width = width; + qtc_ideal_height = height; + + qt_viewfinder = new MyViewfinder; + + QCameraViewfinderSettings viewfinderSettings = qt_camera->viewfinderSettings(); + viewfinderSettings.setResolution(640, 480); + viewfinderSettings.setMinimumFrameRate(15.0); + viewfinderSettings.setMaximumFrameRate(15.0); + + qt_camera->setViewfinderSettings(viewfinderSettings); + qt_camera->setViewfinder(qt_viewfinder); + qt_camera->start(); + + return 0; +} + +int __qt_stopCapture() { + if (qt_camera != nullptr) { + qt_camera->stop(); + qt_camera->unload(); + delete qt_camera; + delete qt_viewfinder; + qt_camera = nullptr; + } + return 0; +} + +//endif defined(USING_QT_UI) +#elif PPSSPP_PLATFORM(LINUX) && !PPSSPP_PLATFORM(ANDROID) + +std::vector __v4l_getDeviceList() { + std::vector deviceList; + for (int i = 0; i < 64; i++) { + char path[256]; + snprintf(path, sizeof(path), "/dev/video%d", i); + if (access(path, F_OK) < 0) { + break; + } + int fd = -1; + if((fd = open(path, O_RDONLY)) < 0) { + ERROR_LOG(HLE, "Cannot open '%s'; errno=%d(%s)", path, errno, strerror(errno)); + continue; + } + struct v4l2_capability video_cap; + if(ioctl(fd, VIDIOC_QUERYCAP, &video_cap) < 0) { + ERROR_LOG(HLE, "VIDIOC_QUERYCAP"); + goto cont; + } else { + char device[256]; + snprintf(device, sizeof(device), "%d:%s", i, video_cap.card); + deviceList.push_back(device); + } +cont: + close(fd); + fd = -1; + } + return deviceList; +} + +void *v4l_loop(void *data) { + setCurrentThreadName("v4l_loop"); + while (v4l_fd >= 0) { + struct v4l2_buffer buf; + memset(&buf, 0, sizeof(buf)); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + + if (ioctl(v4l_fd, VIDIOC_DQBUF, &buf) == -1) { + ERROR_LOG(HLE, "VIDIOC_DQBUF; errno=%d(%s)", errno, strerror(errno)); + switch (errno) { + case EAGAIN: + continue; + default: + return NULL; + } + } + + unsigned char *jpegData = nullptr; + int jpegLen = 0; + + if (v4l_format == V4L2_PIX_FMT_YUYV) { + convert_frame(v4l_hw_width, v4l_hw_height, (unsigned char*)v4l_buffer, AV_PIX_FMT_YUYV422, + v4l_ideal_width, v4l_ideal_height, &jpegData, &jpegLen); + } else if (v4l_format == V4L2_PIX_FMT_MJPEG) { + // decompress jpeg + int width, height, req_comps; + unsigned char *rgbData = jpgd::decompress_jpeg_image_from_memory( + (unsigned char*)v4l_buffer, buf.bytesused, &width, &height, &req_comps, 3); + + convert_frame(v4l_hw_width, v4l_hw_height, (unsigned char*)v4l_buffer, AV_PIX_FMT_RGB24, + v4l_ideal_width, v4l_ideal_height, &jpegData, &jpegLen); + free(rgbData); + } + + if (jpegData) { + Camera::pushCameraImage(jpegLen, jpegData); + free(jpegData); + jpegData = nullptr; + } + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + if (ioctl(v4l_fd, VIDIOC_QBUF, &buf) == -1) { + ERROR_LOG(HLE, "VIDIOC_QBUF"); + return NULL; + } + } +} + +int __v4l_startCapture(int ideal_width, int ideal_height) { + if (v4l_fd >= 0) { + __v4l_stopCapture(); + } + v4l_ideal_width = ideal_width; + v4l_ideal_height = ideal_height; + + int dev_index = 0; + char dev_name[64]; + sscanf(g_Config.sCameraDevice.c_str(), "%d:", &dev_index); + snprintf(dev_name, sizeof(dev_name), "/dev/video%d", dev_index); + + if ((v4l_fd = open(dev_name, O_RDWR)) == -1) { + ERROR_LOG(HLE, "Cannot open '%s'; errno=%d(%s)", dev_name, errno, strerror(errno)); + return -1; + } + + struct v4l2_capability cap; + memset(&cap, 0, sizeof(cap)); + if (ioctl(v4l_fd, VIDIOC_QUERYCAP, &cap) == -1) { + ERROR_LOG(HLE, "VIDIOC_QUERYCAP"); + return -1; + } + if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { + ERROR_LOG(HLE, "V4L2_CAP_VIDEO_CAPTURE"); + return -1; + } + if (!(cap.capabilities & V4L2_CAP_STREAMING)) { + ERROR_LOG(HLE, "V4L2_CAP_STREAMING"); + return -1; + } + + struct v4l2_format fmt; + memset(&fmt, 0, sizeof(fmt)); + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt.fmt.pix.pixelformat = 0; + + // select a pixel format + struct v4l2_fmtdesc desc; + memset(&desc, 0, sizeof(desc)); + desc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + while (ioctl(v4l_fd, VIDIOC_ENUM_FMT, &desc) == 0) { + desc.index++; + INFO_LOG(HLE, "V4L2: pixel format supported: %s", desc.description); + if (fmt.fmt.pix.pixelformat != 0) { + continue; + } else if (desc.pixelformat == V4L2_PIX_FMT_YUYV + || desc.pixelformat == V4L2_PIX_FMT_MJPEG) { + INFO_LOG(HLE, "V4L2: %s selected", desc.description); + fmt.fmt.pix.pixelformat = desc.pixelformat; + v4l_format = desc.pixelformat; + } + } + if (fmt.fmt.pix.pixelformat == 0) { + ERROR_LOG(HLE, "V4L2: No supported format found"); + return -1; + } + + // select a frame size + fmt.fmt.pix.width = 0; + fmt.fmt.pix.height = 0; + struct v4l2_frmsizeenum frmsize; + memset(&frmsize, 0, sizeof(frmsize)); + frmsize.pixel_format = fmt.fmt.pix.pixelformat; + while (ioctl(v4l_fd, VIDIOC_ENUM_FRAMESIZES, &frmsize) == 0) { + frmsize.index++; + if (frmsize.type == V4L2_FRMSIZE_TYPE_DISCRETE) { + INFO_LOG(HLE, "V4L2: frame size supported: %dx%d", frmsize.discrete.width, frmsize.discrete.height); + if (frmsize.discrete.width >= ideal_width && frmsize.discrete.height >= ideal_height + && fmt.fmt.pix.width == 0 && fmt.fmt.pix.height == 0 + || frmsize.discrete.width >= ideal_width && frmsize.discrete.height >= ideal_height + && frmsize.discrete.width < fmt.fmt.pix.width && frmsize.discrete.height < fmt.fmt.pix.height) { + fmt.fmt.pix.width = frmsize.discrete.width; + fmt.fmt.pix.height = frmsize.discrete.height; + } + } + } + + if (fmt.fmt.pix.width == 0 && fmt.fmt.pix.height == 0) { + fmt.fmt.pix.width = ideal_width; + fmt.fmt.pix.height = ideal_height; + } + INFO_LOG(HLE, "V4L2: asking for %dx%d", fmt.fmt.pix.width, fmt.fmt.pix.height); + if (ioctl(v4l_fd, VIDIOC_S_FMT, &fmt) == -1) { + ERROR_LOG(HLE, "VIDIOC_S_FMT"); + return -1; + } + v4l_hw_width = fmt.fmt.pix.width; + v4l_hw_height = fmt.fmt.pix.height; + INFO_LOG(HLE, "V4L2: will receive %dx%d", v4l_hw_width, v4l_hw_height); + v4l_height_fixed_aspect = v4l_hw_width * ideal_height / ideal_width; + INFO_LOG(HLE, "V4L2: will use %dx%d", v4l_hw_width, v4l_height_fixed_aspect); + + struct v4l2_requestbuffers req; + memset(&req, 0, sizeof(req)); + req.count = 1; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_MMAP; + if (ioctl(v4l_fd, VIDIOC_REQBUFS, &req) == -1) { + ERROR_LOG(HLE, "VIDIOC_REQBUFS"); + return -1; + } + + struct v4l2_buffer buf; + memset(&buf, 0, sizeof(buf)); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = 0; + if (ioctl(v4l_fd, VIDIOC_QUERYBUF, &buf) == -1) { + ERROR_LOG(HLE, "VIDIOC_QUERYBUF"); + return -1; + } + + v4l_length = buf.length; + v4l_buffer = mmap(NULL, + v4l_length, + PROT_READ | PROT_WRITE, + MAP_SHARED, + v4l_fd, buf.m.offset); + if (v4l_buffer == MAP_FAILED) { + ERROR_LOG(HLE, "MAP_FAILED"); + return -1; + } + + memset(&buf, 0, sizeof(buf)); + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.type = type; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = 0; + if (ioctl(v4l_fd, VIDIOC_QBUF, &buf) == -1) { + ERROR_LOG(HLE, "VIDIOC_QBUF"); + return -1; + } + + if (ioctl(v4l_fd, VIDIOC_STREAMON, &type) == -1) { + ERROR_LOG(HLE, "VIDIOC_STREAMON"); + return -1; + } + + pthread_create(&v4l_thread, NULL, v4l_loop, NULL); + + return 0; +} + +int __v4l_stopCapture() { + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + if (v4l_fd < 0) { + goto exit; + } + + if (ioctl(v4l_fd, VIDIOC_STREAMOFF, &type) == -1) { + ERROR_LOG(HLE, "VIDIOC_STREAMOFF"); + goto exit; + } + + if (munmap(v4l_buffer, v4l_length) == -1) { + ERROR_LOG(HLE, "munmap"); + goto exit; + } + + if (close(v4l_fd) == -1) { + ERROR_LOG(HLE, "close"); + goto exit; + } + + v4l_fd = -1; + //pthread_join(v4l_thread, NULL); + +exit: + v4l_fd = -1; + return 0; +} + +#endif // PPSSPP_PLATFORM(LINUX) && !PPSSPP_PLATFORM(ANDROID) diff --git a/Core/HW/Camera.h b/Core/HW/Camera.h new file mode 100644 index 0000000000..9308e1e732 --- /dev/null +++ b/Core/HW/Camera.h @@ -0,0 +1,78 @@ +// Copyright (c) 2020- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +#include "ppsspp_config.h" +#include "Core/Config.h" +#include "Core/HLE/sceUsbCam.h" +#include "Log.h" + +#include "ext/jpge/jpgd.h" +#include "ext/jpge/jpge.h" + +extern "C" { +#include "libswscale/swscale.h" +#include "libavutil/imgutils.h" +} + +void __cameraDummyImage(int width, int height, unsigned char** outData, int* outLen); + +#if defined(USING_QT_UI) +#include +#include + + class MyViewfinder : public QAbstractVideoSurface { + Q_OBJECT + public: + QList supportedPixelFormats(QAbstractVideoBuffer::HandleType handleType) const; + bool present(const QVideoFrame &frame); + }; + + static int qtc_ideal_width; + static int qtc_ideal_height; + static QCamera *qt_camera; + static QAbstractVideoSurface *qt_viewfinder; + + std::vector __qt_getDeviceList(); + int __qt_startCapture(int width, int height); + int __qt_stopCapture(); + +#elif PPSSPP_PLATFORM(LINUX) && !PPSSPP_PLATFORM(ANDROID) +#include +#include +#include +#include + +#include "thread/threadutil.h" + + static int v4l_fd = -1; + static uint32_t v4l_format; + static int v4l_hw_width; + static int v4l_hw_height; + static int v4l_height_fixed_aspect; + static int v4l_ideal_width; + static int v4l_ideal_height; + + static pthread_t v4l_thread; + static void *v4l_buffer; + static int v4l_length; + + std::vector __v4l_getDeviceList(); + int __v4l_startCapture(int width, int height); + int __v4l_stopCapture(); +#endif diff --git a/Core/HW/StereoResampler.cpp b/Core/HW/StereoResampler.cpp index 8bf58c3f4c..3894e6d771 100644 --- a/Core/HW/StereoResampler.cpp +++ b/Core/HW/StereoResampler.cpp @@ -71,7 +71,7 @@ StereoResampler::StereoResampler() // Some Android devices are v-synced to non-60Hz framerates. We simply timestretch audio to fit. // TODO: should only do this if auto frameskip is off? - float refresh = System_GetPropertyInt(SYSPROP_DISPLAY_REFRESH_RATE) / 1000.0f; + float refresh = System_GetPropertyFloat(SYSPROP_DISPLAY_REFRESH_RATE); // If framerate is "close"... if (refresh != 60.0f && refresh > 50.0f && refresh < 70.0f) { diff --git a/Core/MIPS/MIPSAnalyst.cpp b/Core/MIPS/MIPSAnalyst.cpp index 7a3459f808..9b951848be 100644 --- a/Core/MIPS/MIPSAnalyst.cpp +++ b/Core/MIPS/MIPSAnalyst.cpp @@ -175,6 +175,7 @@ static const HardHashTableEntry hardcodedHashes[] = { { 0x3024e961d1811dea, 396, "fmod", }, { 0x3050bfd0e729dfbf, 220, "atvoffroadfuryblazintrails_download_frame", }, // ATV Offroad Fury Blazin' Trails (US) { 0x30c9c4f420573eb6, 540, "expf", }, + { 0x311779b4db21dbf3, 124, "motorstorm_pixel_read" }, // Motorstorm Arctic Edge (US) { 0x317afeb882ff324a, 212, "memcpy", }, // Mimana (US) { 0x31ea2e192f5095a1, 52, "vector_add_t", }, { 0x31f523ef18898e0e, 420, "logf", }, diff --git a/Core/PSPLoaders.cpp b/Core/PSPLoaders.cpp index 801a49485c..86097a63c2 100644 --- a/Core/PSPLoaders.cpp +++ b/Core/PSPLoaders.cpp @@ -313,10 +313,18 @@ bool Load_PSP_ISO(FileLoader *fileLoader, std::string *error_string) { static std::string NormalizePath(const std::string &path) { #ifdef _WIN32 - wchar_t buf[512] = {0}; std::wstring wpath = ConvertUTF8ToWString(path); - if (GetFullPathName(wpath.c_str(), (int)ARRAY_SIZE(buf) - 1, buf, NULL) == 0) - return ""; + std::wstring buf; + buf.resize(512); + size_t sz = GetFullPathName(wpath.c_str(), (DWORD)buf.size(), &buf[0], nullptr); + if (sz != 0 && sz < buf.size()) { + buf.resize(sz); + } else if (sz > buf.size()) { + buf.resize(sz); + sz = GetFullPathName(wpath.c_str(), (DWORD)buf.size(), &buf[0], nullptr); + // This should truncate off the null terminator. + buf.resize(sz); + } return ConvertWStringToUTF8(buf); #else char buf[PATH_MAX + 1]; diff --git a/Core/SaveState.cpp b/Core/SaveState.cpp index 86bc96b4e8..7ab068abc5 100644 --- a/Core/SaveState.cpp +++ b/Core/SaveState.cpp @@ -382,7 +382,7 @@ namespace SaveState return StringFromFormat("%s (%c)", title.c_str(), slotChar); } if (detectSlot(UNDO_STATE_EXTENSION)) { - I18NCategory *sy = GetI18NCategory("System"); + auto sy = GetI18NCategory("System"); // Allow the number to be positioned where it makes sense. std::string undo = sy->T("undo %c"); return title + " (" + StringFromFormat(undo.c_str(), slotChar) + ")"; @@ -403,7 +403,7 @@ namespace SaveState } // The file can't be loaded - let's note that. - I18NCategory *sy = GetI18NCategory("System"); + auto sy = GetI18NCategory("System"); return File::GetFilename(filename) + " " + sy->T("(broken)"); } @@ -438,7 +438,7 @@ namespace SaveState if (!fn.empty()) { Load(fn, callback, cbUserData); } else { - I18NCategory *sy = GetI18NCategory("System"); + auto sy = GetI18NCategory("System"); if (callback) callback(Status::FAILURE, sy->T("Failed to load state. Error in the file system."), cbUserData); } @@ -495,7 +495,7 @@ namespace SaveState SaveScreenshot(shot, Callback(), 0); Save(fn + ".tmp", renameCallback, cbUserData); } else { - I18NCategory *sy = GetI18NCategory("System"); + auto sy = GetI18NCategory("System"); if (callback) callback(Status::FAILURE, sy->T("Failed to save state. Error in the file system."), cbUserData); } @@ -730,7 +730,7 @@ namespace SaveState std::string reason; std::string title; - I18NCategory *sc = GetI18NCategory("Screen"); + auto sc = GetI18NCategory("Screen"); const char *i18nLoadFailure = sc->T("Load savestate failed", ""); const char *i18nSaveFailure = sc->T("Save State Failed", ""); if (strlen(i18nLoadFailure) == 0) diff --git a/Core/System.cpp b/Core/System.cpp index f36cbcec83..5ccd0ddd55 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -23,6 +23,9 @@ #include #include #include +#if !PPSSPP_PLATFORM(UWP) +#include "Windows/W32Util/ShellUtil.h" +#endif #endif #include @@ -560,14 +563,14 @@ void InitSysDirectories() { // We set g_Config.memStickDirectory outside. #else - wchar_t myDocumentsPath[MAX_PATH]; - const HRESULT result = SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, myDocumentsPath); - const std::string myDocsPath = ConvertWStringToUTF8(myDocumentsPath) + "/PPSSPP/"; + // Caller sets this to the Documents folder. + const std::string rootMyDocsPath = g_Config.internalDataDirectory; + const std::string myDocsPath = rootMyDocsPath + "/PPSSPP/"; const std::string installedFile = path + "installed.txt"; const bool installed = File::Exists(installedFile); // If installed.txt exists(and we can determine the Documents directory) - if (installed && (result == S_OK)) { + if (installed && rootMyDocsPath.size() > 0) { #if defined(_WIN32) && defined(__MINGW32__) std::ifstream inputFile(installedFile); #else diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index 6087c87aeb..d7c08f7ff8 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -168,7 +168,7 @@ bool TextureReplacer::LoadIniValues(IniFile &ini, bool isOverride) { } if (filenameWarning) { - I18NCategory *err = GetI18NCategory("Error"); + auto err = GetI18NCategory("Error"); host->NotifyUserMessage(err->T("textures.ini filenames may not be cross-platform"), 6.0f); } diff --git a/Core/Util/GameManager.cpp b/Core/Util/GameManager.cpp index d731de0476..1e23a5fa79 100644 --- a/Core/Util/GameManager.cpp +++ b/Core/Util/GameManager.cpp @@ -261,7 +261,7 @@ bool GameManager::InstallGame(const std::string &url, const std::string &fileNam return InstallRawISO(fileName, shortFilename, deleteAfter); } - I18NCategory *sy = GetI18NCategory("System"); + auto sy = GetI18NCategory("System"); installInProgress_ = true; std::string pspGame = GetSysDirectory(DIRECTORY_GAME); @@ -312,7 +312,7 @@ bool GameManager::InstallGame(const std::string &url, const std::string &fileNam } bool GameManager::DetectTexturePackDest(struct zip *z, int iniIndex, std::string *dest) { - I18NCategory *iz = GetI18NCategory("InstallZip"); + auto iz = GetI18NCategory("InstallZip"); struct zip_stat zstat; zip_stat_index(z, iniIndex, 0, &zstat); @@ -491,7 +491,7 @@ bool GameManager::InstallMemstickGame(struct zip *z, const std::string &zipfile, size_t allBytes = 0; size_t bytesCopied = 0; - I18NCategory *sy = GetI18NCategory("System"); + auto sy = GetI18NCategory("System"); auto fileAllowed = [&](const char *fn) { if (!allowRoot && strchr(fn, '/') == 0) diff --git a/GPU/Common/FramebufferCommon.cpp b/GPU/Common/FramebufferCommon.cpp index da2352c6af..d514c868b6 100644 --- a/GPU/Common/FramebufferCommon.cpp +++ b/GPU/Common/FramebufferCommon.cpp @@ -1908,7 +1908,7 @@ void FramebufferManagerCommon::UpdateFramebufUsage(VirtualFramebuffer *vfb) { } void FramebufferManagerCommon::ShowScreenResolution() { - I18NCategory *gr = GetI18NCategory("Graphics"); + auto gr = GetI18NCategory("Graphics"); std::ostringstream messageStream; messageStream << gr->T("Internal Resolution") << ": "; diff --git a/GPU/Common/SoftwareTransformCommon.cpp b/GPU/Common/SoftwareTransformCommon.cpp index 21d45951f7..2949d8e335 100644 --- a/GPU/Common/SoftwareTransformCommon.cpp +++ b/GPU/Common/SoftwareTransformCommon.cpp @@ -62,7 +62,7 @@ static void SwapUVs(TransformedVertex &a, TransformedVertex &b) { // Note: 0 is BR and 2 is TL. -static void RotateUV(TransformedVertex v[4], float flippedMatrix[16], float ySign) { +static void RotateUV(TransformedVertex v[4], float flippedMatrix[16], bool flippedY) { // Transform these two coordinates to figure out whether they're flipped or not. Vec4f tl; Vec3ByMatrix44(tl.AsArray(), v[2].pos, flippedMatrix); @@ -70,6 +70,8 @@ static void RotateUV(TransformedVertex v[4], float flippedMatrix[16], float ySig Vec4f br; Vec3ByMatrix44(br.AsArray(), v[0].pos, flippedMatrix); + float ySign = flippedY ? -1.0 : 1.0; + const float invtlw = 1.0f / tl.w; const float invbrw = 1.0f / br.w; const float x1 = tl.x * invtlw; @@ -153,6 +155,7 @@ static int ColorIndexOffset(int prim, GEShadeMode shadeMode, bool clearMode) { return 0; } +// NOTE: The viewport must be up to date! void SoftwareTransform( int prim, int vertexCount, u32 vertType, u16 *&inds, int indexType, const DecVtxFormat &decVtxFormat, int &maxIndex, TransformedVertex *&drawBuffer, int &numTrans, bool &drawIndexed, const SoftwareTransformParams *params, SoftwareTransformResult *result) { @@ -161,7 +164,6 @@ void SoftwareTransform( TextureCacheCommon *texCache = params->texCache; TransformedVertex *transformed = params->transformed; TransformedVertex *transformedExpanded = params->transformedExpanded; - float ySign = 1.0f; bool throughmode = (vertType & GE_VTYPE_THROUGH_MASK) != 0; bool lmode = gstate.isUsingSecondaryColor() && gstate.isLightingEnabled(); @@ -515,20 +517,24 @@ void SoftwareTransform( numTrans = 0; drawIndexed = false; + bool useBufferedRendering = g_Config.iRenderingMode != FB_NON_BUFFERED_MODE; + + bool flippedY = g_Config.iGPUBackend == (int)GPUBackend::OPENGL && !useBufferedRendering; + if (prim != GE_PRIM_RECTANGLES) { // We can simply draw the unexpanded buffer. numTrans = vertexCount; drawIndexed = true; } else { - bool useBufferedRendering = g_Config.iRenderingMode != FB_NON_BUFFERED_MODE; - if (useBufferedRendering) - ySign = -ySign; + // Pretty bad hackery where we re-do the transform (in RotateUV) to see if the vertices are flipped in screen space. + // Since we've already got API-specific assumptions (Y direction, etc) baked into the projMatrix (which we arguably shouldn't), + // this gets nasty and very hard to understand. float flippedMatrix[16]; if (!throughmode) { memcpy(&flippedMatrix, gstate.projMatrix, 16 * sizeof(float)); - const bool invertedY = useBufferedRendering ? (gstate_c.vpHeight < 0) : (gstate_c.vpHeight > 0); + const bool invertedY = flippedY ? (gstate_c.vpHeight < 0) : (gstate_c.vpHeight > 0); if (invertedY) { flippedMatrix[1] = -flippedMatrix[1]; flippedMatrix[5] = -flippedMatrix[5]; @@ -584,7 +590,7 @@ void SoftwareTransform( if (throughmode) RotateUVThrough(trans); else - RotateUV(trans, flippedMatrix, ySign); + RotateUV(trans, flippedMatrix, flippedY); // Triangle: BR-TR-TL indsOut[0] = i * 2 + 0; diff --git a/GPU/Directx9/DrawEngineDX9.cpp b/GPU/Directx9/DrawEngineDX9.cpp index 9d02936e71..954aa32d07 100644 --- a/GPU/Directx9/DrawEngineDX9.cpp +++ b/GPU/Directx9/DrawEngineDX9.cpp @@ -43,17 +43,16 @@ namespace DX9 { -const D3DPRIMITIVETYPE glprim[8] = { +static const D3DPRIMITIVETYPE d3d_prim[8] = { D3DPT_POINTLIST, D3DPT_LINELIST, D3DPT_LINESTRIP, D3DPT_TRIANGLELIST, D3DPT_TRIANGLESTRIP, D3DPT_TRIANGLEFAN, - D3DPT_TRIANGLELIST, // With OpenGL ES we have to expand sprites into triangles, tripling the data instead of doubling. sigh. OpenGL ES, Y U NO SUPPORT GL_QUADS? + D3DPT_TRIANGLELIST, }; -// hrydgard's quick guesses - TODO verify static const int D3DPRIMITIVEVERTEXCOUNT[8][2] = { {0, 0}, // invalid {1, 0}, // 1 = D3DPT_POINTLIST, @@ -499,9 +498,9 @@ rotateVBO: device_->SetVertexDeclaration(pHardwareVertexDecl); if (vb_ == NULL) { if (useElements) { - device_->DrawIndexedPrimitiveUP(glprim[prim], 0, maxIndex + 1, D3DPrimCount(glprim[prim], vertexCount), decIndex, D3DFMT_INDEX16, decoded, dec_->GetDecVtxFmt().stride); + device_->DrawIndexedPrimitiveUP(d3d_prim[prim], 0, maxIndex + 1, D3DPrimCount(d3d_prim[prim], vertexCount), decIndex, D3DFMT_INDEX16, decoded, dec_->GetDecVtxFmt().stride); } else { - device_->DrawPrimitiveUP(glprim[prim], D3DPrimCount(glprim[prim], vertexCount), decoded, dec_->GetDecVtxFmt().stride); + device_->DrawPrimitiveUP(d3d_prim[prim], D3DPrimCount(d3d_prim[prim], vertexCount), decoded, dec_->GetDecVtxFmt().stride); } } else { device_->SetStreamSource(0, vb_, 0, dec_->GetDecVtxFmt().stride); @@ -509,9 +508,9 @@ rotateVBO: if (useElements) { device_->SetIndices(ib_); - device_->DrawIndexedPrimitive(glprim[prim], 0, 0, maxIndex + 1, 0, D3DPrimCount(glprim[prim], vertexCount)); + device_->DrawIndexedPrimitive(d3d_prim[prim], 0, 0, maxIndex + 1, 0, D3DPrimCount(d3d_prim[prim], vertexCount)); } else { - device_->DrawPrimitive(glprim[prim], 0, D3DPrimCount(glprim[prim], vertexCount)); + device_->DrawPrimitive(d3d_prim[prim], 0, D3DPrimCount(d3d_prim[prim], vertexCount)); } } } @@ -568,9 +567,9 @@ rotateVBO: device_->SetVertexDeclaration(transformedVertexDecl_); if (drawIndexed) { - device_->DrawIndexedPrimitiveUP(glprim[prim], 0, maxIndex, D3DPrimCount(glprim[prim], numTrans), inds, D3DFMT_INDEX16, drawBuffer, sizeof(TransformedVertex)); + device_->DrawIndexedPrimitiveUP(d3d_prim[prim], 0, maxIndex, D3DPrimCount(d3d_prim[prim], numTrans), inds, D3DFMT_INDEX16, drawBuffer, sizeof(TransformedVertex)); } else { - device_->DrawPrimitiveUP(glprim[prim], D3DPrimCount(glprim[prim], numTrans), drawBuffer, sizeof(TransformedVertex)); + device_->DrawPrimitiveUP(d3d_prim[prim], D3DPrimCount(d3d_prim[prim], numTrans), drawBuffer, sizeof(TransformedVertex)); } } else if (result.action == SW_CLEAR) { u32 clearColor = result.color; diff --git a/GPU/Directx9/GPU_DX9.cpp b/GPU/Directx9/GPU_DX9.cpp index 2a2d3ecda0..f54bd4e071 100644 --- a/GPU/Directx9/GPU_DX9.cpp +++ b/GPU/Directx9/GPU_DX9.cpp @@ -101,7 +101,7 @@ GPU_DX9::GPU_DX9(GraphicsContext *gfxCtx, Draw::DrawContext *draw) // Disable hardware tessellation bacause DX9 is still unsupported. g_Config.bHardwareTessellation = false; ERROR_LOG(G3D, "Hardware Tessellation is unsupported, falling back to software tessellation"); - I18NCategory *gr = GetI18NCategory("Graphics"); + auto gr = GetI18NCategory("Graphics"); host->NotifyUserMessage(gr->T("Turn off Hardware Tessellation - unsupported"), 2.5f, 0xFF3030FF); } } diff --git a/GPU/Directx9/ShaderManagerDX9.cpp b/GPU/Directx9/ShaderManagerDX9.cpp index dc5f7d50f8..b77d9d0ce3 100644 --- a/GPU/Directx9/ShaderManagerDX9.cpp +++ b/GPU/Directx9/ShaderManagerDX9.cpp @@ -243,14 +243,18 @@ void ShaderManagerDX9::VSSetMatrix(int creg, const float* pMatrix) { static void ConvertProjMatrixToD3D(Matrix4x4 &in, bool invertedX, bool invertedY) { // Half pixel offset hack float xoff = 1.0f / gstate_c.curRTRenderWidth; - xoff = gstate_c.vpXOffset + (invertedX ? xoff : -xoff); - float yoff = -1.0f / gstate_c.curRTRenderHeight; - yoff = gstate_c.vpYOffset + (invertedY ? yoff : -yoff); + if (invertedX) { + xoff = -gstate_c.vpXOffset - xoff; + } else { + xoff = gstate_c.vpXOffset - xoff; + } - if (invertedX) - xoff = -xoff; - if (invertedY) - yoff = -yoff; + float yoff = -1.0f / gstate_c.curRTRenderHeight; + if (invertedY) { + yoff = gstate_c.vpYOffset - yoff; + } else { + yoff = -gstate_c.vpYOffset - yoff; + } const Vec3 trans(xoff, yoff, gstate_c.vpZOffset * 0.5f + 0.5f); const Vec3 scale(gstate_c.vpWidthScale, gstate_c.vpHeightScale, gstate_c.vpDepthScale * 0.5f); @@ -595,7 +599,7 @@ VSShader *ShaderManagerDX9::ApplyShader(int prim, u32 vertType) { vs = new VSShader(device_, VSID, codeBuffer_, useHWTransform); if (vs->Failed()) { - I18NCategory *gr = GetI18NCategory("Graphics"); + auto gr = GetI18NCategory("Graphics"); ERROR_LOG(G3D, "Shader compilation failed, falling back to software transform"); if (!g_Config.bHideSlowWarnings) { host->NotifyUserMessage(gr->T("hardware transform error - falling back to software"), 2.5f, 0xFF3030FF); diff --git a/GPU/GLES/DrawEngineGLES.cpp b/GPU/GLES/DrawEngineGLES.cpp index a934a25790..2f272def84 100644 --- a/GPU/GLES/DrawEngineGLES.cpp +++ b/GPU/GLES/DrawEngineGLES.cpp @@ -562,15 +562,18 @@ rotateVBO: if (vertexCount > 0x10000 / 3) vertexCount = 0x10000 / 3; #endif + + if (textureNeedsApply) + textureCache_->ApplyTexture(); + + // Need to ApplyDrawState after ApplyTexture because depal can launch a render pass and that wrecks the state. + ApplyDrawState(prim); + SoftwareTransform( prim, vertexCount, dec_->VertexType(), inds, GE_VTYPE_IDX_16BIT, dec_->GetDecVtxFmt(), maxIndex, drawBuffer, numTrans, drawIndexed, ¶ms, &result); - if (textureNeedsApply) - textureCache_->ApplyTexture(); - - ApplyDrawState(prim); ApplyDrawStateLate(result.setStencil, result.stencilValue); LinkedShader *program = shaderManager_->ApplyFragmentShader(vsid, vshader, lastVType_, prim); diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp index 967aaf6709..97b0ecf178 100644 --- a/GPU/GLES/GPU_GLES.cpp +++ b/GPU/GLES/GPU_GLES.cpp @@ -112,7 +112,7 @@ GPU_GLES::GPU_GLES(GraphicsContext *gfxCtx, Draw::DrawContext *draw) if (!gstate_c.SupportsAll(GPU_SUPPORTS_VERTEX_TEXTURE_FETCH | GPU_SUPPORTS_TEXTURE_FLOAT) || !hasTexelFetch) { g_Config.bHardwareTessellation = false; ERROR_LOG(G3D, "Hardware Tessellation is unsupported, falling back to software tessellation"); - I18NCategory *gr = GetI18NCategory("Graphics"); + auto gr = GetI18NCategory("Graphics"); host->NotifyUserMessage(gr->T("Turn off Hardware Tessellation - unsupported"), 2.5f, 0xFF3030FF); } } diff --git a/GPU/GLES/ShaderManagerGLES.cpp b/GPU/GLES/ShaderManagerGLES.cpp index 2629743f7e..e57c125d10 100644 --- a/GPU/GLES/ShaderManagerGLES.cpp +++ b/GPU/GLES/ShaderManagerGLES.cpp @@ -678,7 +678,7 @@ Shader *ShaderManagerGLES::ApplyVertexShader(int prim, u32 vertType, VShaderID * // Vertex shader not in cache. Let's compile it. vs = CompileVertexShader(*VSID); if (vs->Failed()) { - I18NCategory *gr = GetI18NCategory("Graphics"); + auto gr = GetI18NCategory("Graphics"); ERROR_LOG(G3D, "Shader compilation failed, falling back to software transform"); if (!g_Config.bHideSlowWarnings) { host->NotifyUserMessage(gr->T("hardware transform error - falling back to software"), 2.5f, 0xFF3030FF); diff --git a/GPU/GLES/TextureCacheGLES.cpp b/GPU/GLES/TextureCacheGLES.cpp index d4c6f0f964..9c036b85a7 100644 --- a/GPU/GLES/TextureCacheGLES.cpp +++ b/GPU/GLES/TextureCacheGLES.cpp @@ -219,7 +219,7 @@ void TextureCacheGLES::StartFrame() { lowMemoryMode_ = true; decimationCounter_ = 0; - I18NCategory *err = GetI18NCategory("Error"); + auto err = GetI18NCategory("Error"); if (standardScaleFactor_ > 1) { host->NotifyUserMessage(err->T("Warning: Video memory FULL, reducing upscaling and switching to slow caching mode"), 2.0f); } else { diff --git a/GPU/Vulkan/DrawEngineVulkan.cpp b/GPU/Vulkan/DrawEngineVulkan.cpp index b5ca090dc4..9a3ee27ed3 100644 --- a/GPU/Vulkan/DrawEngineVulkan.cpp +++ b/GPU/Vulkan/DrawEngineVulkan.cpp @@ -895,6 +895,14 @@ void DrawEngineVulkan::DoFlush() { params.allowSeparateAlphaClear = false; params.provokeFlatFirst = true; + // We need to update the viewport early because it's checked for flipping in SoftwareTransform. + // We don't have a "DrawStateEarly" in vulkan, so... + // TODO: Probably should eventually refactor this and feed the vp size into SoftwareTransform directly (Unknown's idea). + if (gstate_c.IsDirty(DIRTY_VIEWPORTSCISSOR_STATE)) { + gstate_c.vpWidth = gstate.getViewportXScale() * 2.0f; + gstate_c.vpHeight = gstate.getViewportYScale() * 2.0f; + } + int maxIndex = indexGen.MaxIndex(); SoftwareTransform( prim, indexGen.VertexCount(), diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index 9cad5fdfbc..07d9fa95a1 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -1127,7 +1127,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { // TODO: We should stall the GPU here and wipe things out of memory. // As is, it will almost definitely fail the second time, but next frame it may recover. - I18NCategory *err = GetI18NCategory("Error"); + auto err = GetI18NCategory("Error"); if (scaleFactor > 1) { host->NotifyUserMessage(err->T("Warning: Video memory FULL, reducing upscaling and switching to slow caching mode"), 2.0f); } else { diff --git a/Qt/QtMain.cpp b/Qt/QtMain.cpp index 9aa200641a..7dd7469163 100644 --- a/Qt/QtMain.cpp +++ b/Qt/QtMain.cpp @@ -38,13 +38,15 @@ #include "util/text/utf8.h" #include "Core/Config.h" #include "Core/ConfigValues.h" +#include "Core/HW/Camera.h" #include MainUI *emugl = nullptr; -static int refreshRate = 60000; +static float refreshRate = 60.f; static int browseFileEvent = -1; static int browseFolderEvent = -1; +QTCamera *qtcamera = nullptr; #ifdef SDL static SDL_AudioDeviceID audioDev = 0; @@ -108,7 +110,7 @@ std::string System_GetProperty(SystemProperty prop) { #elif defined(_WIN32) return "Qt:Windows"; #elif defined(Q_OS_MAC) - return "Qt:Mac"; + return "Qt:macOS"; #else return "Qt"; #endif @@ -145,8 +147,6 @@ int System_GetPropertyInt(SystemProperty prop) { switch (prop) { case SYSPROP_AUDIO_SAMPLE_RATE: return 44100; - case SYSPROP_DISPLAY_REFRESH_RATE: - return refreshRate; case SYSPROP_DEVICE_TYPE: #if defined(__ANDROID__) return DEVICE_TYPE_MOBILE; @@ -159,6 +159,21 @@ int System_GetPropertyInt(SystemProperty prop) { #else return DEVICE_TYPE_DESKTOP; #endif + case SYSPROP_DISPLAY_COUNT: + return QApplication::screens().size(); + default: + return -1; + } +} + +float System_GetPropertyFloat(SystemProperty prop) { + switch (prop) { + case SYSPROP_DISPLAY_REFRESH_RATE: + return refreshRate; + case SYSPROP_DISPLAY_LOGICAL_DPI: + return QApplication::primaryScreen()->logicalDotsPerInch(); + case SYSPROP_DISPLAY_DPI: + return QApplication::primaryScreen()->physicalDotsPerInch(); default: return -1; } @@ -191,6 +206,14 @@ void System_SendMessage(const char *command, const char *parameter) { } else if (!strcmp(command, "graphics_restart")) { // Should find a way to properly restart the app. qApp->exit(0); + } else if (!strcmp(command, "camera_command")) { + if (!strncmp(parameter, "startVideo", 10)) { + int width = 0, height = 0; + sscanf(parameter, "startVideo_%dx%d", &width, &height); + emit(qtcamera->onStartCamera(width, height)); + } else if (!strcmp(parameter, "stopVideo")) { + emit(qtcamera->onStopCamera()); + } } else if (!strcmp(command, "setclipboardtext")) { QApplication::clipboard()->setText(parameter); #if defined(SDL) @@ -229,16 +252,6 @@ void LaunchBrowser(const char *url) QDesktopServices::openUrl(QUrl(url)); } -float CalculateDPIScale() -{ - // Sane default rather than check DPI -#if defined(USING_GLES2) - return 1.2f; -#else - return 1.0f; -#endif -} - static int mainInternal(QApplication &a) { #ifdef MOBILE_DEVICE emugl = new MainUI(); @@ -468,7 +481,7 @@ bool MainUI::event(QEvent *e) { } break; } else if (e->type() == browseFolderEvent) { - I18NCategory *mm = GetI18NCategory("MainMenu"); + auto mm = GetI18NCategory("MainMenu"); QString fileName = QFileDialog::getExistingDirectory(nullptr, mm->T("Choose folder"), g_Config.currentDirectory.c_str()); if (QDir(fileName).exists()) { NativeMessageReceived("browse_folderSelect", fileName.toStdString().c_str()); @@ -603,6 +616,14 @@ void MainAudio::timerEvent(QTimerEvent *) { #endif +void QTCamera::startCamera(int width, int height) { + __qt_startCapture(width, height); +} + +void QTCamera::stopCamera() { + __qt_stopCapture(); +} + #ifndef SDL Q_DECL_EXPORT #endif @@ -628,19 +649,26 @@ int main(int argc, char *argv[]) QGLFormat::setDefaultFormat(format); QApplication a(argc, argv); - QSize res = QApplication::desktop()->screenGeometry().size(); + QScreen* screen = a.primaryScreen(); + QSizeF res = screen->physicalSize(); + if (res.width() < res.height()) res.transpose(); pixel_xres = res.width(); pixel_yres = res.height(); - g_dpi_scale_x = CalculateDPIScale(); - g_dpi_scale_y = CalculateDPIScale(); + + g_dpi_scale_x = screen->logicalDotsPerInchX() / screen->physicalDotsPerInchX(); + g_dpi_scale_y = screen->logicalDotsPerInchY() / screen->physicalDotsPerInchY(); g_dpi_scale_real_x = g_dpi_scale_x; g_dpi_scale_real_y = g_dpi_scale_y; dp_xres = (int)(pixel_xres * g_dpi_scale_x); dp_yres = (int)(pixel_yres * g_dpi_scale_y); - refreshRate = (int)(a.primaryScreen()->refreshRate() * 1000); + refreshRate = screen->refreshRate(); + + qtcamera = new QTCamera; + QObject::connect(qtcamera, SIGNAL(onStartCamera(int, int)), qtcamera, SLOT(startCamera(int, int))); + QObject::connect(qtcamera, SIGNAL(onStopCamera()), qtcamera, SLOT(stopCamera())); std::string savegame_dir = "."; std::string external_dir = "."; @@ -669,4 +697,3 @@ int main(int argc, char *argv[]) glslang::FinalizeProcess(); return ret; } - diff --git a/Qt/QtMain.h b/Qt/QtMain.h index bf351c84f7..7391a59f54 100644 --- a/Qt/QtMain.h +++ b/Qt/QtMain.h @@ -147,6 +147,21 @@ private: std::atomic emuThreadState; }; +class QTCamera : public QObject { + Q_OBJECT +public: + QTCamera() {} + ~QTCamera() {}; + +signals: + void onStartCamera(int width, int height); + void onStopCamera(); + +public slots: + void startCamera(int width, int height); + void stopCamera(); +}; + extern MainUI* emugl; #ifndef SDL diff --git a/Qt/macbundle.sh b/Qt/macbundle.sh new file mode 100644 index 0000000000..f906e0d77f --- /dev/null +++ b/Qt/macbundle.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +PPSSPP="${1}" +PPSSPPQt="${PPSSPP}/Contents/MacOS/PPSSPPQt" + +if [ ! -f "${PPSSPPQt}" ]; then + echo "No such file: ${PPSSPPQt}!" + exit 0 +fi + +plutil -replace NSPrincipalClass -string NSApplication ${PPSSPP}/Contents/Info.plist +plutil -replace NSHighResolutionCapable -bool YES ${PPSSPP}/Contents/Info.plist + +plutil -replace NSLocationWhenInUseUsageDescription -string "Your location may be used to emulate Go!Explore, a GPS accessory" ${PPSSPP}/Contents/Info.plist +plutil -replace NSCameraUsageDescription -string "Your camera may be used to emulate Go!Cam, a camera accessory" ${PPSSPP}/Contents/Info.plist +plutil -replace NSMicrophoneUsageDescription -string "Your microphone may be used to emulate Go!Cam/Talkman, a microphone accessory" ${PPSSPP}/Contents/Info.plist + +# TODO: install SDL and Qt frameworks diff --git a/Qt/mainwindow.cpp b/Qt/mainwindow.cpp index 4b4a6aaaf7..8b59c683ea 100644 --- a/Qt/mainwindow.cpp +++ b/Qt/mainwindow.cpp @@ -24,7 +24,7 @@ MainWindow::MainWindow(QWidget *parent, bool fullscreen) : { QDesktopWidget *desktop = QApplication::desktop(); int screenNum = QProcessEnvironment::systemEnvironment().value("SDL_VIDEO_FULLSCREEN_HEAD", "0").toInt(); - + // Move window to the center of selected screen QRect rect = desktop->screenGeometry(screenNum); move((rect.width()-frameGeometry().width()) / 4, (rect.height()-frameGeometry().height()) / 4); @@ -295,19 +295,22 @@ void MainWindow::consoleAct() void MainWindow::raiseTopMost() { setWindowState( (windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); - raise(); + raise(); activateWindow(); } void MainWindow::SetFullScreen(bool fullscreen) { if (fullscreen) { +#if !PPSSPP_PLATFORM(MAC) menuBar()->hide(); - + emugl->setFixedSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); + // TODO: Shouldn't this be physicalSize()? emugl->resizeGL(emugl->size().width(), emugl->size().height()); // TODO: Won't showFullScreen do this for us? setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); setFixedSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); +#endif showFullScreen(); InitPadLayout(dp_xres, dp_yres); @@ -315,8 +318,10 @@ void MainWindow::SetFullScreen(bool fullscreen) { if (GetUIState() == UISTATE_INGAME && !g_Config.bShowTouchControls) QApplication::setOverrideCursor(QCursor(Qt::BlankCursor)); } else { +#if !PPSSPP_PLATFORM(MAC) menuBar()->show(); updateMenus(); +#endif showNormal(); SetWindowScale(-1); @@ -346,7 +351,7 @@ void MainWindow::forumAct() QDesktopServices::openUrl(QUrl("https://forums.ppsspp.org/")); } -void MainWindow::gitAct() +void MainWindow::gitAct() { QDesktopServices::openUrl(QUrl("https://github.com/hrydgard/ppsspp/")); } @@ -394,9 +399,14 @@ void MainWindow::SetWindowScale(int zoom) { g_Config.iWindowWidth = width; g_Config.iWindowHeight = height; +#if !PPSSPP_PLATFORM(MAC) emugl->setFixedSize(g_Config.iWindowWidth, g_Config.iWindowHeight); + // TODO: Shouldn't this be scaled size? emugl->resizeGL(g_Config.iWindowWidth, g_Config.iWindowHeight); setFixedSize(sizeHint()); +#else + resize(g_Config.iWindowWidth, g_Config.iWindowHeight); +#endif } void MainWindow::SetGameTitle(QString text) @@ -585,7 +595,7 @@ void MainWindow::createMenus() } } langMenu->addActions(langGroup->actions()); - + // Help MenuTree* helpMenu = new MenuTree(this, menuBar(), QT_TR_NOOP("&Help")); helpMenu->add(new MenuAction(this, SLOT(websiteAct()), QT_TR_NOOP("Official &website"), QKeySequence::HelpContents)); diff --git a/SDL/SDLCocoaMetalLayer.h b/SDL/SDLCocoaMetalLayer.h new file mode 100644 index 0000000000..28c7849e56 --- /dev/null +++ b/SDL/SDLCocoaMetalLayer.h @@ -0,0 +1,5 @@ +#pragma once + +extern "C" { +void *makeWindowMetalCompatible(void *window); +} diff --git a/SDL/SDLCocoaMetalLayer.mm b/SDL/SDLCocoaMetalLayer.mm new file mode 100644 index 0000000000..d0df82716b --- /dev/null +++ b/SDL/SDLCocoaMetalLayer.mm @@ -0,0 +1,37 @@ +#if defined(PPSSPP_PLATFORM_MAC) +#import +#else +#import +#endif +#import + +#include "base/logging.h" +#include "SDLCocoaMetalLayer.h" + +void *makeWindowMetalCompatible(void *window) { + // https://github.com/KhronosGroup/MoltenVK/issues/78#issuecomment-371118536 +#if defined(PPSSPP_PLATFORM_MAC) + NSView *view = ((NSWindow *)window).contentView; + assert([view isKindOfClass:[NSView class]]); + + if (![view.layer isKindOfClass:[CAMetalLayer class]]) + { + [view setLayer:[CAMetalLayer layer]]; + } + return view.layer; +#else + UIView *view = (UIView *)window; + assert([view isKindOfClass:[UIView class]]); + + CAMetalLayer *metalLayer = [CAMetalLayer new]; + + CGSize viewSize = view.frame.size; + metalLayer.frame = view.frame; + metalLayer.opaque = true; + metalLayer.framebufferOnly = true; + metalLayer.drawableSize = viewSize; + metalLayer.pixelFormat = (MTLPixelFormat)80;//BGRA8Unorm==80 + [view.layer addSublayer:metalLayer]; + return metalLayer; +#endif +} diff --git a/SDL/SDLGLGraphicsContext.h b/SDL/SDLGLGraphicsContext.h index 651f7a534b..666aa72774 100644 --- a/SDL/SDLGLGraphicsContext.h +++ b/SDL/SDLGLGraphicsContext.h @@ -1,6 +1,4 @@ -#if !defined(__APPLE__) #include "SDL_syswm.h" -#endif #include "SDL.h" #include "thin3d/GLRenderManager.h" diff --git a/SDL/SDLMain.cpp b/SDL/SDLMain.cpp index 90db5d4195..548d588ea5 100644 --- a/SDL/SDLMain.cpp +++ b/SDL/SDLMain.cpp @@ -24,6 +24,7 @@ SDLJoystick *joystick = NULL; #include "base/logging.h" #include "base/timeutil.h" #include "ext/glslang/glslang/Public/ShaderLang.h" +#include "image/png_load.h" #include "input/input_state.h" #include "input/keycodes.h" #include "net/resolve.h" @@ -35,9 +36,7 @@ SDLJoystick *joystick = NULL; #include "thread/threadutil.h" #include "math.h" -#if !defined(__APPLE__) #include "SDL_syswm.h" -#endif #if defined(VK_USE_PLATFORM_XLIB_KHR) #include @@ -66,6 +65,7 @@ static int g_QuitRequested = 0; static int g_DesktopWidth = 0; static int g_DesktopHeight = 0; +static float g_RefreshRate = 60.f; int getDisplayNumber(void) { int displayNumber = 0; @@ -257,7 +257,7 @@ std::string System_GetProperty(SystemProperty prop) { #elif __linux__ return "SDL:Linux"; #elif __APPLE__ - return "SDL:OSX"; + return "SDL:macOS"; #else return "SDL:"; #endif @@ -307,14 +307,23 @@ int System_GetPropertyInt(SystemProperty prop) { switch (prop) { case SYSPROP_AUDIO_SAMPLE_RATE: return 44100; - case SYSPROP_DISPLAY_REFRESH_RATE: - return 60000; case SYSPROP_DEVICE_TYPE: #if defined(MOBILE_DEVICE) return DEVICE_TYPE_MOBILE; #else return DEVICE_TYPE_DESKTOP; #endif + case SYSPROP_DISPLAY_COUNT: + return SDL_GetNumVideoDisplays(); + default: + return -1; + } +} + +float System_GetPropertyFloat(SystemProperty prop) { + switch (prop) { + case SYSPROP_DISPLAY_REFRESH_RATE: + return g_RefreshRate; default: return -1; } @@ -509,6 +518,7 @@ int main(int argc, char *argv[]) { } g_DesktopWidth = displayMode.w; g_DesktopHeight = displayMode.h; + g_RefreshRate = displayMode.refresh_rate; SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); @@ -629,6 +639,20 @@ int main(int argc, char *argv[]) { SDL_SetWindowTitle(window, (app_name_nice + " " + PPSSPP_GIT_VERSION).c_str()); + char iconPath[PATH_MAX]; + snprintf(iconPath, PATH_MAX, "%sassets/icon_regular_72.png", SDL_GetBasePath() ? SDL_GetBasePath() : ""); + int width = 0, height = 0; + unsigned char *imageData; + if (pngLoad(iconPath, &width, &height, &imageData, false) == 1) { + SDL_Surface *surface = SDL_CreateRGBSurface(0, width, height, 32, + 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000); + memcpy(surface->pixels, imageData, width*height*4); + SDL_SetWindowIcon(window, surface); + SDL_FreeSurface(surface); + free(imageData); + imageData = NULL; + } + // Since we render from the main thread, there's nothing done here, but we call it to avoid confusion. if (!graphicsContext->InitFromRenderThread(&error_message)) { printf("Init from thread error: '%s'\n", error_message.c_str()); diff --git a/SDL/SDLVulkanGraphicsContext.cpp b/SDL/SDLVulkanGraphicsContext.cpp index 3cf55a78c5..02123b105d 100644 --- a/SDL/SDLVulkanGraphicsContext.cpp +++ b/SDL/SDLVulkanGraphicsContext.cpp @@ -8,6 +8,9 @@ #include "Core/System.h" #include "SDLVulkanGraphicsContext.h" +#if defined(VK_USE_PLATFORM_METAL_EXT) +#include "SDLCocoaMetalLayer.h" +#endif bool SDLVulkanGraphicsContext::Init(SDL_Window *&window, int x, int y, int mode, std::string *error_message) { window = SDL_CreateWindow("Initializing Vulkan...", x, y, pixel_xres, pixel_yres, mode); @@ -50,7 +53,6 @@ bool SDLVulkanGraphicsContext::Init(SDL_Window *&window, int x, int y, int mode, return false; } -#if !defined(__APPLE__) SDL_SysWMinfo sys_info{}; SDL_VERSION(&sys_info.version); //Set SDL version if (!SDL_GetWindowWMInfo(window, &sys_info)) { @@ -71,13 +73,23 @@ bool SDLVulkanGraphicsContext::Init(SDL_Window *&window, int x, int y, int mode, case SDL_SYSWM_WAYLAND: vulkan_->InitSurface(WINDOWSYSTEM_WAYLAND, (void*)sys_info.info.wl.display, (void *)sys_info.info.wl.surface); break; +#endif +#if defined(VK_USE_PLATFORM_METAL_EXT) +#if defined(PPSSPP_PLATFORM_MAC) + case SDL_SYSWM_COCOA: + vulkan_->InitSurface(WINDOWSYSTEM_METAL_EXT, makeWindowMetalCompatible(sys_info.info.cocoa.window), nullptr); + break; +#else + case SDL_SYSWM_UIKIT: + vulkan_->InitSurface(WINDOWSYSTEM_METAL_EXT, makeWindowMetalCompatible(sys_info.info.uikit.window), nullptr); + break; +#endif #endif default: fprintf(stderr, "Vulkan subsystem %d not supported\n", sys_info.subsystem); exit(1); break; } -#endif if (!vulkan_->InitObjects()) { *error_message = vulkan_->InitError(); diff --git a/SDL/SDLVulkanGraphicsContext.h b/SDL/SDLVulkanGraphicsContext.h index 5a29e6c3df..6ecfecfe1e 100644 --- a/SDL/SDLVulkanGraphicsContext.h +++ b/SDL/SDLVulkanGraphicsContext.h @@ -1,7 +1,5 @@ #include "SDL.h" -#if !defined(__APPLE__) #include "SDL_syswm.h" -#endif #include "Common/GraphicsContext.h" #include "Common/Vulkan/VulkanContext.h" diff --git a/UI/ComboKeyMappingScreen.cpp b/UI/ComboKeyMappingScreen.cpp index 7860a49710..aa7d4fae63 100644 --- a/UI/ComboKeyMappingScreen.cpp +++ b/UI/ComboKeyMappingScreen.cpp @@ -33,13 +33,13 @@ void Combo_keyScreen::CreateViews() { using namespace UI; - I18NCategory *co = GetI18NCategory("Controls"); + auto co = GetI18NCategory("Controls"); root_ = new LinearLayout(ORIENT_VERTICAL); root_->Add(new ItemHeader(co->T("Combo Key Setting"))); LinearLayout *root__ = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(1.0)); root_->Add(root__); LinearLayout *leftColumn = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(120, FILL_PARENT)); - I18NCategory *di = GetI18NCategory("Dialog"); + auto di = GetI18NCategory("Dialog"); static const int comboKeyImages[5] = { I_1, I_2, I_3, I_4, I_5, @@ -113,7 +113,7 @@ void Combo_keyScreen::CreateViews() { std::map::iterator imageFinder; - I18NCategory *mc = GetI18NCategory("MappableControls"); + auto mc = GetI18NCategory("MappableControls"); for (auto i = keyToggles.begin(); i != keyToggles.end(); ++i) { LinearLayout *row = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); diff --git a/UI/ControlMappingScreen.cpp b/UI/ControlMappingScreen.cpp index 8b9763503b..fd65d63efe 100644 --- a/UI/ControlMappingScreen.cpp +++ b/UI/ControlMappingScreen.cpp @@ -88,7 +88,7 @@ void ControlMapper::Update() { void ControlMapper::Refresh() { bool hasFocus = UI::GetFocusedView() == this; Clear(); - I18NCategory *mc = GetI18NCategory("MappableControls"); + auto mc = GetI18NCategory("MappableControls"); std::map keyImages; keyImages["Circle"] = I_CIRCLE; @@ -186,28 +186,28 @@ void ControlMapper::MappedCallback(KeyDef kdf) { UI::EventReturn ControlMapper::OnReplace(UI::EventParams ¶ms) { actionIndex_ = atoi(params.v->Tag().c_str()); action_ = REPLACEONE; - I18NCategory *km = GetI18NCategory("KeyMapping"); + auto km = GetI18NCategory("KeyMapping"); scrm_->push(new KeyMappingNewKeyDialog(pspKey_, true, std::bind(&ControlMapper::MappedCallback, this, std::placeholders::_1), km)); return UI::EVENT_DONE; } UI::EventReturn ControlMapper::OnReplaceAll(UI::EventParams ¶ms) { action_ = REPLACEALL; - I18NCategory *km = GetI18NCategory("KeyMapping"); + auto km = GetI18NCategory("KeyMapping"); scrm_->push(new KeyMappingNewKeyDialog(pspKey_, true, std::bind(&ControlMapper::MappedCallback, this, std::placeholders::_1), km)); return UI::EVENT_DONE; } UI::EventReturn ControlMapper::OnAdd(UI::EventParams ¶ms) { action_ = ADD; - I18NCategory *km = GetI18NCategory("KeyMapping"); + auto km = GetI18NCategory("KeyMapping"); scrm_->push(new KeyMappingNewKeyDialog(pspKey_, true, std::bind(&ControlMapper::MappedCallback, this, std::placeholders::_1), km)); return UI::EVENT_DONE; } UI::EventReturn ControlMapper::OnAddMouse(UI::EventParams ¶ms) { action_ = ADD; g_Config.bMapMouse = true; - I18NCategory *km = GetI18NCategory("KeyMapping"); + auto km = GetI18NCategory("KeyMapping"); scrm_->push(new KeyMappingNewMouseKeyDialog(pspKey_, true, std::bind(&ControlMapper::MappedCallback, this, std::placeholders::_1), km)); return UI::EVENT_DONE; } @@ -224,7 +224,7 @@ void ControlMappingScreen::CreateViews() { using namespace UI; mappers_.clear(); - I18NCategory *km = GetI18NCategory("KeyMapping"); + auto km = GetI18NCategory("KeyMapping"); root_ = new LinearLayout(ORIENT_HORIZONTAL); @@ -275,7 +275,7 @@ UI::EventReturn ControlMappingScreen::OnAutoConfigure(UI::EventParams ¶ms) { for (auto s = seenPads.begin(), end = seenPads.end(); s != end; ++s) { items.push_back(*s); } - I18NCategory *km = GetI18NCategory("KeyMapping"); + auto km = GetI18NCategory("KeyMapping"); ListPopupScreen *autoConfList = new ListPopupScreen(km->T("Autoconfigure for device"), items, -1); if (params.v) autoConfList->SetPopupOrigin(params.v); @@ -306,8 +306,8 @@ void ControlMappingScreen::KeyMapped(int pspkey) { // Notification to let us re void KeyMappingNewKeyDialog::CreatePopupContents(UI::ViewGroup *parent) { using namespace UI; - I18NCategory *km = GetI18NCategory("KeyMapping"); - I18NCategory *mc = GetI18NCategory("MappableControls"); + auto km = GetI18NCategory("KeyMapping"); + auto mc = GetI18NCategory("MappableControls"); std::string pspButtonName = KeyMap::GetPspButtonName(this->pspBtn_); @@ -334,7 +334,7 @@ bool KeyMappingNewKeyDialog::key(const KeyInput &key) { void KeyMappingNewMouseKeyDialog::CreatePopupContents(UI::ViewGroup *parent) { using namespace UI; - I18NCategory *km = GetI18NCategory("KeyMapping"); + auto km = GetI18NCategory("KeyMapping"); parent->Add(new TextView(std::string(km->T("You can press ESC to cancel.")), new LinearLayoutParams(Margins(10, 0)))); } @@ -544,7 +544,7 @@ bool AnalogTestScreen::axis(const AxisInput &axis) { void AnalogTestScreen::CreateViews() { using namespace UI; - I18NCategory *di = GetI18NCategory("Dialog"); + auto di = GetI18NCategory("Dialog"); root_ = new LinearLayout(ORIENT_VERTICAL); @@ -627,8 +627,8 @@ bool TouchTestScreen::touch(const TouchInput &touch) { void TouchTestScreen::CreateViews() { using namespace UI; - I18NCategory *di = GetI18NCategory("Dialog"); - I18NCategory *gr = GetI18NCategory("Graphics"); + auto di = GetI18NCategory("Dialog"); + auto gr = GetI18NCategory("Graphics"); root_ = new LinearLayout(ORIENT_VERTICAL); LinearLayout *theTwo = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(1.0f)); root_->Add(theTwo); diff --git a/UI/ControlMappingScreen.h b/UI/ControlMappingScreen.h index 77f5c90df5..130596ee05 100644 --- a/UI/ControlMappingScreen.h +++ b/UI/ControlMappingScreen.h @@ -18,8 +18,9 @@ #pragma once #include -#include +#include #include +#include #include "i18n/i18n.h" #include "ui/view.h" @@ -51,7 +52,7 @@ private: class KeyMappingNewKeyDialog : public PopupScreen { public: - explicit KeyMappingNewKeyDialog(int btn, bool replace, std::function callback, I18NCategory *i18n) + explicit KeyMappingNewKeyDialog(int btn, bool replace, std::function callback, std::shared_ptr i18n) : PopupScreen(i18n->T("Map Key"), "Cancel", ""), callback_(callback), mapped_(false) { pspBtn_ = btn; } @@ -74,7 +75,7 @@ private: class KeyMappingNewMouseKeyDialog : public PopupScreen { public: - explicit KeyMappingNewMouseKeyDialog(int btn, bool replace, std::function callback, I18NCategory *i18n) + explicit KeyMappingNewMouseKeyDialog(int btn, bool replace, std::function callback, std::shared_ptr i18n) : PopupScreen(i18n->T("Map Mouse"), "", ""), callback_(callback), mapped_(false) { pspBtn_ = btn; } diff --git a/UI/CwCheatScreen.cpp b/UI/CwCheatScreen.cpp index ee2147bc15..a685d6d705 100644 --- a/UI/CwCheatScreen.cpp +++ b/UI/CwCheatScreen.cpp @@ -79,8 +79,8 @@ void CwCheatScreen::CreateCodeList() { void CwCheatScreen::CreateViews() { using namespace UI; - I18NCategory *cw = GetI18NCategory("CwCheats"); - I18NCategory *di = GetI18NCategory("Dialog"); + auto cw = GetI18NCategory("CwCheats"); + auto di = GetI18NCategory("Dialog"); CreateCodeList(); g_Config.bReloadCheats = true; root_ = new LinearLayout(ORIENT_HORIZONTAL); diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index f83e3e2f08..1bc9d42267 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -66,14 +66,15 @@ static const char *logLevelList[] = { void DevMenu::CreatePopupContents(UI::ViewGroup *parent) { using namespace UI; - I18NCategory *dev = GetI18NCategory("Developer"); - I18NCategory *sy = GetI18NCategory("System"); + auto dev = GetI18NCategory("Developer"); + auto sy = GetI18NCategory("System"); ScrollView *scroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 1.0f)); LinearLayout *items = new LinearLayout(ORIENT_VERTICAL); items->Add(new CheckBox(&g_Config.bShowFrameProfiler, dev->T("Frame Profiler"), "")); items->Add(new CheckBox(&g_Config.bSimpleFrameStats, dev->T("Simple Frame Stats"), "")); + items->Add(new CheckBox(&g_Config.bDrawFrameGraph, dev->T("Draw Frametimes Graph"))); #if !defined(MOBILE_DEVICE) items->Add(new Choice(dev->T("Log View")))->OnClick.Handle(this, &DevMenu::OnLogView); #endif @@ -89,6 +90,7 @@ void DevMenu::CreatePopupContents(UI::ViewGroup *parent) { items->Add(new Choice(dev->T("Dump Frame GPU Commands")))->OnClick.Handle(this, &DevMenu::OnDumpFrame); items->Add(new Choice(dev->T("Toggle Audio Debug")))->OnClick.Handle(this, &DevMenu::OnToggleAudioDebug); + scroll->Add(items); parent->Add(scroll); @@ -188,7 +190,7 @@ void LogScreen::update() { void LogScreen::CreateViews() { using namespace UI; - I18NCategory *di = GetI18NCategory("Dialog"); + auto di = GetI18NCategory("Dialog"); LinearLayout *outer = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); root_ = outer; @@ -222,8 +224,8 @@ UI::EventReturn LogScreen::OnSubmit(UI::EventParams &e) { void LogConfigScreen::CreateViews() { using namespace UI; - I18NCategory *di = GetI18NCategory("Dialog"); - I18NCategory *dev = GetI18NCategory("Developer"); + auto di = GetI18NCategory("Dialog"); + auto dev = GetI18NCategory("Developer"); root_ = new ScrollView(ORIENT_VERTICAL); @@ -293,7 +295,7 @@ UI::EventReturn LogConfigScreen::OnLogLevelChange(UI::EventParams &e) { } UI::EventReturn LogConfigScreen::OnLogLevel(UI::EventParams &e) { - I18NCategory *dev = GetI18NCategory("Developer"); + auto dev = GetI18NCategory("Developer"); auto logLevelScreen = new LogLevelScreen(dev->T("Log Level")); logLevelScreen->OnChoice.Handle(this, &LogConfigScreen::OnLogLevelChange); @@ -363,8 +365,8 @@ static const JitDisableFlag jitDisableFlags[] = { void JitDebugScreen::CreateViews() { using namespace UI; - I18NCategory *di = GetI18NCategory("Dialog"); - I18NCategory *dev = GetI18NCategory("Developer"); + auto di = GetI18NCategory("Dialog"); + auto dev = GetI18NCategory("Developer"); root_ = new ScrollView(ORIENT_VERTICAL); @@ -416,9 +418,9 @@ void SystemInfoScreen::CreateViews() { using namespace UI; // NOTE: Do not translate this section. It will change a lot and will be impossible to keep up. - I18NCategory *di = GetI18NCategory("Dialog"); - I18NCategory *si = GetI18NCategory("SysInfo"); - I18NCategory *gr = GetI18NCategory("Graphics"); + auto di = GetI18NCategory("Dialog"); + auto si = GetI18NCategory("SysInfo"); + auto gr = GetI18NCategory("Graphics"); root_ = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT)); ViewGroup *leftColumn = new AnchorLayout(new LinearLayoutParams(1.0f)); @@ -521,7 +523,11 @@ void SystemInfoScreen::CreateViews() { deviceSpecs->Add(new InfoItem(si->T("Native Resolution"), StringFromFormat("%dx%d", System_GetPropertyInt(SYSPROP_DISPLAY_XRES), System_GetPropertyInt(SYSPROP_DISPLAY_YRES)))); - deviceSpecs->Add(new InfoItem(si->T("Refresh rate"), StringFromFormat("%0.3f Hz", (float)System_GetPropertyInt(SYSPROP_DISPLAY_REFRESH_RATE) / 1000.0f))); +#endif + +#if !PPSSPP_PLATFORM(WINDOWS) + // Don't show on Windows, since it's always treated as 60 there. + deviceSpecs->Add(new InfoItem(si->T("Refresh rate"), StringFromFormat("%0.3f Hz", (float)System_GetPropertyFloat(SYSPROP_DISPLAY_REFRESH_RATE)))); #endif #if 0 @@ -673,7 +679,7 @@ void SystemInfoScreen::CreateViews() { void AddressPromptScreen::CreatePopupContents(UI::ViewGroup *parent) { using namespace UI; - I18NCategory *dev = GetI18NCategory("Developer"); + auto dev = GetI18NCategory("Developer"); addrView_ = new TextView(dev->T("Enter address"), ALIGN_HCENTER, false); parent->Add(addrView_); @@ -727,7 +733,7 @@ void AddressPromptScreen::BackspaceDigit() { } void AddressPromptScreen::UpdatePreviewDigits() { - I18NCategory *dev = GetI18NCategory("Developer"); + auto dev = GetI18NCategory("Developer"); if (addr_ != 0) { char temp[32]; @@ -760,8 +766,8 @@ bool AddressPromptScreen::key(const KeyInput &key) { // Three panes: Block chooser, MIPS view, ARM/x86 view void JitCompareScreen::CreateViews() { - I18NCategory *di = GetI18NCategory("Dialog"); - I18NCategory *dev = GetI18NCategory("Developer"); + auto di = GetI18NCategory("Dialog"); + auto dev = GetI18NCategory("Developer"); using namespace UI; @@ -806,7 +812,7 @@ void JitCompareScreen::UpdateDisasm() { using namespace UI; - I18NCategory *dev = GetI18NCategory("Developer"); + auto dev = GetI18NCategory("Developer"); JitBlockCacheDebugInterface *blockCacheDebug = MIPSComp::jit->GetBlockCacheDebugInterface(); @@ -897,7 +903,7 @@ UI::EventReturn JitCompareScreen::OnShowStats(UI::EventParams &e) { UI::EventReturn JitCompareScreen::OnSelectBlock(UI::EventParams &e) { - I18NCategory *dev = GetI18NCategory("Developer"); + auto dev = GetI18NCategory("Developer"); auto addressPrompt = new AddressPromptScreen(dev->T("Block address")); addressPrompt->OnChoice.Handle(this, &JitCompareScreen::OnBlockAddress); @@ -1041,7 +1047,7 @@ struct { DebugShaderType type; const char *name; } shaderTypes[] = { void ShaderListScreen::CreateViews() { using namespace UI; - I18NCategory *di = GetI18NCategory("Dialog"); + auto di = GetI18NCategory("Dialog"); LinearLayout *layout = new LinearLayout(ORIENT_VERTICAL); root_ = layout; @@ -1070,7 +1076,7 @@ UI::EventReturn ShaderListScreen::OnShaderClick(UI::EventParams &e) { void ShaderViewScreen::CreateViews() { using namespace UI; - I18NCategory *di = GetI18NCategory("Dialog"); + auto di = GetI18NCategory("Dialog"); LinearLayout *layout = new LinearLayout(ORIENT_VERTICAL); root_ = layout; diff --git a/UI/DevScreens.h b/UI/DevScreens.h index a6c480bc66..8b98b022c6 100644 --- a/UI/DevScreens.h +++ b/UI/DevScreens.h @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -31,7 +32,7 @@ class DevMenu : public PopupScreen { public: - DevMenu(I18NCategory *i18n) : PopupScreen(i18n->T("Dev Tools")) {} + DevMenu(std::shared_ptr i18n) : PopupScreen(i18n->T("Dev Tools")) {} void CreatePopupContents(UI::ViewGroup *parent) override; void dialogFinished(const Screen *dialog, DialogResult result) override; diff --git a/UI/DiscordIntegration.cpp b/UI/DiscordIntegration.cpp index b88680f048..f8006763c1 100644 --- a/UI/DiscordIntegration.cpp +++ b/UI/DiscordIntegration.cpp @@ -101,7 +101,7 @@ void Discord::SetPresenceGame(const char *gameTitle) { } #ifdef ENABLE_DISCORD - I18NCategory *sc = GetI18NCategory("Screen"); + auto sc = GetI18NCategory("Screen"); DiscordRichPresence discordPresence{}; discordPresence.state = gameTitle; @@ -123,7 +123,7 @@ void Discord::SetPresenceMenu() { } #ifdef ENABLE_DISCORD - I18NCategory *sc = GetI18NCategory("Screen"); + auto sc = GetI18NCategory("Screen"); DiscordRichPresence discordPresence{}; discordPresence.state = sc->T("In menu"); diff --git a/UI/DisplayLayoutScreen.cpp b/UI/DisplayLayoutScreen.cpp index 3f5f14ef13..6a5cedbff5 100644 --- a/UI/DisplayLayoutScreen.cpp +++ b/UI/DisplayLayoutScreen.cpp @@ -231,9 +231,9 @@ void DisplayLayoutScreen::CreateViews() { using namespace UI; - I18NCategory *di = GetI18NCategory("Dialog"); - I18NCategory *gr = GetI18NCategory("Graphics"); - I18NCategory *co = GetI18NCategory("Controls"); + auto di = GetI18NCategory("Dialog"); + auto gr = GetI18NCategory("Graphics"); + auto co = GetI18NCategory("Controls"); root_ = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT)); diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index b7fae08ae5..18f1d5ab3e 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -95,7 +95,7 @@ extern bool g_TakeScreenshot; static void __EmuScreenVblank() { - I18NCategory *sy = GetI18NCategory("System"); + auto sy = GetI18NCategory("System"); if (frameStep_ && lastNumFlips != gpuStats.numFlips) { @@ -192,7 +192,7 @@ void EmuScreen::bootGame(const std::string &filename) { if (!bootAllowStorage(filename)) return; - I18NCategory *sc = GetI18NCategory("Screen"); + auto sc = GetI18NCategory("Screen"); invalid_ = true; @@ -267,17 +267,17 @@ void EmuScreen::bootGame(const std::string &filename) { } if (PSP_CoreParameter().compat.flags().RequireBufferedRendering && g_Config.iRenderingMode == FB_NON_BUFFERED_MODE) { - I18NCategory *gr = GetI18NCategory("Graphics"); + auto gr = GetI18NCategory("Graphics"); host->NotifyUserMessage(gr->T("BufferedRenderingRequired", "Warning: This game requires Rendering Mode to be set to Buffered."), 15.0f); } if (PSP_CoreParameter().compat.flags().RequireBlockTransfer && g_Config.bBlockTransferGPU == false) { - I18NCategory *gr = GetI18NCategory("Graphics"); + auto gr = GetI18NCategory("Graphics"); host->NotifyUserMessage(gr->T("BlockTransferRequired", "Warning: This game requires Simulate Block Transfer Mode to be set to On."), 15.0f); } if (PSP_CoreParameter().compat.flags().RequireDefaultCPUClock && g_Config.iLockedCPUSpeed != 0) { - I18NCategory *gr = GetI18NCategory("Graphics"); + auto gr = GetI18NCategory("Graphics"); host->NotifyUserMessage(gr->T("DefaultCPUClockRequired", "Warning: This game requires the CPU clock to be set to default."), 15.0f); } @@ -293,7 +293,7 @@ void EmuScreen::bootComplete() { NOTICE_LOG(BOOT, "Loading %s...", PSP_CoreParameter().fileToStart.c_str()); autoLoad(); - I18NCategory *sc = GetI18NCategory("Screen"); + auto sc = GetI18NCategory("Screen"); #ifndef MOBILE_DEVICE if (g_Config.bFirstRun) { @@ -318,7 +318,7 @@ void EmuScreen::bootComplete() { #endif if (Core_GetPowerSaving()) { - I18NCategory *sy = GetI18NCategory("System"); + auto sy = GetI18NCategory("System"); #ifdef __ANDROID__ osm.Show(sy->T("WARNING: Android battery save mode is on"), 2.0f, 0xFFFFFF, -1, true, "core_powerSaving"); #else @@ -474,7 +474,7 @@ bool EmuScreen::touch(const TouchInput &touch) { } void EmuScreen::onVKeyDown(int virtualKeyCode) { - I18NCategory *sc = GetI18NCategory("Screen"); + auto sc = GetI18NCategory("Screen"); switch (virtualKeyCode) { case VIRTKEY_UNTHROTTLE: @@ -628,11 +628,14 @@ void EmuScreen::onVKeyDown(int virtualKeyCode) { osm.Show(sc->T("replaceTextures_false", "Textures no longer are being replaced"), 2.0); NativeMessageReceived("gpu_clearCache", ""); break; + case VIRTKEY_RAPID_FIRE: + __CtrlSetRapidFire(true); + break; } } void EmuScreen::onVKeyUp(int virtualKeyCode) { - I18NCategory *sc = GetI18NCategory("Screen"); + auto sc = GetI18NCategory("Screen"); switch (virtualKeyCode) { case VIRTKEY_UNTHROTTLE: @@ -677,6 +680,10 @@ void EmuScreen::onVKeyUp(int virtualKeyCode) { setVKeyAnalogY(CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_Y_MIN, VIRTKEY_AXIS_RIGHT_Y_MAX); break; + case VIRTKEY_RAPID_FIRE: + __CtrlSetRapidFire(false); + break; + default: break; } @@ -975,8 +982,8 @@ protected: void EmuScreen::CreateViews() { using namespace UI; - I18NCategory *sc = GetI18NCategory("Screen"); - I18NCategory *dev = GetI18NCategory("Developer"); + auto sc = GetI18NCategory("Screen"); + auto dev = GetI18NCategory("Developer"); const Bounds &bounds = screenManager()->getUIContext()->GetBounds(); InitPadLayout(bounds.w, bounds.h); @@ -1046,7 +1053,7 @@ void EmuScreen::CreateViews() { } UI::EventReturn EmuScreen::OnDevTools(UI::EventParams ¶ms) { - I18NCategory *dev = GetI18NCategory("Developer"); + auto dev = GetI18NCategory("Developer"); DevMenu *devMenu = new DevMenu(dev); if (params.v) devMenu->SetPopupOrigin(params.v); @@ -1088,7 +1095,7 @@ void EmuScreen::update() { quit_ = true; return; } - I18NCategory *err = GetI18NCategory("Error"); + auto err = GetI18NCategory("Error"); std::string errLoadingFile = gamePath_ + "\n"; errLoadingFile.append(err->T("Error loading file", "Could not load game")); errLoadingFile.append(" "); @@ -1103,9 +1110,6 @@ void EmuScreen::update() { if (invalid_) return; - // Virtual keys. - __CtrlSetRapidFire(virtKeys[VIRTKEY_RAPID_FIRE - VIRTKEY_FIRST]); - // This is here to support the iOS on screen back button. if (pauseTrigger_) { pauseTrigger_ = false; @@ -1209,6 +1213,34 @@ static void DrawFPS(DrawBuffer *draw2d, const Bounds &bounds) { draw2d->SetFontScale(1.0f, 1.0f); } +static void DrawFrameTimes(UIContext *ctx) { + int valid, pos; + double *sleepHistory; + double *history = __DisplayGetFrameTimes(&valid, &pos, &sleepHistory); + int scale = 7000; + int width = 600; + + ctx->Flush(); + ctx->BeginNoTex(); + int bottom = ctx->GetBounds().y2(); + for (int i = 0; i < valid; ++i) { + double activeTime = history[i] - sleepHistory[i]; + ctx->Draw()->vLine(i, bottom, bottom - activeTime * scale, 0xFF3FFF3F); + ctx->Draw()->vLine(i, bottom - activeTime * scale, bottom - history[i] * scale, 0x7F3FFF3F); + } + ctx->Draw()->vLine(pos, bottom, bottom - 512, 0xFFff3F3f); + + ctx->Draw()->hLine(0, bottom - 0.0333*scale, width, 0xFF3f3Fff); + ctx->Draw()->hLine(0, bottom - 0.0167*scale, width, 0xFF3f3Fff); + + ctx->Flush(); + ctx->Begin(); + ctx->Draw()->SetFontScale(0.5f, 0.5f); + ctx->Draw()->DrawText(UBUNTU24, "33.3ms", width, bottom - 0.0333*scale, 0xFF3f3Fff, ALIGN_BOTTOMLEFT | FLAG_DYNAMIC_ASCII); + ctx->Draw()->DrawText(UBUNTU24, "16.7ms", width, bottom - 0.0167*scale, 0xFF3f3Fff, ALIGN_BOTTOMLEFT | FLAG_DYNAMIC_ASCII); + ctx->Draw()->SetFontScale(1.0f, 1.0f); +} + void EmuScreen::preRender() { using namespace Draw; DrawContext *draw = screenManager()->getDrawContext(); @@ -1386,6 +1418,10 @@ void EmuScreen::renderUI() { DrawFPS(draw2d, ctx->GetBounds()); } + if (g_Config.bDrawFrameGraph && !invalid_) { + DrawFrameTimes(ctx); + } + #if !PPSSPP_PLATFORM(UWP) if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN && g_Config.bShowAllocatorDebug) { DrawAllocatorVis(ctx, gpu); diff --git a/UI/GPUDriverTestScreen.cpp b/UI/GPUDriverTestScreen.cpp index bd9f0aa7ab..f9bda115a2 100644 --- a/UI/GPUDriverTestScreen.cpp +++ b/UI/GPUDriverTestScreen.cpp @@ -78,8 +78,8 @@ GPUDriverTestScreen::~GPUDriverTestScreen() { void GPUDriverTestScreen::CreateViews() { // Don't bother with views for now. using namespace UI; - I18NCategory *di = GetI18NCategory("Dialog"); - I18NCategory *cr = GetI18NCategory("PSPCredits"); + auto di = GetI18NCategory("Dialog"); + auto cr = GetI18NCategory("PSPCredits"); AnchorLayout *anchor = new AnchorLayout(); root_ = anchor; diff --git a/UI/GameScreen.cpp b/UI/GameScreen.cpp index 556579e64e..e31f6d7892 100644 --- a/UI/GameScreen.cpp +++ b/UI/GameScreen.cpp @@ -50,9 +50,9 @@ void GameScreen::CreateViews() { if (info && !info->id.empty()) saveDirs = info->GetSaveDataDirectories(); // Get's very heavy, let's not do it in update() - I18NCategory *di = GetI18NCategory("Dialog"); - I18NCategory *ga = GetI18NCategory("Game"); - I18NCategory *pa = GetI18NCategory("Pause"); + auto di = GetI18NCategory("Dialog"); + auto ga = GetI18NCategory("Game"); + auto pa = GetI18NCategory("Pause"); // Information in the top left. // Back button to the bottom left. @@ -176,8 +176,8 @@ void GameScreen::CallbackDeleteConfig(bool yes) { UI::EventReturn GameScreen::OnDeleteConfig(UI::EventParams &e) { - I18NCategory *di = GetI18NCategory("Dialog"); - I18NCategory *ga = GetI18NCategory("Game"); + auto di = GetI18NCategory("Dialog"); + auto ga = GetI18NCategory("Game"); screenManager()->push( new PromptScreen(di->T("DeleteConfirmGameConfig", "Do you really want to delete the settings for this game?"), ga->T("ConfirmDelete"), di->T("Cancel"), std::bind(&GameScreen::CallbackDeleteConfig, this, std::placeholders::_1))); @@ -188,7 +188,7 @@ UI::EventReturn GameScreen::OnDeleteConfig(UI::EventParams &e) void GameScreen::render() { UIScreen::render(); - I18NCategory *ga = GetI18NCategory("Game"); + auto ga = GetI18NCategory("Game"); Draw::DrawContext *thin3d = screenManager()->getDrawContext(); @@ -277,8 +277,8 @@ UI::EventReturn GameScreen::OnGameSettings(UI::EventParams &e) { } UI::EventReturn GameScreen::OnDeleteSaveData(UI::EventParams &e) { - I18NCategory *di = GetI18NCategory("Dialog"); - I18NCategory *ga = GetI18NCategory("Game"); + auto di = GetI18NCategory("Dialog"); + auto ga = GetI18NCategory("Game"); std::shared_ptr info = g_gameInfoCache->GetInfo(NULL, gamePath_, GAMEINFO_WANTBG | GAMEINFO_WANTSIZE); if (info) { // Check that there's any savedata to delete @@ -303,8 +303,8 @@ void GameScreen::CallbackDeleteSaveData(bool yes) { } UI::EventReturn GameScreen::OnDeleteGame(UI::EventParams &e) { - I18NCategory *di = GetI18NCategory("Dialog"); - I18NCategory *ga = GetI18NCategory("Game"); + auto di = GetI18NCategory("Dialog"); + auto ga = GetI18NCategory("Game"); std::shared_ptr info = g_gameInfoCache->GetInfo(NULL, gamePath_, GAMEINFO_WANTBG | GAMEINFO_WANTSIZE); if (info) { screenManager()->push( @@ -380,7 +380,7 @@ SetBackgroundPopupScreen::SetBackgroundPopupScreen(const std::string &title, con } void SetBackgroundPopupScreen::CreatePopupContents(UI::ViewGroup *parent) { - I18NCategory *ga = GetI18NCategory("Game"); + auto ga = GetI18NCategory("Game"); parent->Add(new UI::TextView(ga->T("One moment please..."), ALIGN_LEFT | ALIGN_VCENTER, false, new UI::LinearLayoutParams(UI::Margins(10, 0, 10, 10)))); } @@ -415,7 +415,7 @@ void SetBackgroundPopupScreen::update() { } UI::EventReturn GameScreen::OnSetBackground(UI::EventParams &e) { - I18NCategory *ga = GetI18NCategory("Game"); + auto ga = GetI18NCategory("Game"); // This popup is used to prevent any race condition: // g_gameInfoCache may take time to load the data, and a crash could happen if they exit before then. SetBackgroundPopupScreen *pop = new SetBackgroundPopupScreen(ga->T("Setting Background"), gamePath_); diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 93273c61e7..472d988761 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -58,6 +58,7 @@ #include "Core/Reporting.h" #include "Core/TextureReplacer.h" #include "Core/WebServer.h" +#include "Core/HLE/sceUsbCam.h" #include "GPU/Common/PostShader.h" #include "android/jni/TestRunner.h" #include "GPU/GPUInterface.h" @@ -113,7 +114,7 @@ bool DoesBackendSupportHWTess() { } static std::string PostShaderTranslateName(const char *value) { - I18NCategory *ps = GetI18NCategory("PostShaders"); + auto ps = GetI18NCategory("PostShaders"); const ShaderInfo *info = GetPostShaderInfo(value); if (info) { return ps->T(value, info ? info->name.c_str() : value); @@ -152,16 +153,16 @@ void GameSettingsScreen::CreateViews() { // Scrolling action menu to the right. using namespace UI; - I18NCategory *di = GetI18NCategory("Dialog"); - I18NCategory *gr = GetI18NCategory("Graphics"); - I18NCategory *co = GetI18NCategory("Controls"); - I18NCategory *a = GetI18NCategory("Audio"); - I18NCategory *sa = GetI18NCategory("Savedata"); - I18NCategory *sy = GetI18NCategory("System"); - I18NCategory *n = GetI18NCategory("Networking"); - I18NCategory *ms = GetI18NCategory("MainSettings"); - I18NCategory *dev = GetI18NCategory("Developer"); - I18NCategory *ri = GetI18NCategory("RemoteISO"); + auto di = GetI18NCategory("Dialog"); + auto gr = GetI18NCategory("Graphics"); + auto co = GetI18NCategory("Controls"); + auto a = GetI18NCategory("Audio"); + auto sa = GetI18NCategory("Savedata"); + auto sy = GetI18NCategory("System"); + auto n = GetI18NCategory("Networking"); + auto ms = GetI18NCategory("MainSettings"); + auto dev = GetI18NCategory("Developer"); + auto ri = GetI18NCategory("RemoteISO"); root_ = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT)); @@ -267,6 +268,13 @@ void GameSettingsScreen::CreateViews() { softwareGPU->SetEnabled(false); } + std::vector cameraList = Camera::getDeviceList(); + if (cameraList.size() >= 1) { + graphicsSettings->Add(new ItemHeader(gr->T("Camera"))); + PopupMultiChoiceDynamic *cameraChoice = graphicsSettings->Add(new PopupMultiChoiceDynamic(&g_Config.sCameraDevice, gr->T("Camera Device"), cameraList, nullptr, screenManager())); + cameraChoice->OnChoice.Handle(this, &GameSettingsScreen::OnCameraDeviceChange); + } + graphicsSettings->Add(new ItemHeader(gr->T("Frame Rate Control"))); static const char *frameSkip[] = { "Off", "1", "2", "3", "4", "5", "6", "7", "8" }; graphicsSettings->Add(new PopupMultiChoice(&g_Config.iFrameSkip, gr->T("Frame Skipping"), frameSkip, 0, ARRAY_SIZE(frameSkip), gr->GetName(), screenManager())); @@ -289,7 +297,7 @@ void GameSettingsScreen::CreateViews() { graphicsSettings->Add(new ItemHeader(gr->T("Features"))); // Hide postprocess option on unsupported backends to avoid confusion. if (GetGPUBackend() != GPUBackend::DIRECT3D9) { - I18NCategory *ps = GetI18NCategory("PostShaders"); + auto ps = GetI18NCategory("PostShaders"); postProcChoice_ = graphicsSettings->Add(new ChoiceWithValueDisplay(&g_Config.sPostShaderName, gr->T("Postprocessing Shader"), &PostShaderTranslateName)); postProcChoice_->OnClick.Handle(this, &GameSettingsScreen::OnPostProcShader); postProcEnable_ = !g_Config.bSoftwareRendering && (g_Config.iRenderingMode != FB_NON_BUFFERED_MODE); @@ -812,13 +820,12 @@ void GameSettingsScreen::CreateViews() { SavePathInOtherChoice = systemSettings->Add(new CheckBox(&otherinstalled_, sy->T("Save path in installed.txt", "Save path in installed.txt"))); SavePathInOtherChoice->SetEnabled(false); SavePathInOtherChoice->OnClick.Handle(this, &GameSettingsScreen::OnSavePathOther); - wchar_t myDocumentsPath[MAX_PATH]; - const HRESULT result = SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, myDocumentsPath); + const bool myDocsExists = W32Util::UserDocumentsPath().size() != 0; const std::string PPSSPPpath = File::GetExeDirectory(); const std::string installedFile = PPSSPPpath + "installed.txt"; installed_ = File::Exists(installedFile); otherinstalled_ = false; - if (!installed_ && result == S_OK) { + if (!installed_ && myDocsExists) { if (File::CreateEmptyFile(PPSSPPpath + "installedTEMP.txt")) { // Disable the setting whether cannot create & delete file if (!(File::Delete(PPSSPPpath + "installedTEMP.txt"))) @@ -828,7 +835,7 @@ void GameSettingsScreen::CreateViews() { } else SavePathInMyDocumentChoice->SetEnabled(false); } else { - if (installed_ && (result == S_OK)) { + if (installed_ && myDocsExists) { #ifdef _MSC_VER std::ifstream inputFile(ConvertUTF8ToWString(installedFile)); #else @@ -848,8 +855,9 @@ void GameSettingsScreen::CreateViews() { } } inputFile.close(); - } else if (result != S_OK) + } else if (!myDocsExists) { SavePathInMyDocumentChoice->SetEnabled(false); + } } #endif @@ -941,7 +949,7 @@ void RecreateActivity() { System_SendMessage("recreate", ""); ILOG("Got back from recreate"); } else { - I18NCategory *gr = GetI18NCategory("Graphics"); + auto gr = GetI18NCategory("Graphics"); System_SendMessage("toast", gr->T("Must Restart", "You must restart PPSSPP for this change to take effect")); } } @@ -988,7 +996,7 @@ UI::EventReturn GameSettingsScreen::OnJitAffectingSetting(UI::EventParams &e) { #if PPSSPP_PLATFORM(ANDROID) UI::EventReturn GameSettingsScreen::OnChangeMemStickDir(UI::EventParams &e) { - I18NCategory *sy = GetI18NCategory("System"); + auto sy = GetI18NCategory("System"); System_SendMessage("inputbox", (std::string(sy->T("Memory Stick Folder")) + ":" + g_Config.memStickDirectory).c_str()); return UI::EVENT_DONE; } @@ -1003,26 +1011,20 @@ UI::EventReturn GameSettingsScreen::OnSavePathMydoc(UI::EventParams &e) { File::Delete(PPSSPPpath + "installed.txt"); File::CreateEmptyFile(PPSSPPpath + "installed.txt"); otherinstalled_ = false; - wchar_t myDocumentsPath[MAX_PATH]; - const HRESULT result = SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, myDocumentsPath); - const std::string myDocsPath = ConvertWStringToUTF8(myDocumentsPath) + "/PPSSPP/"; + const std::string myDocsPath = W32Util::UserDocumentsPath() + "/PPSSPP/"; g_Config.memStickDirectory = myDocsPath; - } - else if (installed_) { + } else if (installed_) { File::Delete(PPSSPPpath + "installed.txt"); installed_ = false; g_Config.memStickDirectory = PPSSPPpath + "memstick/"; - } - else { + } else { std::ofstream myfile; myfile.open(PPSSPPpath + "installed.txt"); if (myfile.is_open()){ myfile.close(); } - wchar_t myDocumentsPath[MAX_PATH]; - const HRESULT result = SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, myDocumentsPath); - const std::string myDocsPath = ConvertWStringToUTF8(myDocumentsPath) + "/PPSSPP/"; + const std::string myDocsPath = W32Util::UserDocumentsPath() + "/PPSSPP/"; g_Config.memStickDirectory = myDocsPath; installed_ = true; } @@ -1032,7 +1034,7 @@ UI::EventReturn GameSettingsScreen::OnSavePathMydoc(UI::EventParams &e) { UI::EventReturn GameSettingsScreen::OnSavePathOther(UI::EventParams &e) { const std::string PPSSPPpath = File::GetExeDirectory(); if (otherinstalled_) { - I18NCategory *di = GetI18NCategory("Dialog"); + auto di = GetI18NCategory("Dialog"); std::string folder = W32Util::BrowseForFolder(MainWindow::GetHWND(), di->T("Choose PPSSPP save folder")); if (folder.size()) { g_Config.memStickDirectory = folder; @@ -1162,8 +1164,8 @@ void GameSettingsScreen::onFinish(DialogResult result) { void GameSettingsScreen::sendMessage(const char *message, const char *value) { UIDialogScreenWithGameBackground::sendMessage(message, value); - I18NCategory *sy = GetI18NCategory("System"); - I18NCategory *di = GetI18NCategory("Dialog"); + auto sy = GetI18NCategory("System"); + auto di = GetI18NCategory("Dialog"); if (!strcmp(message, "inputbox_completed")) { std::vector inputboxValue; @@ -1198,7 +1200,7 @@ void GameSettingsScreen::sendMessage(const char *message, const char *value) { #if PPSSPP_PLATFORM(ANDROID) void GameSettingsScreen::CallbackMemstickFolder(bool yes) { - I18NCategory *sy = GetI18NCategory("System"); + auto sy = GetI18NCategory("System"); if (yes) { std::string memstickDirFile = g_Config.internalDataDirectory + "/memstick_dir.txt"; @@ -1254,7 +1256,7 @@ void GameSettingsScreen::CallbackRenderingDevice(bool yes) { } UI::EventReturn GameSettingsScreen::OnRenderingBackend(UI::EventParams &e) { - I18NCategory *di = GetI18NCategory("Dialog"); + auto di = GetI18NCategory("Dialog"); // It only makes sense to show the restart prompt if the backend was actually changed. if (g_Config.iGPUBackend != (int)GetGPUBackend()) { @@ -1265,7 +1267,7 @@ UI::EventReturn GameSettingsScreen::OnRenderingBackend(UI::EventParams &e) { } UI::EventReturn GameSettingsScreen::OnRenderingDevice(UI::EventParams &e) { - I18NCategory *di = GetI18NCategory("Dialog"); + auto di = GetI18NCategory("Dialog"); // It only makes sense to show the restart prompt if the device was actually changed. std::string *deviceNameSetting = GPUDeviceNameSetting(); @@ -1276,8 +1278,13 @@ UI::EventReturn GameSettingsScreen::OnRenderingDevice(UI::EventParams &e) { return UI::EVENT_DONE; } +UI::EventReturn GameSettingsScreen::OnCameraDeviceChange(UI::EventParams& e) { + Camera::onCameraDeviceChange(); + return UI::EVENT_DONE; +} + UI::EventReturn GameSettingsScreen::OnAudioDevice(UI::EventParams &e) { - I18NCategory *a = GetI18NCategory("Audio"); + auto a = GetI18NCategory("Audio"); if (g_Config.sAudioDevice == a->T("Auto")) { g_Config.sAudioDevice.clear(); } @@ -1303,7 +1310,7 @@ UI::EventReturn GameSettingsScreen::OnChangeNickname(UI::EventParams &e) { } UI::EventReturn GameSettingsScreen::OnChangeproAdhocServerAddress(UI::EventParams &e) { - I18NCategory *sy = GetI18NCategory("System"); + auto sy = GetI18NCategory("System"); #if defined(__ANDROID__) System_SendMessage("inputbox", ("IP:" + g_Config.proAdhocServer).c_str()); @@ -1326,7 +1333,7 @@ UI::EventReturn GameSettingsScreen::OnComboKey(UI::EventParams &e) { } UI::EventReturn GameSettingsScreen::OnLanguage(UI::EventParams &e) { - I18NCategory *dev = GetI18NCategory("Developer"); + auto dev = GetI18NCategory("Developer"); auto langScreen = new NewLanguageScreen(dev->T("Language")); langScreen->OnChoice.Handle(this, &GameSettingsScreen::OnLanguageChange); if (e.v) @@ -1345,7 +1352,7 @@ UI::EventReturn GameSettingsScreen::OnLanguageChange(UI::EventParams &e) { } UI::EventReturn GameSettingsScreen::OnPostProcShader(UI::EventParams &e) { - I18NCategory *gr = GetI18NCategory("Graphics"); + auto gr = GetI18NCategory("Graphics"); auto procScreen = new PostProcScreen(gr->T("Postprocessing Shader")); procScreen->OnChoice.Handle(this, &GameSettingsScreen::OnPostProcShaderChange); if (e.v) @@ -1414,11 +1421,11 @@ void DeveloperToolsScreen::CreateViews() { settingsScroll->SetTag("DevToolsSettings"); root_->Add(settingsScroll); - I18NCategory *di = GetI18NCategory("Dialog"); - I18NCategory *dev = GetI18NCategory("Developer"); - I18NCategory *gr = GetI18NCategory("Graphics"); - I18NCategory *a = GetI18NCategory("Audio"); - I18NCategory *sy = GetI18NCategory("System"); + auto di = GetI18NCategory("Dialog"); + auto dev = GetI18NCategory("Developer"); + auto gr = GetI18NCategory("Graphics"); + auto a = GetI18NCategory("Audio"); + auto sy = GetI18NCategory("System"); AddStandardBack(root_); @@ -1491,9 +1498,10 @@ void OtherSettingsScreen::CreateViews() { settingsScroll->SetTag("OtherSettings"); root_->Add(settingsScroll); - I18NCategory *gr = GetI18NCategory("Graphics"); - I18NCategory *sy = GetI18NCategory("System"); - I18NCategory *n = GetI18NCategory("Networking"); + auto gr = GetI18NCategory("Graphics"); + auto sy = GetI18NCategory("System"); + auto n = GetI18NCategory("Networking"); + auto dev = GetI18NCategory("Developer"); AddStandardBack(root_); @@ -1508,6 +1516,7 @@ void OtherSettingsScreen::CreateViews() { list->Add(new CheckBox(&g_Config.bFrameSkipUnthrottle, gr->T("Frameskip unthrottle(good for CPU benchmark)"))); list->Add(new CheckBox(&g_Config.bShowFrameProfiler, gr->T("Display frame profiler(heavy!)"))); list->Add(new CheckBox(&g_Config.bSimpleFrameStats, gr->T("Display simple frame stats(heavy!)"))); + list->Add(new CheckBox(&g_Config.bDrawFrameGraph, dev->T("Draw Frametimes Graph"))); list->Add(new CheckBox(&g_Config.bFullscreenOnDoubleclick, gr->T("Doubleclick fullscreen(only when no mouse or touch control is used)"))); list->Add(new CheckBox(&g_Config.bHideSlowWarnings, gr->T("Hide performance warnings"))); list->Add(new CheckBox(&g_Config.bSavestateScreenshotResLimit, gr->T("Limit resolution of savestates screenshots"))); @@ -1558,8 +1567,8 @@ void GameSettingsScreen::CallbackRestoreDefaults(bool yes) { } UI::EventReturn GameSettingsScreen::OnRestoreDefaultSettings(UI::EventParams &e) { - I18NCategory *dev = GetI18NCategory("Developer"); - I18NCategory *di = GetI18NCategory("Dialog"); + auto dev = GetI18NCategory("Developer"); + auto di = GetI18NCategory("Dialog"); if (g_Config.bGameSpecific) { screenManager()->push( @@ -1651,9 +1660,9 @@ void DeveloperToolsScreen::update() { void HostnameSelectScreen::CreatePopupContents(UI::ViewGroup *parent) { using namespace UI; - I18NCategory *sy = GetI18NCategory("System"); - I18NCategory *di = GetI18NCategory("Dialog"); - I18NCategory *n = GetI18NCategory("Networking"); + auto sy = GetI18NCategory("System"); + auto di = GetI18NCategory("Dialog"); + auto n = GetI18NCategory("Networking"); LinearLayout *valueRow = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT, Margins(0, 0, 0, 10))); diff --git a/UI/GameSettingsScreen.h b/UI/GameSettingsScreen.h index 5f628cda62..b3ceb36256 100644 --- a/UI/GameSettingsScreen.h +++ b/UI/GameSettingsScreen.h @@ -98,6 +98,7 @@ private: UI::EventReturn OnRenderingMode(UI::EventParams &e); UI::EventReturn OnRenderingBackend(UI::EventParams &e); UI::EventReturn OnRenderingDevice(UI::EventParams &e); + UI::EventReturn OnCameraDeviceChange(UI::EventParams& e); UI::EventReturn OnAudioDevice(UI::EventParams &e); UI::EventReturn OnJitAffectingSetting(UI::EventParams &e); #if PPSSPP_PLATFORM(ANDROID) diff --git a/UI/GamepadEmu.cpp b/UI/GamepadEmu.cpp index 9a4fc1ceea..126cfa28c6 100644 --- a/UI/GamepadEmu.cpp +++ b/UI/GamepadEmu.cpp @@ -157,6 +157,19 @@ void FPSLimitButton::Touch(const TouchInput &input) { } } +void RapidFireButton::Touch(const TouchInput &input) { + bool lastDown = pointerDownMask_ != 0; + MultiTouchButton::Touch(input); + bool down = pointerDownMask_ != 0; + if (down && !lastDown) { + __CtrlSetRapidFire(!__CtrlGetRapidFire()); + } +} + +bool RapidFireButton::IsDown() { + return __CtrlGetRapidFire(); +} + bool FPSLimitButton::IsDown() { return PSP_CoreParameter().fpsLimit == limit_; } @@ -510,6 +523,7 @@ void InitPadLayout(float xres, float yres, float globalScale) { initTouchPos(g_Config.touchSpeed1Key, unthrottle_key_X, unthrottle_key_Y - 60 * scale); initTouchPos(g_Config.touchSpeed2Key, unthrottle_key_X + bottom_key_spacing * scale, unthrottle_key_Y - 60 * scale); + initTouchPos(g_Config.touchRapidFireKey, unthrottle_key_X + 2*bottom_key_spacing * scale, unthrottle_key_Y - 60 * scale); // L and R------------------------------------------------------------ // Put them above the analog stick / above the buttons to the right. @@ -633,6 +647,11 @@ UI::ViewGroup *CreatePadLayout(float xres, float yres, bool *pause) { }); } + if (g_Config.touchRapidFireKey.show) { + auto rapidFire = root->Add(new RapidFireButton(rectImage, I_RECT, I_ARROW, g_Config.touchRapidFireKey.scale, buttonLayoutParams(g_Config.touchRapidFireKey))); + rapidFire->SetAngle(90.0f, 180.0f); + } + FPSLimitButton *speed1 = addFPSLimitButton(FPSLimit::CUSTOM1, rectImage, I_RECT, I_ARROW, g_Config.touchSpeed1Key); if (speed1) speed1->SetAngle(170.0f, 180.0f); diff --git a/UI/GamepadEmu.h b/UI/GamepadEmu.h index ec52ac575b..70ad2fd56b 100644 --- a/UI/GamepadEmu.h +++ b/UI/GamepadEmu.h @@ -97,6 +97,15 @@ private: FPSLimit limit_; }; +class RapidFireButton : public MultiTouchButton { +public: + RapidFireButton(int bgImg, int bgDownImg, int img, float scale, UI::LayoutParams *layoutParams) + : MultiTouchButton(bgImg, bgDownImg, img, scale, layoutParams) { + } + void Touch(const TouchInput &input) override; + bool IsDown() override; +}; + class PSPButton : public MultiTouchButton { public: PSPButton(int pspButtonBit, int bgImg, int bgDownImg, int img, float scale, UI::LayoutParams *layoutParams) diff --git a/UI/InstallZipScreen.cpp b/UI/InstallZipScreen.cpp index 1af1e85dc7..a815e01b7a 100644 --- a/UI/InstallZipScreen.cpp +++ b/UI/InstallZipScreen.cpp @@ -34,8 +34,8 @@ void InstallZipScreen::CreateViews() { FileInfo fileInfo; bool success = getFileInfo(zipPath_.c_str(), &fileInfo); - I18NCategory *di = GetI18NCategory("Dialog"); - I18NCategory *iz = GetI18NCategory("InstallZip"); + auto di = GetI18NCategory("Dialog"); + auto iz = GetI18NCategory("InstallZip"); Margins actionMenuMargins(0, 100, 15, 0); @@ -106,7 +106,7 @@ UI::EventReturn InstallZipScreen::OnInstall(UI::EventParams ¶ms) { } void InstallZipScreen::update() { - I18NCategory *iz = GetI18NCategory("InstallZip"); + auto iz = GetI18NCategory("InstallZip"); using namespace UI; if (g_GameManager.GetState() != GameManagerState::IDLE) { diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index e63c50b271..03d9a6827b 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -96,8 +96,14 @@ static bool IsTempPath(const std::string &str) { // Normalize slashes. item = ReplaceAll(str, "\\", "/"); - wchar_t tempPath[MAX_PATH]; - GetTempPath(MAX_PATH, tempPath); + std::wstring tempPath(MAX_PATH, '\0'); + size_t sz = GetTempPath((DWORD)tempPath.size(), &tempPath[0]); + if (sz >= tempPath.size()) { + tempPath.resize(sz); + sz = GetTempPath((DWORD)tempPath.size(), &tempPath[0]); + } + // Need to resize off the null terminator either way. + tempPath.resize(sz); if (testPath(ConvertWStringToUTF8(tempPath))) return true; #endif @@ -517,7 +523,7 @@ void GameBrowser::Refresh() { Clear(); Add(new Spacer(1.0f)); - I18NCategory *mm = GetI18NCategory("MainMenu"); + auto mm = GetI18NCategory("MainMenu"); // No topbar with SimpleUI if (!g_Config.bSimpleUI) { //(DisplayTopBar()) { // No topbar on recent screen @@ -771,7 +777,7 @@ void MainScreen::CreateViews() { bool vertical = UseVerticalLayout(); - I18NCategory *mm = GetI18NCategory("MainMenu"); + auto mm = GetI18NCategory("MainMenu"); Margins actionMenuMargins(0, 10, 10, 0); @@ -944,7 +950,7 @@ void MainScreen::CreateViews() { root_->SetDefaultFocusView(tabHolder_); } - I18NCategory *u = GetI18NCategory("Upgrade"); + auto u = GetI18NCategory("Upgrade"); upgradeBar_ = 0; if (!g_Config.upgradeMessage.empty()) { @@ -1245,8 +1251,8 @@ void MainScreen::dialogFinished(const Screen *dialog, DialogResult result) { void UmdReplaceScreen::CreateViews() { using namespace UI; Margins actionMenuMargins(0, 100, 15, 0); - I18NCategory *mm = GetI18NCategory("MainMenu"); - I18NCategory *di = GetI18NCategory("Dialog"); + auto mm = GetI18NCategory("MainMenu"); + auto di = GetI18NCategory("Dialog"); TabHolder *leftColumn = new TabHolder(ORIENT_HORIZONTAL, 64, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 1.0)); leftColumn->SetTag("UmdReplace"); diff --git a/UI/MiscScreens.cpp b/UI/MiscScreens.cpp index eccceecd0f..35a2a6213e 100644 --- a/UI/MiscScreens.cpp +++ b/UI/MiscScreens.cpp @@ -183,7 +183,7 @@ void HandleCommonMessages(const char *message, const char *value, ScreenManager UpdateUIState(UISTATE_MENU); manager->push(new GameSettingsScreen("")); } else if (!strcmp(message, "language screen") && isActiveScreen) { - I18NCategory *dev = GetI18NCategory("Developer"); + auto dev = GetI18NCategory("Developer"); auto langScreen = new NewLanguageScreen(dev->T("Language")); langScreen->OnChoice.Add([](UI::EventParams &) { NativeMessageReceived("recreateviews", ""); @@ -247,7 +247,7 @@ void UIDialogScreenWithBackground::DrawBackground(UIContext &dc) { void UIDialogScreenWithBackground::AddStandardBack(UI::ViewGroup *parent) { using namespace UI; - I18NCategory *di = GetI18NCategory("Dialog"); + auto di = GetI18NCategory("Dialog"); parent->Add(new Choice(di->T("Back"), "", false, new AnchorLayoutParams(150, 64, 10, NONE, NONE, 10)))->OnClick.Handle(this, &UIScreen::OnBack); } @@ -257,7 +257,7 @@ void UIDialogScreenWithBackground::sendMessage(const char *message, const char * PromptScreen::PromptScreen(std::string message, std::string yesButtonText, std::string noButtonText, std::function callback) : message_(message), callback_(callback) { - I18NCategory *di = GetI18NCategory("Dialog"); + auto di = GetI18NCategory("Dialog"); yesButtonText_ = di->T(yesButtonText.c_str()); noButtonText_ = di->T(noButtonText.c_str()); } @@ -303,7 +303,7 @@ void PromptScreen::TriggerFinish(DialogResult result) { } PostProcScreen::PostProcScreen(const std::string &title) : ListPopupScreen(title) { - I18NCategory *ps = GetI18NCategory("PostShaders"); + auto ps = GetI18NCategory("PostShaders"); ReloadAllPostShaderInfo(); shaders_ = GetAllPostShaderInfo(); std::vector items; @@ -487,8 +487,8 @@ void LogoScreen::render() { ::DrawBackground(dc, alpha); - I18NCategory *cr = GetI18NCategory("PSPCredits"); - I18NCategory *gr = GetI18NCategory("Graphics"); + auto cr = GetI18NCategory("PSPCredits"); + auto gr = GetI18NCategory("Graphics"); char temp[256]; // Manually formatting UTF-8 is fun. \xXX doesn't work everywhere. snprintf(temp, sizeof(temp), "%s Henrik Rydg%c%crd", cr->T("created", "Created by"), 0xC3, 0xA5); @@ -515,8 +515,8 @@ void LogoScreen::render() { void CreditsScreen::CreateViews() { using namespace UI; - I18NCategory *di = GetI18NCategory("Dialog"); - I18NCategory *cr = GetI18NCategory("PSPCredits"); + auto di = GetI18NCategory("Dialog"); + auto cr = GetI18NCategory("PSPCredits"); root_ = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT)); Button *back = root_->Add(new Button(di->T("Back"), new AnchorLayoutParams(260, 64, NONE, NONE, 10, 10, false))); @@ -567,7 +567,7 @@ UI::EventReturn CreditsScreen::OnForums(UI::EventParams &e) { } UI::EventReturn CreditsScreen::OnShare(UI::EventParams &e) { - I18NCategory *cr = GetI18NCategory("PSPCredits"); + auto cr = GetI18NCategory("PSPCredits"); System_SendMessage("sharetext", cr->T("CheckOutPPSSPP", "Check out PPSSPP, the awesome PSP emulator: http://www.ppsspp.org/")); return UI::EVENT_DONE; } @@ -586,7 +586,7 @@ void CreditsScreen::update() { void CreditsScreen::render() { UIScreen::render(); - I18NCategory *cr = GetI18NCategory("PSPCredits"); + auto cr = GetI18NCategory("PSPCredits"); std::string specialthanksMaxim = "Maxim "; specialthanksMaxim += cr->T("specialthanksMaxim", "for his amazing Atrac3+ decoder work"); diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 29882fd81c..a4006ada88 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -39,6 +39,10 @@ #include "Windows/MainWindow.h" #endif +#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP) +#include "Windows/CaptureDevice.h" +#endif + #include "base/display.h" #include "base/timeutil.h" #include "base/logging.h" @@ -67,6 +71,7 @@ #include "Common/LogManager.h" #include "Common/MemArena.h" #include "Common/GraphicsContext.h" +#include "Common/OSVersion.h" #include "Core/Config.h" #include "Core/ConfigValues.h" #include "Core/Core.h" @@ -656,7 +661,7 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch } - I18NCategory *des = GetI18NCategory("DesktopUI"); + auto des = GetI18NCategory("DesktopUI"); // Note to translators: do not translate this/add this to PPSSPP-lang's files. // It's intended to be custom for every user. // Only add it to your own personal copies of PPSSPP. @@ -855,6 +860,13 @@ bool NativeInitGraphics(GraphicsContext *graphicsContext) { #endif #endif +#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP) + if (IsWin7OrHigher()) { + winCamera = new WindowsCaptureDevice(CAPTUREDEVIDE_TYPE::VIDEO); + winCamera->sendMessage({ CAPTUREDEVIDE_COMMAND::INITIALIZE, nullptr }); + } +#endif + g_gameInfoCache = new GameInfoCache(); if (gpu) @@ -879,6 +891,15 @@ void NativeShutdownGraphics() { winAudioBackend = nullptr; #endif +#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP) + if (winCamera) { + winCamera->sendMessage({ CAPTUREDEVIDE_COMMAND::SHUTDOWN, nullptr }); + while (!winCamera->isShutDown()) {};// Wait for shutting down. + delete winCamera; + winCamera = nullptr; + } +#endif + ShutdownWebServer(); UIBackgroundShutdown(); @@ -935,7 +956,7 @@ void TakeScreenshot() { if (success) { osm.Show(filename); } else { - I18NCategory *err = GetI18NCategory("Error"); + auto err = GetI18NCategory("Error"); osm.Show(err->T("Could not save screenshot file")); } } @@ -1084,7 +1105,7 @@ void HandleGlobalMessage(const std::string &msg, const std::string &value) { UIBackgroundInit(*uiContext); } if (msg == "savestate_displayslot") { - I18NCategory *sy = GetI18NCategory("System"); + auto sy = GetI18NCategory("System"); std::string msg = StringFromFormat("%s: %d", sy->T("Savestate Slot"), SaveState::GetCurrentSlot() + 1); // Show for the same duration as the preview. osm.Show(msg, 2.0f, 0xFFFFFF, -1, true, "savestate_slot"); @@ -1104,7 +1125,7 @@ void HandleGlobalMessage(const std::string &msg, const std::string &value) { } if (msg == "core_powerSaving") { if (value != "false") { - I18NCategory *sy = GetI18NCategory("System"); + auto sy = GetI18NCategory("System"); #ifdef __ANDROID__ osm.Show(sy->T("WARNING: Android battery save mode is on"), 2.0f, 0xFFFFFF, -1, true, "core_powerSaving"); #else @@ -1344,11 +1365,3 @@ void NativeShutdown() { // Previously we did exit() here on Android but that makes it hard to do things like restart on backend change. // I think we handle most globals correctly or correct-enough now. } - -void PushNewGpsData(float latitude, float longitude, float altitude, float speed, float bearing, long long time) { - GPS::setGpsData(latitude, longitude, altitude, speed, bearing, time); -} - -void PushCameraImage(long long length, unsigned char* image) { - Camera::pushCameraImage(length, image); -} diff --git a/UI/PauseScreen.cpp b/UI/PauseScreen.cpp index 2ad9e63a7d..a8a15c193b 100644 --- a/UI/PauseScreen.cpp +++ b/UI/PauseScreen.cpp @@ -16,6 +16,7 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include +#include #include "i18n/i18n.h" #include "gfx_es2/draw_buffer.h" #include "ui/view.h" @@ -155,7 +156,7 @@ void AsyncImageFileView::Draw(UIContext &dc) { class ScreenshotViewScreen : public PopupScreen { public: - ScreenshotViewScreen(std::string filename, std::string title, int slot, I18NCategory *i18n) + ScreenshotViewScreen(std::string filename, std::string title, int slot, std::shared_ptr i18n) : PopupScreen(title, i18n->T("Load State"), "Back"), filename_(filename), slot_(slot) {} // PopupScreen will translate Back on its own int GetSlot() const { @@ -232,7 +233,7 @@ SaveSlotView::SaveSlotView(const std::string &gameFilename, int slot, UI::Layout AsyncImageFileView *fv = Add(new AsyncImageFileView(screenshotFilename_, IS_DEFAULT, wq, new UI::LayoutParams(80 * 2, 45 * 2))); fv->SetOverlayText(StringFromFormat("%d", slot_ + 1)); - I18NCategory *pa = GetI18NCategory("Pause"); + auto pa = GetI18NCategory("Pause"); LinearLayout *buttons = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT)); buttons->SetSpacing(2.0); @@ -321,8 +322,8 @@ void GamePauseScreen::CreateViews() { using namespace UI; Margins scrollMargins(0, 20, 0, 0); Margins actionMenuMargins(0, 20, 15, 0); - I18NCategory *gr = GetI18NCategory("Graphics"); - I18NCategory *pa = GetI18NCategory("Pause"); + auto gr = GetI18NCategory("Graphics"); + auto pa = GetI18NCategory("Pause"); root_ = new LinearLayout(ORIENT_HORIZONTAL); @@ -377,12 +378,12 @@ void GamePauseScreen::CreateViews() { // TODO, also might be nice to show overall compat rating here? // Based on their platform or even cpu/gpu/config. Would add an API for it. if (Reporting::IsSupported() && g_paramSFO.GetValueString("DISC_ID").size()) { - I18NCategory *rp = GetI18NCategory("Reporting"); + auto rp = GetI18NCategory("Reporting"); rightColumnItems->Add(new Choice(rp->T("ReportButton", "Report Feedback")))->OnClick.Handle(this, &GamePauseScreen::OnReportFeedback); } rightColumnItems->Add(new Spacer(25.0)); if (g_Config.bPauseMenuExitsEmulator) { - I18NCategory *mm = GetI18NCategory("MainMenu"); + auto mm = GetI18NCategory("MainMenu"); rightColumnItems->Add(new Choice(mm->T("Exit")))->OnClick.Handle(this, &GamePauseScreen::OnExitToMenu); } else { rightColumnItems->Add(new Choice(pa->T("Exit to menu")))->OnClick.Handle(this, &GamePauseScreen::OnExitToMenu); @@ -421,7 +422,7 @@ UI::EventReturn GamePauseScreen::OnScreenshotClicked(UI::EventParams &e) { if (SaveState::HasSaveInSlot(gamePath_, slot)) { std::string fn = v->GetScreenshotFilename(); std::string title = v->GetScreenshotTitle(); - I18NCategory *pa = GetI18NCategory("Pause"); + auto pa = GetI18NCategory("Pause"); Screen *screen = new ScreenshotViewScreen(fn, title, v->GetSlot(), pa); screenManager()->push(screen); } @@ -487,8 +488,8 @@ UI::EventReturn GamePauseScreen::OnCreateConfig(UI::EventParams &e) UI::EventReturn GamePauseScreen::OnDeleteConfig(UI::EventParams &e) { - I18NCategory *di = GetI18NCategory("Dialog"); - I18NCategory *ga = GetI18NCategory("Game"); + auto di = GetI18NCategory("Dialog"); + auto ga = GetI18NCategory("Game"); screenManager()->push( new PromptScreen(di->T("DeleteConfirmGameConfig", "Do you really want to delete the settings for this game?"), ga->T("ConfirmDelete"), di->T("Cancel"), std::bind(&GamePauseScreen::CallbackDeleteConfig, this, std::placeholders::_1))); diff --git a/UI/RemoteISOScreen.cpp b/UI/RemoteISOScreen.cpp index 2b34cfc440..1bc941570e 100644 --- a/UI/RemoteISOScreen.cpp +++ b/UI/RemoteISOScreen.cpp @@ -181,8 +181,8 @@ void RemoteISOScreen::update() { } void RemoteISOScreen::CreateViews() { - I18NCategory *di = GetI18NCategory("Dialog"); - I18NCategory *ri = GetI18NCategory("RemoteISO"); + auto di = GetI18NCategory("Dialog"); + auto ri = GetI18NCategory("RemoteISO"); Margins actionMenuMargins(0, 20, 15, 0); Margins contentMargins(0, 20, 5, 5); @@ -276,8 +276,8 @@ RemoteISOConnectScreen::~RemoteISOConnectScreen() { } void RemoteISOConnectScreen::CreateViews() { - I18NCategory *di = GetI18NCategory("Dialog"); - I18NCategory *ri = GetI18NCategory("RemoteISO"); + auto di = GetI18NCategory("Dialog"); + auto ri = GetI18NCategory("RemoteISO"); Margins actionMenuMargins(0, 20, 15, 0); Margins contentMargins(0, 20, 5, 5); @@ -300,7 +300,7 @@ void RemoteISOConnectScreen::CreateViews() { } void RemoteISOConnectScreen::update() { - I18NCategory *ri = GetI18NCategory("RemoteISO"); + auto ri = GetI18NCategory("RemoteISO"); UIScreenWithBackground::update(); @@ -412,8 +412,8 @@ RemoteISOBrowseScreen::RemoteISOBrowseScreen(const std::string &url, const std:: void RemoteISOBrowseScreen::CreateViews() { bool vertical = UseVerticalLayout(); - I18NCategory *di = GetI18NCategory("Dialog"); - I18NCategory *ri = GetI18NCategory("RemoteISO"); + auto di = GetI18NCategory("Dialog"); + auto ri = GetI18NCategory("RemoteISO"); Margins actionMenuMargins(0, 10, 10, 0); @@ -478,7 +478,7 @@ void RemoteISOSettingsScreen::update() { } void RemoteISOSettingsScreen::CreateViews() { - I18NCategory *ri = GetI18NCategory("RemoteISO"); + auto ri = GetI18NCategory("RemoteISO"); ViewGroup *remoteisoSettingsScroll = new ScrollView(ORIENT_VERTICAL, new LayoutParams(FILL_PARENT, FILL_PARENT)); remoteisoSettingsScroll->SetTag("RemoteISOSettings"); diff --git a/UI/ReportScreen.cpp b/UI/ReportScreen.cpp index 43c50959ed..577fb40e57 100644 --- a/UI/ReportScreen.cpp +++ b/UI/ReportScreen.cpp @@ -66,7 +66,7 @@ RatingChoice::RatingChoice(const char *captionKey, int *value, LayoutParams *lay : LinearLayout(ORIENT_VERTICAL, layoutParams), value_(value) { SetSpacing(0.0f); - I18NCategory *rp = GetI18NCategory("Reporting"); + auto rp = GetI18NCategory("Reporting"); group_ = new LinearLayout(ORIENT_HORIZONTAL); Add(new TextView(rp->T(captionKey), FLAG_WRAP_TEXT, false))->SetShadow(true); Add(group_); @@ -98,7 +98,7 @@ RatingChoice *RatingChoice::SetEnabledPtr(bool *ptr) { } void RatingChoice::SetupChoices() { - I18NCategory *rp = GetI18NCategory("Reporting"); + auto rp = GetI18NCategory("Reporting"); AddChoice(0, rp->T("Bad")); AddChoice(1, rp->T("OK")); AddChoice(2, rp->T("Great")); @@ -146,7 +146,7 @@ CompatRatingChoice::CompatRatingChoice(const char *captionKey, int *value, Layou } void CompatRatingChoice::SetupChoices() { - I18NCategory *rp = GetI18NCategory("Reporting"); + auto rp = GetI18NCategory("Reporting"); group_->Clear(); AddChoice(0, rp->T("Perfect")); AddChoice(1, rp->T("Plays")); @@ -219,9 +219,9 @@ EventReturn ReportScreen::HandleReportingChange(EventParams &e) { } void ReportScreen::CreateViews() { - I18NCategory *rp = GetI18NCategory("Reporting"); - I18NCategory *di = GetI18NCategory("Dialog"); - I18NCategory *sy = GetI18NCategory("System"); + auto rp = GetI18NCategory("Reporting"); + auto di = GetI18NCategory("Dialog"); + auto sy = GetI18NCategory("System"); Margins actionMenuMargins(0, 20, 15, 0); Margins contentMargins(0, 20, 5, 5); @@ -301,7 +301,7 @@ void ReportScreen::UpdateSubmit() { } void ReportScreen::UpdateOverallDescription() { - I18NCategory *rp = GetI18NCategory("Reporting"); + auto rp = GetI18NCategory("Reporting"); const char *desc; uint32_t c = 0xFFFFFFFF; switch (overall_) { @@ -351,8 +351,8 @@ ReportFinishScreen::ReportFinishScreen(const std::string &gamePath, ReportingOve } void ReportFinishScreen::CreateViews() { - I18NCategory *rp = GetI18NCategory("Reporting"); - I18NCategory *di = GetI18NCategory("Dialog"); + auto rp = GetI18NCategory("Reporting"); + auto di = GetI18NCategory("Dialog"); Margins actionMenuMargins(0, 20, 15, 0); Margins contentMargins(0, 20, 5, 5); @@ -386,7 +386,7 @@ void ReportFinishScreen::CreateViews() { } void ReportFinishScreen::update() { - I18NCategory *rp = GetI18NCategory("Reporting"); + auto rp = GetI18NCategory("Reporting"); if (!setStatus_) { Reporting::ReportStatus status = Reporting::GetStatus(); @@ -412,7 +412,7 @@ void ReportFinishScreen::update() { } void ReportFinishScreen::ShowSuggestions() { - I18NCategory *rp = GetI18NCategory("Reporting"); + auto rp = GetI18NCategory("Reporting"); auto suggestions = Reporting::CompatibilitySuggestions(); if (score_ == ReportingOverallScore::PERFECT || score_ == ReportingOverallScore::PLAYABLE) { diff --git a/UI/SavedataScreen.cpp b/UI/SavedataScreen.cpp index 05e17353de..a89fc0d90e 100644 --- a/UI/SavedataScreen.cpp +++ b/UI/SavedataScreen.cpp @@ -80,7 +80,7 @@ public: LinearLayout *toprow = new LinearLayout(ORIENT_HORIZONTAL, new LayoutParams(FILL_PARENT, WRAP_CONTENT)); content->Add(toprow); - I18NCategory *sa = GetI18NCategory("Savedata"); + auto sa = GetI18NCategory("Savedata"); if (ginfo->fileType == IdentifiedFileType::PSP_SAVEDATA_DIRECTORY) { std::string savedata_detail = ginfo->paramSFO.GetValueString("SAVEDATA_DETAIL"); std::string savedata_title = ginfo->paramSFO.GetValueString("SAVEDATA_TITLE"); @@ -108,7 +108,7 @@ public: content->Add(new TextView(GetFileDateAsString(savePath_), 0, true, new LinearLayoutParams(Margins(10, 5))))->SetTextColor(textStyle.fgColor); } - I18NCategory *di = GetI18NCategory("Dialog"); + auto di = GetI18NCategory("Dialog"); LinearLayout *buttons = new LinearLayout(ORIENT_HORIZONTAL); buttons->Add(new Button(di->T("Back"), new LinearLayoutParams(1.0)))->OnClick.Handle(this, &UIScreen::OnBack); buttons->Add(new Button(di->T("Delete"), new LinearLayoutParams(1.0)))->OnClick.Handle(this, &SavedataPopupScreen::OnDeleteButtonClick); @@ -394,8 +394,8 @@ void SavedataBrowser::Refresh() { Clear(); Add(new Spacer(1.0f)); - I18NCategory *mm = GetI18NCategory("MainMenu"); - I18NCategory *sa = GetI18NCategory("Savedata"); + auto mm = GetI18NCategory("MainMenu"); + auto sa = GetI18NCategory("Savedata"); SortedLinearLayout *gl = new SortedLinearLayout(UI::ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); gl->SetSpacing(4.0f); @@ -458,8 +458,8 @@ SavedataScreen::~SavedataScreen() { void SavedataScreen::CreateViews() { using namespace UI; - I18NCategory *sa = GetI18NCategory("Savedata"); - I18NCategory *di = GetI18NCategory("Dialog"); + auto sa = GetI18NCategory("Savedata"); + auto di = GetI18NCategory("Dialog"); std::string savedata_dir = GetSysDirectory(DIRECTORY_SAVEDATA); std::string savestate_dir = GetSysDirectory(DIRECTORY_SAVESTATE); diff --git a/UI/Store.cpp b/UI/Store.cpp index c7b02c66fd..9b733a07ef 100644 --- a/UI/Store.cpp +++ b/UI/Store.cpp @@ -240,8 +240,8 @@ void ProductView::CreateViews() { Add(new TextView(entry_.name)); Add(new TextView(entry_.author)); - I18NCategory *st = GetI18NCategory("Store"); - I18NCategory *di = GetI18NCategory("Dialog"); + auto st = GetI18NCategory("Store"); + auto di = GetI18NCategory("Dialog"); wasInstalled_ = IsGameInstalled(); if (!wasInstalled_) { installButton_ = Add(new Button(st->T("Install"))); @@ -431,8 +431,8 @@ void StoreScreen::CreateViews() { root_ = new LinearLayout(ORIENT_VERTICAL); - I18NCategory *di = GetI18NCategory("Dialog"); - I18NCategory *st = GetI18NCategory("Store"); + auto di = GetI18NCategory("Dialog"); + auto st = GetI18NCategory("Store"); // Top bar LinearLayout *topBar = root_->Add(new LinearLayout(ORIENT_HORIZONTAL)); diff --git a/UI/TiltAnalogSettingsScreen.cpp b/UI/TiltAnalogSettingsScreen.cpp index 2e7d4add46..fd427dbf29 100644 --- a/UI/TiltAnalogSettingsScreen.cpp +++ b/UI/TiltAnalogSettingsScreen.cpp @@ -23,8 +23,8 @@ void TiltAnalogSettingsScreen::CreateViews() { using namespace UI; - I18NCategory *co = GetI18NCategory("Controls"); - I18NCategory *di = GetI18NCategory("Dialog"); + auto co = GetI18NCategory("Controls"); + auto di = GetI18NCategory("Dialog"); root_ = new ScrollView(ORIENT_VERTICAL); root_->SetTag("TiltAnalogSettings"); diff --git a/UI/TouchControlLayoutScreen.cpp b/UI/TouchControlLayoutScreen.cpp index 65ebb654cf..0e8f12618a 100644 --- a/UI/TouchControlLayoutScreen.cpp +++ b/UI/TouchControlLayoutScreen.cpp @@ -213,6 +213,34 @@ private: float &spacing_; }; +class SnapGrid : public UI::View { +public: + SnapGrid(int leftMargin, int rightMargin, int topMargin, int bottomMargin, u32 color) { + x1 = leftMargin; + x2 = rightMargin; + y1 = topMargin; + y2 = bottomMargin; + col = color; + } + + void Draw(UIContext &dc) override { + if (g_Config.bTouchSnapToGrid) { + dc.Flush(); + dc.BeginNoTex(); + for (int x = x1; x < x2; x += g_Config.iTouchSnapGridSize) + dc.Draw()->vLine(x, y1, y2, col); + for (int y = y1; y < y2; y += g_Config.iTouchSnapGridSize) + dc.Draw()->hLine(x1, y, x2, col); + dc.Flush(); + dc.Begin(); + } + } + +private: + int x1, x2, y1, y2; + u32 col; +}; + TouchControlLayoutScreen::TouchControlLayoutScreen() { pickedControl_ = 0; }; @@ -260,6 +288,11 @@ bool TouchControlLayoutScreen::touch(const TouchInput &touch) { float diffX = (touch.x - startX_); float diffY = -(touch.y - startY_); + // Snap to grid + if (g_Config.bTouchSnapToGrid) { + diffX -= (int)(touch.x - startX_) % (g_Config.iTouchSnapGridSize/2); + diffY += (int)(touch.y - startY_) % (g_Config.iTouchSnapGridSize/2); + } float movementScale = 0.02f; float newScale = startScale_ + diffY * movementScale; float newSpacing = startSpacing_ + diffX * movementScale; @@ -323,8 +356,8 @@ void TouchControlLayoutScreen::CreateViews() { using namespace UI; - I18NCategory *co = GetI18NCategory("Controls"); - I18NCategory *di = GetI18NCategory("Dialog"); + auto co = GetI18NCategory("Controls"); + auto di = GetI18NCategory("Dialog"); root_ = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT)); @@ -335,7 +368,7 @@ void TouchControlLayoutScreen::CreateViews() { // ->OnChange.Handle(this, &GameSettingsScreen::OnChangeControlScaling); CheckBox *snap = new CheckBox(&g_Config.bTouchSnapToGrid, di->T("Snap"), "", new AnchorLayoutParams(leftColumnWidth, WRAP_CONTENT, 10, NONE, NONE, 228)); - PopupSliderChoice *gridSize = new PopupSliderChoice(&g_Config.iTouchSnapGridSize, 1, 256, di->T("Grid"), screenManager(), "", new AnchorLayoutParams(leftColumnWidth, WRAP_CONTENT, 10, NONE, NONE, 158)); + PopupSliderChoice *gridSize = new PopupSliderChoice(&g_Config.iTouchSnapGridSize, 2, 256, di->T("Grid"), screenManager(), "", new AnchorLayoutParams(leftColumnWidth, WRAP_CONTENT, 10, NONE, NONE, 158)); gridSize->SetEnabledPtr(&g_Config.bTouchSnapToGrid); mode_ = new ChoiceStrip(ORIENT_VERTICAL, new AnchorLayoutParams(leftColumnWidth, WRAP_CONTENT, 10, NONE, NONE, 140 + 158 + 64 + 10)); @@ -362,7 +395,7 @@ void TouchControlLayoutScreen::CreateViews() { // serves no other purpose. AnchorLayout *controlsHolder = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT)); - I18NCategory *ms = GetI18NCategory("MainSettings"); + auto ms = GetI18NCategory("MainSettings"); //tabHolder->AddTab(ms->T("Controls"), controlsHolder); @@ -420,6 +453,12 @@ void TouchControlLayoutScreen::CreateViews() { controls_.push_back(speed2); } + if (g_Config.touchRapidFireKey.show) { + DragDropButton *rapidFire = new DragDropButton(g_Config.touchRapidFireKey, rectImage, I_ARROW); + rapidFire->SetAngle(90.0f, 180.0f); + controls_.push_back(rapidFire); + } + if (g_Config.touchLKey.show) { controls_.push_back(new DragDropButton(g_Config.touchLKey, shoulderImage, I_L)); } @@ -455,6 +494,8 @@ void TouchControlLayoutScreen::CreateViews() { for (size_t i = 0; i < controls_.size(); i++) { root_->Add(controls_[i]); } + + root_->Add(new SnapGrid(leftColumnWidth+10, bounds.w, 0, bounds.h, 0x3FFFFFFF)); } // return the control which was picked up by the touchEvent. If a control diff --git a/UI/TouchControlVisibilityScreen.cpp b/UI/TouchControlVisibilityScreen.cpp index 1c7f23af4e..1ba3972128 100644 --- a/UI/TouchControlVisibilityScreen.cpp +++ b/UI/TouchControlVisibilityScreen.cpp @@ -42,8 +42,8 @@ private: void TouchControlVisibilityScreen::CreateViews() { using namespace UI; - I18NCategory *di = GetI18NCategory("Dialog"); - I18NCategory *co = GetI18NCategory("Controls"); + auto di = GetI18NCategory("Dialog"); + auto co = GetI18NCategory("Controls"); root_ = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT)); @@ -89,8 +89,9 @@ void TouchControlVisibilityScreen::CreateViews() { toggles_.push_back({ "Combo4", &g_Config.touchCombo4.show, I_5 }); toggles_.push_back({ "Alt speed 1", &g_Config.touchSpeed1Key.show, -1 }); toggles_.push_back({ "Alt speed 2", &g_Config.touchSpeed2Key.show, -1 }); + toggles_.push_back({ "Rapid Fire", &g_Config.touchRapidFireKey.show, -1 }); - I18NCategory *mc = GetI18NCategory("MappableControls"); + auto mc = GetI18NCategory("MappableControls"); for (auto toggle : toggles_) { LinearLayout *row = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); diff --git a/UI/UI.vcxproj b/UI/UI.vcxproj index 769c127261..ea06201cf5 100644 --- a/UI/UI.vcxproj +++ b/UI/UI.vcxproj @@ -207,7 +207,7 @@ Level3 _CRTDBG_MAP_ALLOC;USING_WIN_UI;_CRT_SECURE_NO_WARNINGS;_ARCH_32=1;WIN32;_DEBUG;_LIB;_UNICODE;UNICODE;%(PreprocessorDefinitions) - ../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/zlib;../ext/native/ext + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/zlib;../ext/native/ext true false false @@ -231,7 +231,7 @@ Level3 _CRTDBG_MAP_ALLOC;USING_WIN_UI;_CRT_SECURE_NO_WARNINGS;_ARCH_64=1;WIN32;_DEBUG;_LIB;_UNICODE;UNICODE;%(PreprocessorDefinitions) - ../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/zlib;../ext/native/ext + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/zlib;../ext/native/ext true false false @@ -281,7 +281,7 @@ Level3 _CRTDBG_MAP_ALLOC;USING_WIN_UI;_CRT_SECURE_NO_WARNINGS;_ARCH_32=1;WIN32;_DEBUG;_LIB;_UNICODE;UNICODE;%(PreprocessorDefinitions) - ../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/zlib;../ext/native/ext + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/zlib;../ext/native/ext true false false @@ -310,7 +310,7 @@ true true USING_WIN_UI;_CRT_SECURE_NO_WARNINGS;_ARCH_32=1;WIN32;NDEBUG;_LIB;_UNICODE;UNICODE;%(PreprocessorDefinitions) - ../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/zlib;../ext/native/ext + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/zlib;../ext/native/ext false Size true @@ -339,7 +339,7 @@ true true USING_WIN_UI;_CRT_SECURE_NO_WARNINGS;_ARCH_64=1;WIN32;NDEBUG;_LIB;_UNICODE;UNICODE;%(PreprocessorDefinitions) - ../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/zlib;../ext/native/ext + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/zlib;../ext/native/ext false true false @@ -401,7 +401,7 @@ true true USING_WIN_UI;_CRT_SECURE_NO_WARNINGS;_ARCH_32=1;WIN32;NDEBUG;_LIB;_UNICODE;UNICODE;%(PreprocessorDefinitions) - ../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/zlib;../ext/native/ext + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../common;..;../ext/native;../ext/glew;../ext/snappy;../ext/zlib;../ext/native/ext false true false diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj index a26bcbac2c..49703f02f3 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj +++ b/UWP/CoreUWP/CoreUWP.vcxproj @@ -509,6 +509,7 @@ + @@ -719,6 +720,7 @@ + @@ -945,4 +947,4 @@ - + \ No newline at end of file diff --git a/UWP/CoreUWP/CoreUWP.vcxproj.filters b/UWP/CoreUWP/CoreUWP.vcxproj.filters index fe872876ad..649a1c4405 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj.filters +++ b/UWP/CoreUWP/CoreUWP.vcxproj.filters @@ -703,6 +703,9 @@ MIPS\ARM64 + + HW + @@ -1297,5 +1300,8 @@ MIPS\ARM64 + + HW + - + \ No newline at end of file diff --git a/UWP/PPSSPP_UWPMain.cpp b/UWP/PPSSPP_UWPMain.cpp index 1f77adf425..35739b4dc8 100644 --- a/UWP/PPSSPP_UWPMain.cpp +++ b/UWP/PPSSPP_UWPMain.cpp @@ -96,7 +96,7 @@ PPSSPP_UWPMain::PPSSPP_UWPMain(App ^app, const std::shared_ptrGetSampleRate() : -1; - case SYSPROP_DISPLAY_REFRESH_RATE: - return 60000; case SYSPROP_DEVICE_TYPE: { auto ver = Windows::System::Profile::AnalyticsInfo::VersionInfo; @@ -375,6 +373,15 @@ int System_GetPropertyInt(SystemProperty prop) { } } +float System_GetPropertyFloat(SystemProperty prop) { + switch (prop) { + case SYSPROP_DISPLAY_REFRESH_RATE: + return 60.f; + default: + return -1; + } +} + bool VulkanMayBeAvailable() { return false; } @@ -477,7 +484,7 @@ bool System_InputBoxGetWString(const wchar_t *title, const std::wstring &default std::string GetCPUBrandString() { Platform::String^ cpu_id = nullptr; Platform::String^ cpu_name = nullptr; - + // GUID_DEVICE_PROCESSOR: {97FADB10-4E33-40AE-359C-8BEF029DBDD0} Platform::String^ if_filter = L"System.Devices.InterfaceClassGuid:=\"{97FADB10-4E33-40AE-359C-8BEF029DBDD0}\""; diff --git a/UWP/UWP.vcxproj b/UWP/UWP.vcxproj index f581533162..8581912823 100644 --- a/UWP/UWP.vcxproj +++ b/UWP/UWP.vcxproj @@ -188,7 +188,7 @@ True False Never - B57919568D3F60B8D25EC6856DE94173682C548C + b5e422213fb26042db3327f1102c4f9b86c27b1c False 1 OnApplicationRun diff --git a/Windows/BufferLock.h b/Windows/BufferLock.h new file mode 100644 index 0000000000..a19b8c965d --- /dev/null +++ b/Windows/BufferLock.h @@ -0,0 +1,123 @@ +////////////////////////////////////////////////////////////////////////// +// +// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +// PARTICULAR PURPOSE. +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +////////////////////////////////////////////////////////////////////////// + + + +#pragma once + + +//------------------------------------------------------------------- +// VideoBufferLock class +// +// Locks a video buffer that might or might not support IMF2DBuffer. +// +//------------------------------------------------------------------- + +class VideoBufferLock +{ +public: + VideoBufferLock(IMFMediaBuffer *pBuffer) : m_p2DBuffer(NULL), m_bLocked(FALSE) + { + m_pBuffer = pBuffer; + m_pBuffer->AddRef(); + + // Query for the 2-D buffer interface. OK if this fails. + (void)m_pBuffer->QueryInterface(IID_PPV_ARGS(&m_p2DBuffer)); + } + + ~VideoBufferLock() + { + UnlockBuffer(); + SafeRelease(&m_pBuffer); + SafeRelease(&m_p2DBuffer); + } + + //------------------------------------------------------------------- + // LockBuffer + // + // Locks the buffer. Returns a pointer to scan line 0 and returns the stride. + // + // The caller must provide the default stride as an input parameter, in case + // the buffer does not expose IMF2DBuffer. You can calculate the default stride + // from the media type. + //------------------------------------------------------------------- + + HRESULT LockBuffer( + LONG lDefaultStride, // Minimum stride (with no padding). + DWORD dwHeightInPixels, // Height of the image, in pixels. + BYTE **ppbScanLine0, // Receives a pointer to the start of scan line 0. + LONG *plStride // Receives the actual stride. + ) + { + HRESULT hr = S_OK; + + // Use the 2-D version if available. + if (m_p2DBuffer) + { + hr = m_p2DBuffer->Lock2D(ppbScanLine0, plStride); + } + else + { + // Use non-2D version. + BYTE *pData = NULL; + + hr = m_pBuffer->Lock(&pData, NULL, NULL); + if (SUCCEEDED(hr)) + { + *plStride = lDefaultStride; + if (lDefaultStride < 0) + { + // Bottom-up orientation. Return a pointer to the start of the + // last row *in memory* which is the top row of the image. + *ppbScanLine0 = pData + abs(lDefaultStride) * (dwHeightInPixels - 1); + } + else + { + // Top-down orientation. Return a pointer to the start of the + // buffer. + *ppbScanLine0 = pData; + } + } + } + + m_bLocked = (SUCCEEDED(hr)); + + return hr; + } + + //------------------------------------------------------------------- + // UnlockBuffer + // + // Unlocks the buffer. Called automatically by the destructor. + //------------------------------------------------------------------- + + void UnlockBuffer() + { + if (m_bLocked) + { + if (m_p2DBuffer) + { + (void)m_p2DBuffer->Unlock2D(); + } + else + { + (void)m_pBuffer->Unlock(); + } + m_bLocked = FALSE; + } + } + +private: + IMFMediaBuffer *m_pBuffer; + IMF2DBuffer *m_p2DBuffer; + + BOOL m_bLocked; +}; diff --git a/Windows/CaptureDevice.cpp b/Windows/CaptureDevice.cpp new file mode 100644 index 0000000000..d6c453c05d --- /dev/null +++ b/Windows/CaptureDevice.cpp @@ -0,0 +1,828 @@ +// Copyright (c) 2020- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include +#include "thread/threadutil.h" +#include "CaptureDevice.h" +#include "BufferLock.h" +#include "ext/jpge/jpge.h" +#include "CommonTypes.h" +#include "Core/HLE/sceUsbCam.h" +#include "Core/Config.h" + +bool isDeviceChanged = false; + +namespace MFAPI { + HINSTANCE Mflib; + HINSTANCE Mfplatlib; + HINSTANCE Mfreadwritelib; + + typedef HRESULT(WINAPI *MFEnumDeviceSourcesFunc)(IMFAttributes *, IMFActivate ***, UINT32 *); + typedef HRESULT(WINAPI *MFGetStrideForBitmapInfoHeaderFunc)(DWORD, DWORD, LONG *); + typedef HRESULT(WINAPI *MFCreateSourceReaderFromMediaSourceFunc)(IMFMediaSource *, IMFAttributes *, IMFSourceReader **); + typedef HRESULT(WINAPI *MFCopyImageFunc)(BYTE *, LONG, const BYTE *, LONG, DWORD, DWORD); + + MFEnumDeviceSourcesFunc EnumDeviceSources; + MFGetStrideForBitmapInfoHeaderFunc GetStrideForBitmapInfoHeader; + MFCreateSourceReaderFromMediaSourceFunc CreateSourceReaderFromMediaSource; + MFCopyImageFunc CopyImage; +} + +using namespace MFAPI; + +bool RegisterCMPTMFApis(){ + //For the compatibility,these funcs don't be supported on vista. + Mflib = LoadLibrary(L"Mf.dll"); + Mfplatlib = LoadLibrary(L"Mfplat.dll"); + Mfreadwritelib = LoadLibrary(L"Mfreadwrite.dll"); + if (!Mflib || !Mfplatlib || !Mfreadwritelib) + return false; + + EnumDeviceSources = (MFEnumDeviceSourcesFunc)GetProcAddress(Mflib, "MFEnumDeviceSources"); + GetStrideForBitmapInfoHeader = (MFGetStrideForBitmapInfoHeaderFunc)GetProcAddress(Mfplatlib, "MFGetStrideForBitmapInfoHeader"); + MFAPI::CopyImage = (MFCopyImageFunc)GetProcAddress(Mfplatlib, "MFCopyImage"); + CreateSourceReaderFromMediaSource = (MFCreateSourceReaderFromMediaSourceFunc)GetProcAddress(Mfreadwritelib, "MFCreateSourceReaderFromMediaSource"); + if (!EnumDeviceSources || !GetStrideForBitmapInfoHeader || !CreateSourceReaderFromMediaSource || !MFAPI::CopyImage) + return false; + + return true; +} + +bool unRegisterCMPTMFApis() { + if (Mflib) { + FreeLibrary(Mflib); + Mflib = nullptr; + } + + if (Mfplatlib) { + FreeLibrary(Mfplatlib); + Mfplatlib = nullptr; + } + + if (Mfreadwritelib) { + FreeLibrary(Mfreadwritelib); + Mfreadwritelib = nullptr; + } + + EnumDeviceSources = nullptr; + GetStrideForBitmapInfoHeader = nullptr; + CreateSourceReaderFromMediaSource = nullptr; + MFAPI::CopyImage = nullptr; + + return true; +} + +WindowsCaptureDevice *winCamera; + +// TODO: Add more formats, but need some tests. +VideoFormatTransform g_VideoFormats[] = +{ + { MFVideoFormat_RGB32, AV_PIX_FMT_RGBA }, + { MFVideoFormat_RGB24, AV_PIX_FMT_RGB24 }, + { MFVideoFormat_YUY2, AV_PIX_FMT_YUYV422 }, + { MFVideoFormat_NV12, AV_PIX_FMT_NV12 } +}; + +const int g_cVideoFormats = 4; + +MediaParam defaultVideoParam = { 640, 480, 0, MFVideoFormat_RGB24 }; +MediaParam defaultAudioParam = { 44100, 2, 0, MFAudioFormat_PCM }; + +HRESULT GetDefaultStride(IMFMediaType *pType, LONG *plStride); + +ReaderCallback::ReaderCallback(WindowsCaptureDevice *device): img_convert_ctx(nullptr){ + this->device = device; +} + +ReaderCallback::~ReaderCallback() { + sws_freeContext(img_convert_ctx); +} + +HRESULT ReaderCallback::QueryInterface(REFIID riid, void** ppv) +{ + static const QITAB qit[] = + { + QITABENT(ReaderCallback, IMFSourceReaderCallback), + { 0 }, + }; + return QISearch(this, qit, riid, ppv); +} + +HRESULT ReaderCallback::OnReadSample( + HRESULT hrStatus, + DWORD dwStreamIndex, + DWORD dwStreamFlags, + LONGLONG llTimestamp, + IMFSample *pSample) { + HRESULT hr = S_OK; + IMFMediaBuffer *pBuffer = nullptr; + LONG lStride = 0; + std::lock_guard lock(device->sdMutex); + if (device->isShutDown()) + return hr; + + if (FAILED(hrStatus)) + hr = hrStatus; + + if (SUCCEEDED(hr)) { + if (pSample) { + hr = pSample->GetBufferByIndex(0, &pBuffer); + } + } + if (SUCCEEDED(hr)) { + switch (device->type) { + case CAPTUREDEVIDE_TYPE::VIDEO: { + BYTE *pbScanline0 = nullptr; + VideoBufferLock *videoBuffer = nullptr; + int imgJpegSize = device->imgJpegSize; + unsigned char* invertedSrcImg = nullptr; + LONG srcPadding = 0; + + UINT32 srcW = device->deviceParam.width; + UINT32 srcH = device->deviceParam.height; + UINT32 dstW = device->targetMediaParam.width; + UINT32 dstH = device->targetMediaParam.height; + GUID srcMFVideoFormat = device->deviceParam.videoFormat; + + // pSample can be null, in this case ReadSample still should be called to request next frame. + if (pSample) { + videoBuffer = new VideoBufferLock(pBuffer); + hr = videoBuffer->LockBuffer(device->deviceParam.default_stride, device->deviceParam.height, &pbScanline0, &lStride); + + if (lStride > 0) + srcPadding = lStride - device->deviceParam.default_stride; + else + srcPadding = device->deviceParam.default_stride - lStride; + + if (SUCCEEDED(hr)) { + // Convert image to RGB24 + if (lStride > 0) { + imgConvert( + device->imageRGB, dstW, dstH, device->imgRGBLineSizes, + pbScanline0, srcW, srcH, srcMFVideoFormat, srcPadding); + } else { + // If stride < 0, the pointer to the first row of source image is the last row in memory,should invert it in memory. + invertedSrcImg = (unsigned char*)av_malloc(av_image_get_buffer_size(getAVVideoFormatbyMFVideoFormat(srcMFVideoFormat), srcW, srcH, 1)); + imgInvert(invertedSrcImg, pbScanline0, srcW, srcH, device->deviceParam.videoFormat, lStride); + // We alloc a inverted image with no padding, set padding to zero. + srcPadding = 0; + imgConvert( + device->imageRGB, dstW, dstH, device->imgRGBLineSizes, + invertedSrcImg, srcW, srcH, srcMFVideoFormat, srcPadding); + av_free(invertedSrcImg); + } + + // Compress image to jpeg from RGB24. + jpge::compress_image_to_jpeg_file_in_memory( + device->imageJpeg, imgJpegSize, + dstW, + dstH, + 3, + device->imageRGB); + } + Camera::pushCameraImage(imgJpegSize, device->imageJpeg); + } + // Request the next frame. + if (SUCCEEDED(hr)) { + hr = device->m_pReader->ReadSample( + (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, + 0, + nullptr, + nullptr, + nullptr, + nullptr + ); + } + + delete videoBuffer; + break; + } + case CAPTUREDEVIDE_TYPE::AUDIO: + // TODO: + break; + } + } + + SafeRelease(&pBuffer); + return hr; +} + +AVPixelFormat ReaderCallback::getAVVideoFormatbyMFVideoFormat(const GUID &MFVideoFormat) { + for (int i = 0; i < g_cVideoFormats; i++) { + if (MFVideoFormat == g_VideoFormats[i].MFVideoFormat) + return g_VideoFormats[i].AVVideoFormat; + } + return AV_PIX_FMT_RGB24; +} + +void ReaderCallback::imgConvert( + unsigned char *dst, unsigned int &dstW, unsigned int &dstH, int dstLineSizes[4], + unsigned char *src, const unsigned int &srcW, const unsigned int &srcH, const GUID &srcFormat, + const int &srcPadding) { + int srcLineSizes[4] = { 0, 0, 0, 0 }; + unsigned char *pSrc[4]; + unsigned char *pDst[4]; + + AVPixelFormat srcAvFormat = getAVVideoFormatbyMFVideoFormat(srcFormat); + + av_image_fill_linesizes(srcLineSizes, srcAvFormat, srcW); + + // Is this correct? + if (srcPadding != 0) { + for (int i = 0; i < 4; i++) { + if (srcLineSizes[i] != 0) + srcLineSizes[i] += srcPadding; + } + } + + av_image_fill_pointers(pSrc, srcAvFormat, srcH, src, srcLineSizes); + av_image_fill_pointers(pDst, AV_PIX_FMT_RGB24, dstH, dst, dstLineSizes); + + + + if (img_convert_ctx == nullptr) { + img_convert_ctx = sws_getContext( + srcW, + srcH, + srcAvFormat, + dstW, + dstH, + AV_PIX_FMT_RGB24, + SWS_BICUBIC, + nullptr, + nullptr, + nullptr + ); + } + + if (img_convert_ctx) { + sws_scale(img_convert_ctx, + (const uint8_t *const *)pSrc, + srcLineSizes, + 0, + srcH, + (uint8_t *const *)pDst, + dstLineSizes + ); + } +} + +void ReaderCallback::imgInvert(unsigned char *dst, unsigned char *src, const int &srcW, const int &srcH, const GUID &srcFormat, const int &srcStride) { + AVPixelFormat srcAvFormat = getAVVideoFormatbyMFVideoFormat(srcFormat); + int dstLineSizes[4] = { 0, 0, 0, 0 }; + + av_image_fill_linesizes(dstLineSizes, srcAvFormat, srcW); + + if(srcFormat == MFVideoFormat_RGB32) + imgInvertRGBA(dst, dstLineSizes[0], src, srcStride, srcH); + else if(srcFormat == MFVideoFormat_RGB24) + imgInvertRGB(dst, dstLineSizes[0], src, srcStride, srcH); + else if (srcFormat == MFVideoFormat_YUY2) + imgInvertYUY2(dst, dstLineSizes[0], src, srcStride, srcH); + else if (srcFormat == MFVideoFormat_NV12) + imgInvertNV12(dst, dstLineSizes[0], src, srcStride, srcH);; +} + +void ReaderCallback::imgInvertRGBA(unsigned char *dst, int &dstStride, unsigned char *src, const int &srcStride, const int &h) { + MFAPI::CopyImage(dst, dstStride, src, srcStride, dstStride, h); +} + +void ReaderCallback::imgInvertRGB(unsigned char *dst, int &dstStride, unsigned char *src, const int &srcStride, const int &h) { + for (int y = 0; y < h; y++) { + for (int srcx = dstStride - 1, dstx = 0; dstx < dstStride; srcx--, dstx++) { + dst[dstx] = src[srcx]; + } + dst += dstStride; + src += srcStride; + } +} + +void ReaderCallback::imgInvertYUY2(unsigned char *dst, int &dstStride, unsigned char *src, const int &srcStride, const int &h) { + for (int y = 0; y < h; y++) { + for (int srcx = dstStride - 1, dstx = 0; dstx < dstStride; srcx--, dstx++) { + dst[dstx] = src[srcx]; + } + dst += dstStride; + src += srcStride; + } +} + +void ReaderCallback::imgInvertNV12(unsigned char *dst, int &dstStride, unsigned char *src, const int &srcStride, const int &h) { + unsigned char *dstY = dst; + unsigned char *dstU = dst + dstStride * h; + unsigned char *srcY = src; + unsigned char *srcV = src + srcStride * h; + + unsigned char *srcY1 = srcY; + unsigned char *srcY2 = srcY1 + srcStride; + unsigned char *dstY1 = dstY; + unsigned char *dstY2 = dstY1 + dstStride; + + bool isodd = h % 2 != 0; + + for (int y = 0; y < (isodd ? h - 1 : h); y += 2) { + for (int srcx = dstStride - 1, dstx = 0; dstx < dstStride; srcx--, dstx++) { + dstY1[dstx] = srcY1[srcx]; + dstY2[dstx] = srcY2[srcx]; + dstU[dstx] = srcV[srcx]; + } + dstY += dstStride * 2; + srcY1 += srcStride * 2; + srcY2 += srcStride *2; + srcV += srcStride; + dstU += dstStride; + } +} + +WindowsCaptureDevice::WindowsCaptureDevice(CAPTUREDEVIDE_TYPE type) : + type(type), + m_pCallback(nullptr), + m_pSource(nullptr), + m_pReader(nullptr), + error(CAPTUREDEVIDE_ERROR_NO_ERROR), + errorMessage(""), + state(CAPTUREDEVIDE_STATE::UNINITIALIZED) { + param = { 0 }; + + switch (type) { + case CAPTUREDEVIDE_TYPE::VIDEO: + targetMediaParam = defaultVideoParam; + imageRGB = (unsigned char*)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_RGB24, targetMediaParam.width, targetMediaParam.height, 1)); + imgJpegSize = av_image_get_buffer_size(AV_PIX_FMT_YUVJ411P, targetMediaParam.width, targetMediaParam.height, 1); + imageJpeg = (unsigned char*)av_malloc(imgJpegSize); + break; + case CAPTUREDEVIDE_TYPE::AUDIO: + // TODO: + targetMediaParam = defaultAudioParam; + imageRGB = nullptr; + imageJpeg = nullptr; + break; + } + + std::thread t(&WindowsCaptureDevice::messageHandler, this); + t.detach(); +} + +WindowsCaptureDevice::~WindowsCaptureDevice() { + switch (type) { + case CAPTUREDEVIDE_TYPE::VIDEO: + av_free(imageRGB); + av_free(imageJpeg); + break; + case CAPTUREDEVIDE_TYPE::AUDIO: + // TODO: + break; + } +} +void WindowsCaptureDevice::CheckDevices() { + isDeviceChanged = true; +} + +bool WindowsCaptureDevice::init() { + HRESULT hr = S_OK; + + if (!RegisterCMPTMFApis()) { + setError(CAPTUREDEVIDE_ERROR_INIT_FAILED, "Cannot register devices"); + return false; + } + std::unique_lock lock(paramMutex); + hr = enumDevices(); + lock.unlock(); + + if (FAILED(hr)) { + setError(CAPTUREDEVIDE_ERROR_INIT_FAILED, "Cannot enumerate devices"); + return false; + } + + updateState(CAPTUREDEVIDE_STATE::STOPPED); + return true; +} + +bool WindowsCaptureDevice::start(UINT32 width, UINT32 height) { + HRESULT hr = S_OK; + IMFAttributes *pAttributes = nullptr; + IMFMediaType *pType = nullptr; + UINT32 selection = 0; + UINT32 count = 0; + + // Release old sources first(if any). + SafeRelease(&m_pSource); + SafeRelease(&m_pReader); + if (m_pCallback) { + delete m_pCallback; + m_pCallback = nullptr; + } + // Need to re-enumerate the list,because old sources were released. + std::vector deviceList = getDeviceList(true); + + if (deviceList.size() < 1) { + setError(CAPTUREDEVIDE_ERROR_START_FAILED, "Has no device"); + return false; + } + + targetMediaParam.width = width; + targetMediaParam.height = height; + av_image_fill_linesizes(imgRGBLineSizes, AV_PIX_FMT_RGB24, targetMediaParam.width); + + m_pCallback = new ReaderCallback(this); + + switch (state) { + case CAPTUREDEVIDE_STATE::STOPPED: + for (auto &name : deviceList) { + if (name == g_Config.sCameraDevice) { + selection = count; + break; + } + ++count; + } + setSelction(selection); + hr = param.ppDevices[param.selection]->ActivateObject( + __uuidof(IMFMediaSource), + (void**)&m_pSource); + + if (SUCCEEDED(hr)) + hr = MFCreateAttributes(&pAttributes, 2); + + // Use async mode + if (SUCCEEDED(hr)) + hr = pAttributes->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK, m_pCallback); + + if (SUCCEEDED(hr)) + hr = pAttributes->SetUINT32(MF_READWRITE_DISABLE_CONVERTERS, TRUE); + + if (SUCCEEDED(hr)) { + hr = CreateSourceReaderFromMediaSource( + m_pSource, + pAttributes, + &m_pReader + ); + } + + if (!m_pReader) + hr = E_FAIL; + + if (SUCCEEDED(hr)) { + switch (type) { + case CAPTUREDEVIDE_TYPE::VIDEO: + for (DWORD i = 0; ; i++) { + hr = m_pReader->GetNativeMediaType( + (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, + i, + &pType + ); + + if (FAILED(hr)) { break; } + + hr = setDeviceParam(pType); + + if (SUCCEEDED(hr)) + break; + } + /* + hr = m_pReader->GetNativeMediaType( + (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, + (DWORD)0xFFFFFFFF,//MF_SOURCE_READER_CURRENT_TYPE_INDEX + &pType + ); + if (SUCCEEDED(hr)) + hr = setDeviceParam(pType);*/ // Don't support on Win7 + + // Request the first frame, in async mode, OnReadSample will be called when ReadSample completed. + if (SUCCEEDED(hr)) { + hr = m_pReader->ReadSample( + (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, + 0, + nullptr, + nullptr, + nullptr, + nullptr + ); + } + + break; + + case CAPTUREDEVIDE_TYPE::AUDIO: + // TODO: + break; + } + } + + if (FAILED(hr)) { + setError(CAPTUREDEVIDE_ERROR_START_FAILED, "Cannot start"); + if(m_pSource) + m_pSource->Shutdown(); + SafeRelease(&m_pSource); + SafeRelease(&pAttributes); + SafeRelease(&pType); + SafeRelease(&m_pReader); + return false; + } + + SafeRelease(&pAttributes); + SafeRelease(&pType); + updateState(CAPTUREDEVIDE_STATE::STARTED); + break; + case CAPTUREDEVIDE_STATE::LOST: + setError(CAPTUREDEVIDE_ERROR_START_FAILED, "Device has lost"); + return false; + case CAPTUREDEVIDE_STATE::STARTED: + setError(CAPTUREDEVIDE_ERROR_START_FAILED, "Device has started"); + return false; + case CAPTUREDEVIDE_STATE::UNINITIALIZED: + setError(CAPTUREDEVIDE_ERROR_START_FAILED, "Device doesn't initialize"); + return false; + default: + break; + } + return true; +} + +bool WindowsCaptureDevice::stop() { + if (state == CAPTUREDEVIDE_STATE::STOPPED) + return true; + if (m_pSource) + m_pSource->Stop(); + + updateState(CAPTUREDEVIDE_STATE::STOPPED); + + return true; +}; + +std::vector WindowsCaptureDevice::getDeviceList(bool forceEnum, int *pActuallCount) { + HRESULT hr = S_OK; + UINT32 count = 0; + LPWSTR pwstrName = nullptr; + char *cstrName = nullptr; + std::string strName; + DWORD dwMinSize = 0; + std::vector deviceList; + + if (isDeviceChanged || forceEnum) { + std::unique_lock lock(paramMutex); + for (DWORD i = 0; i < param.count; i++) { + SafeRelease(¶m.ppDevices[i]); + } + CoTaskMemFree(param.ppDevices); // Null pointer is okay. + + hr = enumDevices(); + + lock.unlock(); + + if (SUCCEEDED(hr)) + isDeviceChanged = false; + else + return deviceList; + } + + for (; count < param.count; count++) { + hr = param.ppDevices[count]->GetAllocatedString( + MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, + &pwstrName, + nullptr + ); + + if (SUCCEEDED(hr)) { + // Get the size needed first + dwMinSize = WideCharToMultiByte(CP_OEMCP, NULL, pwstrName, -1, nullptr, 0, nullptr, FALSE); + if (dwMinSize == 0) + hr = -1; + } + if (SUCCEEDED(hr)) { + cstrName = new char[dwMinSize]; + WideCharToMultiByte(CP_OEMCP, NULL, pwstrName, -1, cstrName, dwMinSize, NULL, FALSE); + strName = cstrName; + delete[] cstrName; + + deviceList.push_back(strName); + } + + CoTaskMemFree(pwstrName); + + if (FAILED(hr)) { + setError(CAPTUREDEVIDE_ERROR_GETNAMES_FAILED, "Error occurred,gotten " + std::to_string((int)count) + " device names"); + if(pActuallCount) + *pActuallCount = count; + return deviceList; + } + } + if (pActuallCount) + *pActuallCount = count + 1; + return deviceList; +} + +HRESULT WindowsCaptureDevice::setDeviceParam(IMFMediaType *pType) { + HRESULT hr = S_OK; + GUID subtype = { 0 }; + bool getFormat = false; + + switch (type) { + case CAPTUREDEVIDE_TYPE::VIDEO: + hr = pType->GetGUID(MF_MT_SUBTYPE, &subtype); + if (FAILED(hr)) + break; + + for (int i = 0; i < g_cVideoFormats; i++) { + if (subtype == g_VideoFormats[i].MFVideoFormat) { + deviceParam.videoFormat = subtype; + getFormat = true; + break; + } + } + + if (!getFormat) { + for (int i = 0; i < g_cVideoFormats; i++) { + hr = pType->SetGUID(MF_MT_SUBTYPE, g_VideoFormats[i].MFVideoFormat); + if (FAILED(hr)) + continue; + + hr = m_pReader->SetCurrentMediaType( + (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, + NULL, + pType + ); + + if (SUCCEEDED(hr)) { + deviceParam.videoFormat = g_VideoFormats[i].MFVideoFormat; + getFormat = true; + break; + } + } + } + if (SUCCEEDED(hr)) + hr = MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, &deviceParam.width, &deviceParam.height); + + if (SUCCEEDED(hr)) + hr = GetDefaultStride(pType, &deviceParam.default_stride); + + break; + case CAPTUREDEVIDE_TYPE::AUDIO: + // TODO: + break; + } + + return hr; +} + +void WindowsCaptureDevice::sendMessage(CAPTUREDEVIDE_MESSAGE message) { + // Must be unique lock + std::unique_lock lock(mutex); + messageQueue.push(message); + lock.unlock(); + cond.notify_one(); +} + +CAPTUREDEVIDE_MESSAGE WindowsCaptureDevice::getMessage() { + // Must be unique lock + std::unique_lock lock(mutex); + CAPTUREDEVIDE_MESSAGE message; + cond.wait(lock, [this]() { return !messageQueue.empty(); }); + message = messageQueue.front(); + messageQueue.pop(); + lock.unlock(); + + return message; +} + +void WindowsCaptureDevice::messageHandler() { + CoInitializeEx(NULL, COINIT_MULTITHREADED); + MFStartup(MF_VERSION); + CAPTUREDEVIDE_MESSAGE message; + std::vector* resolution; + + if (type == CAPTUREDEVIDE_TYPE::VIDEO) { + setCurrentThreadName("Camera"); + } + + while ((message = getMessage()).command != CAPTUREDEVIDE_COMMAND::SHUTDOWN) { + switch (message.command) { + case CAPTUREDEVIDE_COMMAND::INITIALIZE: + init(); + break; + case CAPTUREDEVIDE_COMMAND::START: + resolution = static_cast*>(message.opacity); + start(resolution->at(0), resolution->at(1)); + break; + case CAPTUREDEVIDE_COMMAND::STOP: + stop(); + break; + case CAPTUREDEVIDE_COMMAND::UPDATE_STATE: + updateState((*(CAPTUREDEVIDE_STATE *)message.opacity)); + break; + } + } + + if (state != CAPTUREDEVIDE_STATE::STOPPED) + stop(); + + std::lock_guard lock(sdMutex); + SafeRelease(&m_pSource); + SafeRelease(&m_pReader); + delete m_pCallback; + unRegisterCMPTMFApis(); + + std::unique_lock lock2(paramMutex); + for (DWORD i = 0; i < param.count; i++) { + SafeRelease(¶m.ppDevices[i]); + } + CoTaskMemFree(param.ppDevices); // Null pointer is okay. + lock2.unlock(); + + MFShutdown(); + CoUninitialize(); + + updateState(CAPTUREDEVIDE_STATE::SHUTDOWN); +} + +HRESULT WindowsCaptureDevice::enumDevices() { + HRESULT hr = S_OK; + IMFAttributes *pAttributes = nullptr; + + hr = MFCreateAttributes(&pAttributes, 1); + if (SUCCEEDED(hr)) { + switch (type) { + case CAPTUREDEVIDE_TYPE::VIDEO: + hr = pAttributes->SetGUID( + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID + ); + + break; + case CAPTUREDEVIDE_TYPE::AUDIO: + hr = pAttributes->SetGUID( + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, + MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_AUDCAP_GUID + ); + + break; + default: + setError(CAPTUREDEVIDE_ERROR_UNKNOWN_TYPE, "Unknown device type"); + return E_FAIL; + } + } + if (SUCCEEDED(hr)) { + hr = EnumDeviceSources(pAttributes, ¶m.ppDevices, ¶m.count); + } + + SafeRelease(&pAttributes); + return hr; +} + +//----------------------------------------------------------------------------- +// GetDefaultStride +// +// Gets the default stride for a video frame, assuming no extra padding bytes. +// +//----------------------------------------------------------------------------- + +HRESULT GetDefaultStride(IMFMediaType *pType, LONG *plStride) +{ + LONG lStride = 0; + + // Try to get the default stride from the media type. + HRESULT hr = pType->GetUINT32(MF_MT_DEFAULT_STRIDE, (UINT32*)&lStride); + if (FAILED(hr)) + { + // Attribute not set. Try to calculate the default stride. + GUID subtype = GUID_NULL; + + UINT32 width = 0; + UINT32 height = 0; + + // Get the subtype and the image size. + hr = pType->GetGUID(MF_MT_SUBTYPE, &subtype); + if (SUCCEEDED(hr)) + { + hr = MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, &width, &height); + } + if (SUCCEEDED(hr)) + { + hr = GetStrideForBitmapInfoHeader(subtype.Data1, width, &lStride); + } + + // Set the attribute for later reference. + if (SUCCEEDED(hr)) + { + (void)pType->SetUINT32(MF_MT_DEFAULT_STRIDE, UINT32(lStride)); + } + } + + if (SUCCEEDED(hr)) + { + *plStride = lStride; + } + return hr; +} diff --git a/Windows/CaptureDevice.h b/Windows/CaptureDevice.h new file mode 100644 index 0000000000..fe82f02a93 --- /dev/null +++ b/Windows/CaptureDevice.h @@ -0,0 +1,225 @@ +// Copyright (c) 2020- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#include "libavformat/avformat.h" +#include "libswscale/swscale.h" +#include "libavutil/imgutils.h" +} +#endif // __cplusplus + +struct VideoFormatTransform { + GUID MFVideoFormat; + AVPixelFormat AVVideoFormat; +}; + +enum class CAPTUREDEVIDE_TYPE { + VIDEO, + AUDIO +}; + +enum class CAPTUREDEVIDE_STATE { + UNINITIALIZED, + LOST, + STOPPED, + STARTED, + SHUTDOWN +}; + +enum class CAPTUREDEVIDE_COMMAND { + INITIALIZE, + START, + STOP, + SHUTDOWN, + UPDATE_STATE +}; + +enum CAPTUREDEVIDE_ERROR { + CAPTUREDEVIDE_ERROR_NO_ERROR, + CAPTUREDEVIDE_ERROR_UNKNOWN_TYPE = 0x80000001, + CAPTUREDEVIDE_ERROR_INIT_FAILED, + CAPTUREDEVIDE_ERROR_START_FAILED, + CAPTUREDEVIDE_ERROR_STOP_FAILED, + CAPTUREDEVIDE_ERROR_GETNAMES_FAILED +}; + +struct CAPTUREDEVIDE_MESSAGE{ + CAPTUREDEVIDE_COMMAND command; + void *opacity; +}; + +struct ChooseDeviceParam { + IMFActivate **ppDevices; + UINT32 count; + UINT32 selection; +}; + +union MediaParam { + struct { + UINT32 width; + UINT32 height; + LONG default_stride; + GUID videoFormat; + }; + struct { + UINT32 sampleRate; + UINT32 channels; + LONG padding; + GUID audioFomat; + }; +}; + +template void SafeRelease(T **ppT) { + if (*ppT) { + (*ppT)->Release(); + *ppT = nullptr; + } +} + +class WindowsCaptureDevice; + +class ReaderCallback : public IMFSourceReaderCallback { +public: + ReaderCallback(WindowsCaptureDevice *device); + ~ReaderCallback(); + + // IUnknown methods. + STDMETHODIMP QueryInterface(REFIID iid, void** ppv); + STDMETHODIMP_(ULONG) AddRef() { return 0; } // Unused, just define. + STDMETHODIMP_(ULONG) Release() { return 0; } // Unused, just define. + + // IMFSourceReaderCallback methods. + STDMETHODIMP OnReadSample( + HRESULT hrStatus, + DWORD dwStreamIndex, + DWORD dwStreamFlags, + LONGLONG llTimestamp, + IMFSample *pSample // Can be null,even if hrStatus is success. + ); + + STDMETHODIMP OnEvent(DWORD, IMFMediaEvent *) { return S_OK; } + STDMETHODIMP OnFlush(DWORD) { return S_OK; } + + AVPixelFormat getAVVideoFormatbyMFVideoFormat(const GUID &MFVideoFormat); + + /* + * Always convet the image to RGB24 + * @param dst/src pointer to destination/source image + * @param dstW/srcW, dstH/srcH destination/source image's width and height in pixels + * @param dstLineSizes get the linesize of each plane by av_image_fill_linesizes() + * @param srcFormat MF_MT_SUBTYPE attribute of source image + * @param srcPadding should be setted to non-zero if source image has padding + */ + void imgConvert( + unsigned char *dst, unsigned int &dstW, unsigned int &dstH, int dstLineSizes[4], + unsigned char *src, const unsigned int &srcW, const unsigned int &srcH, const GUID &srcFormat, + const int &srcPadding); + + // Flip image start and end in memory, it is neccessary if stride of source image is a negative value + // Might need some tests in different machine. + void imgInvert(unsigned char *dst, unsigned char *src, const int &srcW, const int &srcH, const GUID &srcFormat, const int &srcStride); + void imgInvertRGBA(unsigned char *dst, int &dstStride, unsigned char *src, const int &srcStride, const int &h); + void imgInvertRGB(unsigned char *dst, int &dstStride, unsigned char *src, const int &srcStride, const int &h); + void imgInvertYUY2(unsigned char *dst, int &dstStride, unsigned char *src, const int &srcStride, const int &h); + void imgInvertNV12(unsigned char *dst, int &dstStride, unsigned char *src, const int &srcStride, const int &h); + + +protected: + WindowsCaptureDevice *device; + SwsContext *img_convert_ctx; +}; + +class WindowsCaptureDevice { +public: + WindowsCaptureDevice(CAPTUREDEVIDE_TYPE type); + ~WindowsCaptureDevice(); + + static void CheckDevices(); + + bool init(); + bool start(UINT32 width, UINT32 height); + bool stop(); + + CAPTUREDEVIDE_ERROR getError() const { return error; } + std::string getErrorMessage() const { return errorMessage; } + int getDeviceCounts() const { return param.count; } + // Get a list contained friendly device name. + std::vector getDeviceList(bool forceEnum = false, int *pActuallCount = nullptr); + + void setError(const CAPTUREDEVIDE_ERROR &newError, const std::string &newErrorMessage) { error = newError; errorMessage = newErrorMessage; } + void setSelction(const UINT32 &selection) { param.selection = selection; } + void updateState(const CAPTUREDEVIDE_STATE &newState) { state = newState; } + HRESULT setDeviceParam(IMFMediaType *pType); + + bool isShutDown() const { return state == CAPTUREDEVIDE_STATE::SHUTDOWN; } + + void sendMessage(CAPTUREDEVIDE_MESSAGE message); + CAPTUREDEVIDE_MESSAGE getMessage(); + + HRESULT enumDevices(); + + friend class ReaderCallback; + +protected: +// Handle message here. + void messageHandler(); + + CAPTUREDEVIDE_TYPE type; + MediaParam deviceParam; + MediaParam targetMediaParam; + CAPTUREDEVIDE_STATE state; + ChooseDeviceParam param; + + CAPTUREDEVIDE_ERROR error; + std::string errorMessage; + +// MF interface. + ReaderCallback *m_pCallback; + IMFSourceReader *m_pReader; + IMFMediaSource *m_pSource; + +// Message loop. + std::mutex mutex; + std::condition_variable cond; + std::queue messageQueue; + +// For the shutdown event safety. + std::mutex sdMutex; + +// Param updating synchronously. + std::mutex paramMutex; + +// Camera only + unsigned char *imageRGB; + int imgRGBLineSizes[4]; + unsigned char *imageJpeg; + int imgJpegSize; +}; + +extern WindowsCaptureDevice *winCamera; diff --git a/Windows/Debugger/CtrlDisAsmView.cpp b/Windows/Debugger/CtrlDisAsmView.cpp index 71fea8b4ac..7717503399 100644 --- a/Windows/Debugger/CtrlDisAsmView.cpp +++ b/Windows/Debugger/CtrlDisAsmView.cpp @@ -4,6 +4,7 @@ #include "Core/MemMap.h" #include "Core/MIPS/JitCommon/JitCommon.h" #include "Windows/W32Util/Misc.h" +#include "Windows/W32Util/ShellUtil.h" #include "Windows/MainWindow.h" #include "Windows/InputBox.h" @@ -1323,47 +1324,31 @@ std::string CtrlDisAsmView::disassembleRange(u32 start, u32 size) return result; } -void CtrlDisAsmView::disassembleToFile() -{ - wchar_t fileName[MAX_PATH]; - u32 size; - +void CtrlDisAsmView::disassembleToFile() { // get size - if (executeExpressionWindow(wnd,debugger,size) == false) return; - if (size == 0 || size > 10*1024*1024) - { + u32 size; + if (executeExpressionWindow(wnd,debugger,size) == false) + return; + if (size == 0 || size > 10*1024*1024) { MessageBox(wnd,L"Invalid size!",L"Error",MB_OK); return; } - // get file name - OPENFILENAME ofn; - ZeroMemory( &ofn , sizeof( ofn)); - ofn.lStructSize = sizeof ( ofn ); - ofn.hwndOwner = NULL ; - ofn.lpstrFile = fileName ; - ofn.lpstrFile[0] = '\0'; - ofn.nMaxFile = sizeof( fileName ); - ofn.lpstrFilter = L"All Files\0*.*\0\0"; - ofn.nFilterIndex = 1; - ofn.lpstrFileTitle = NULL ; - ofn.nMaxFileTitle = 0 ; - ofn.lpstrInitialDir = NULL ; - ofn.Flags = OFN_PATHMUSTEXIST|OFN_FILEMUSTEXIST|OFN_OVERWRITEPROMPT; + std::string filename; + if (W32Util::BrowseForFileName(false, nullptr, L"Save Disassembly As...", nullptr, L"All Files\0*.*\0\0", nullptr, filename)) { + std::wstring fileName = ConvertUTF8ToWString(filename); + FILE *output = _wfopen(fileName.c_str(), L"wb"); + if (output == nullptr) { + MessageBox(wnd, L"Could not open file!", L"Error", MB_OK); + return; + } - if (GetSaveFileName(&ofn) == false) return; + std::string disassembly = disassembleRange(curAddress, size); + fprintf(output, "%s", disassembly.c_str()); - FILE* output = _wfopen(fileName, L"wb"); - if (output == NULL) { - MessageBox(wnd,L"Could not open file!",L"Error",MB_OK); - return; + fclose(output); + MessageBox(wnd, L"Finished!", L"Done", MB_OK); } - - std::string disassembly = disassembleRange(curAddress,size); - fprintf(output,"%s",disassembly.c_str()); - - fclose(output); - MessageBox(wnd,L"Finished!",L"Done",MB_OK); } void CtrlDisAsmView::getOpcodeText(u32 address, char* dest, int bufsize) diff --git a/Windows/Debugger/DumpMemoryWindow.cpp b/Windows/Debugger/DumpMemoryWindow.cpp index 7fcc420157..1fdb549486 100644 --- a/Windows/Debugger/DumpMemoryWindow.cpp +++ b/Windows/Debugger/DumpMemoryWindow.cpp @@ -1,11 +1,28 @@ -#include "DumpMemoryWindow.h" -#include "../resource.h" -#include -#include "Core/MemMap.h" -#include "Windows/W32Util/ShellUtil.h" +#include +#include +#include "util/text/utf8.h" #include "Core/Core.h" +#include "Core/MemMap.h" +#include "Windows/Debugger/DumpMemoryWindow.h" +#include "Windows/resource.h" +#include "Windows/W32Util/ShellUtil.h" DumpMemoryWindow* DumpMemoryWindow::bp; + +void DumpMemoryWindow::HandleBrowseClick(HWND hwnd) { + std::wstring buffer; + HWND filenameWnd = GetDlgItem(hwnd, IDC_DUMP_FILENAME); + buffer.resize(GetWindowTextLengthW(filenameWnd) + 1); + GetWindowTextW(filenameWnd, &buffer[0], (int)buffer.size()); + std::string fn = ConvertWStringToUTF8(buffer); + + bool result = W32Util::BrowseForFileName(false, hwnd, L"Select filename", NULL, NULL, NULL, fn); + if (result) { + filenameChosen_ = true; + buffer = ConvertUTF8ToWString(fn); + SetWindowTextW(filenameWnd, buffer.c_str()); + } +} INT_PTR CALLBACK DumpMemoryWindow::dlgFunc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam) { @@ -53,16 +70,7 @@ INT_PTR CALLBACK DumpMemoryWindow::dlgFunc(HWND hwnd, UINT iMsg, WPARAM wParam, switch (HIWORD(wParam)) { case BN_CLICKED: - char str[MAX_PATH]; - GetWindowTextA(GetDlgItem(hwnd,IDC_DUMP_FILENAME),str,MAX_PATH); - std::string fn = str; - - bool result = W32Util::BrowseForFileName(false, hwnd, L"Select filename", NULL,NULL,NULL,fn); - if (result) - { - bp->filenameChosen = true; - SetWindowTextA(GetDlgItem(hwnd,IDC_DUMP_FILENAME),fn.c_str()); - } + bp->HandleBrowseClick(hwnd); break; } break; @@ -73,10 +81,10 @@ INT_PTR CALLBACK DumpMemoryWindow::dlgFunc(HWND hwnd, UINT iMsg, WPARAM wParam, if (!PSP_IsInited()) break; - FILE* output = fopen(bp->fileName,"wb"); + FILE *output = _wfopen(bp->fileName_.c_str(), L"wb"); if (output == NULL) { char errorMessage[2048]; - snprintf(errorMessage, sizeof(errorMessage), "Could not open file \"%s\".",bp->fileName); + snprintf(errorMessage, sizeof(errorMessage), "Could not open file \"%S\".", bp->fileName_.c_str()); MessageBoxA(hwnd,errorMessage,"Error",MB_OK); break; } @@ -140,8 +148,10 @@ bool DumpMemoryWindow::fetchDialogData(HWND hwnd) } // get filename - GetWindowTextA(GetDlgItem(hwnd,IDC_DUMP_FILENAME),fileName,MAX_PATH); - if (strlen(fileName) == 0) return false; + fileName_.resize(GetWindowTextLengthW(GetDlgItem(hwnd, IDC_DUMP_FILENAME)) + 1); + GetWindowTextW(GetDlgItem(hwnd, IDC_DUMP_FILENAME), &fileName_[0], (int)fileName_.size()); + if (fileName_.size() == 0) + return false; // now check if data makes sense... bool invalidSize = false; @@ -190,7 +200,7 @@ void DumpMemoryWindow::changeMode(HWND hwnd, Mode newMode) EnableWindow(GetDlgItem(hwnd,IDC_DUMP_STARTADDRESS),TRUE); EnableWindow(GetDlgItem(hwnd,IDC_DUMP_SIZE),TRUE); - if (filenameChosen == false) + if (filenameChosen_ == false) SetWindowTextA(GetDlgItem(hwnd,IDC_DUMP_FILENAME),"Custom.dump"); } else { u32 start, size; @@ -223,7 +233,7 @@ void DumpMemoryWindow::changeMode(HWND hwnd, Mode newMode) SetWindowTextA(GetDlgItem(hwnd,IDC_DUMP_SIZE),buffer); EnableWindow(GetDlgItem(hwnd,IDC_DUMP_SIZE),FALSE); - if (filenameChosen == false) + if (filenameChosen_ == false) SetWindowTextA(GetDlgItem(hwnd,IDC_DUMP_FILENAME),defaultFileName); } } diff --git a/Windows/Debugger/DumpMemoryWindow.h b/Windows/Debugger/DumpMemoryWindow.h index 6c808552d3..f9a94151a6 100644 --- a/Windows/Debugger/DumpMemoryWindow.h +++ b/Windows/Debugger/DumpMemoryWindow.h @@ -9,21 +9,23 @@ class DumpMemoryWindow HWND parentHwnd; DebugInterface* cpu; - bool filenameChosen; + bool filenameChosen_; Mode selectedMode; u32 start; u32 size; - char fileName[MAX_PATH]; + std::wstring fileName_; static DumpMemoryWindow* bp; void changeMode(HWND hwnd, Mode newMode); bool fetchDialogData(HWND hwnd); + void HandleBrowseClick(HWND hwnd); + public: DumpMemoryWindow(HWND parent, DebugInterface* cpu): cpu(cpu) { parentHwnd = parent; - filenameChosen = false; + filenameChosen_ = false; selectedMode = MODE_RAM; }; diff --git a/Windows/EmuThread.cpp b/Windows/EmuThread.cpp index 3e310bf7cb..2d5c6d06f4 100644 --- a/Windows/EmuThread.cpp +++ b/Windows/EmuThread.cpp @@ -135,7 +135,7 @@ void MainThreadFunc() { if (g_Config.sFailedGPUBackends.find("ALL") != std::string::npos) { Reporting::ReportMessage("Graphics init error: %s", "ALL"); - I18NCategory *err = GetI18NCategory("Error"); + auto err = GetI18NCategory("Error"); const char *defaultErrorAll = "Failed initializing any graphics. Try upgrading your graphics drivers."; const char *genericError = err->T("GenericAllGraphicsError", defaultErrorAll); std::wstring title = ConvertUTF8ToWString(err->T("GenericGraphicsError", "Graphics Error")); @@ -163,7 +163,7 @@ void MainThreadFunc() { W32Util::ExitAndRestart(); } - I18NCategory *err = GetI18NCategory("Error"); + auto err = GetI18NCategory("Error"); Reporting::ReportMessage("Graphics init error: %s", error_string.c_str()); const char *defaultErrorVulkan = "Failed initializing graphics. Try upgrading your graphics drivers.\n\nWould you like to try switching to OpenGL?\n\nError message:"; diff --git a/Windows/GPU/D3D11Context.cpp b/Windows/GPU/D3D11Context.cpp index fef1438fb6..c8911e5144 100644 --- a/Windows/GPU/D3D11Context.cpp +++ b/Windows/GPU/D3D11Context.cpp @@ -142,7 +142,7 @@ bool D3D11Context::Init(HINSTANCE hInst, HWND wnd, std::string *error_message) { if (FAILED(hr)) { const char *defaultError = "Your GPU does not appear to support Direct3D 11.\n\nWould you like to try again using Direct3D 9 instead?"; - I18NCategory *err = GetI18NCategory("Error"); + auto err = GetI18NCategory("Error"); std::wstring error; diff --git a/Windows/GPU/WindowsGLContext.cpp b/Windows/GPU/WindowsGLContext.cpp index 97540ec629..0c8c3bd685 100644 --- a/Windows/GPU/WindowsGLContext.cpp +++ b/Windows/GPU/WindowsGLContext.cpp @@ -236,7 +236,7 @@ bool WindowsGLContext::InitFromRenderThread(std::string *error_message) { // GL_VERSION GL_VENDOR GL_RENDERER // "1.4.0 - Build 8.14.10.2364" "intel" intel Pineview Platform - I18NCategory *err = GetI18NCategory("Error"); + auto err = GetI18NCategory("Error"); std::string glVersion = (const char *)glGetString(GL_VERSION); std::string glRenderer = (const char *)glGetString(GL_RENDERER); diff --git a/Windows/MainWindow.cpp b/Windows/MainWindow.cpp index 76c90d7cd7..310dc94265 100644 --- a/Windows/MainWindow.cpp +++ b/Windows/MainWindow.cpp @@ -72,6 +72,7 @@ #include "Windows/W32Util/ShellUtil.h" #include "Windows/W32Util/Misc.h" #include "Windows/RawInput.h" +#include "Windows/CaptureDevice.h" #include "Windows/TouchInputHandler.h" #include "Windows/MainWindowMenu.h" #include "GPU/GPUInterface.h" @@ -153,7 +154,7 @@ namespace MainWindow // Register classes - Main Window WNDCLASSEX wcex; memset(&wcex, 0, sizeof(wcex)); - wcex.cbSize = sizeof(WNDCLASSEX); + wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = 0; // Show in taskbar wcex.lpfnWndProc = (WNDPROC)WndProc; wcex.hInstance = hInstance; @@ -227,7 +228,7 @@ namespace MainWindow if (++g_Config.iInternalResolution > RESOLUTION_MAX) g_Config.iInternalResolution = 0; } - + // Taking auto-texture scaling into account if (g_Config.iTexScalingLevel == TEXSCALING_AUTO) setTexScalingMultiplier(0); @@ -328,7 +329,7 @@ namespace MainWindow dwStyle &= ~WS_POPUP; // Re-add caption and border styles. dwStyle |= WS_OVERLAPPEDWINDOW; - + // Put back the menu bar. ::SetMenu(hWnd, menu); } else { @@ -450,7 +451,7 @@ namespace MainWindow bool portrait = g_Config.IsPortrait(); // We want to adjust for DPI but still get an integer pixel scaling ratio. - double dpi_scale = 96.0 / System_GetPropertyInt(SYSPROP_DISPLAY_DPI); + double dpi_scale = 96.0 / System_GetPropertyFloat(SYSPROP_DISPLAY_DPI); int scale = (int)ceil(2.0 / dpi_scale); GetWindowSizeAtResolution(scale * (portrait ? 272 : 480), scale * (portrait ? 480 : 272), &windowWidth, &windowHeight); @@ -582,7 +583,7 @@ namespace MainWindow if (disasmWindow[0]) delete disasmWindow[0]; disasmWindow[0] = 0; - + #if PPSSPP_API(ANY_GL) DialogManager::RemoveDlg(geDebuggerWindow); if (geDebuggerWindow) @@ -717,7 +718,7 @@ namespace MainWindow } return 0; } - + LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_CREATE: @@ -726,7 +727,7 @@ namespace MainWindow RemoveMenu(GetMenu(hWnd), ID_OPTIONS_DIRECT3D11, MF_BYCOMMAND); } break; - + case WM_GETMINMAXINFO: { MINMAXINFO *minmax = reinterpret_cast(lParam); @@ -888,6 +889,7 @@ namespace MainWindow #ifndef _M_ARM DinputDevice::CheckDevices(); #endif + WindowsCaptureDevice::CheckDevices(); return DefWindowProc(hWnd, message, wParam, lParam); case WM_VERYSLEEPY_MSG: @@ -1013,10 +1015,10 @@ namespace MainWindow case WM_SYSCOMMAND: { switch (wParam) { - case SC_SCREENSAVE: + case SC_SCREENSAVE: return 0; case SC_MONITORPOWER: - return 0; + return 0; } return DefWindowProc(hWnd, message, wParam, lParam); } @@ -1026,7 +1028,7 @@ namespace MainWindow } return 0; } - + void Redraw() { InvalidateRect(hwndDisplay,0,0); } diff --git a/Windows/MainWindowMenu.cpp b/Windows/MainWindowMenu.cpp index bcd97012f9..825b0a4722 100644 --- a/Windows/MainWindowMenu.cpp +++ b/Windows/MainWindowMenu.cpp @@ -125,7 +125,7 @@ namespace MainWindow { } void CreateHelpMenu(HMENU menu) { - I18NCategory *des = GetI18NCategory("DesktopUI"); + auto des = GetI18NCategory("DesktopUI"); const std::wstring visitMainWebsite = ConvertUTF8ToWString(des->T("www.ppsspp.org")); const std::wstring visitForum = ConvertUTF8ToWString(des->T("PPSSPP Forums")); @@ -144,7 +144,7 @@ namespace MainWindow { } void CreateSimpleUIMenu(HMENU menu) { - I18NCategory *des = GetI18NCategory("DesktopUI"); + auto des = GetI18NCategory("DesktopUI"); const std::wstring simpleUI = ConvertUTF8ToWString(des->T("Simple UI")); const std::wstring enable = ConvertUTF8ToWString(des->T("Enable")); @@ -184,7 +184,7 @@ namespace MainWindow { return false; } - I18NCategory *ps = GetI18NCategory("PostShaders"); + auto ps = GetI18NCategory("PostShaders"); HMENU shaderMenu = GetSubmenuById(menu, ID_OPTIONS_SHADER_MENU); EmptySubMenu(shaderMenu); @@ -215,7 +215,7 @@ namespace MainWindow { } static void TranslateMenuItem(const HMENU hMenu, const int menuID, const std::wstring& accelerator = L"", const char *key = nullptr) { - I18NCategory *des = GetI18NCategory("DesktopUI"); + auto des = GetI18NCategory("DesktopUI"); std::wstring translated; if (key == nullptr || !strcmp(key, "")) { @@ -508,7 +508,7 @@ namespace MainWindow { NativeMessageReceived("gpu_clearCache", ""); std::ostringstream messageStream; - I18NCategory *gr = GetI18NCategory("Graphics"); + auto gr = GetI18NCategory("Graphics"); messageStream << gr->T("Texture Scaling") << ":" << " "; if (realtime) { messageStream << gr->T("Realtime"); @@ -519,7 +519,7 @@ namespace MainWindow { } static void setRenderingMode(int mode) { - I18NCategory *gr = GetI18NCategory("Graphics"); + auto gr = GetI18NCategory("Graphics"); g_Config.iRenderingMode = mode; switch (g_Config.iRenderingMode) { @@ -544,7 +544,7 @@ namespace MainWindow { g_Config.iFrameSkip = FRAMESKIP_OFF; } - I18NCategory *gr = GetI18NCategory("Graphics"); + auto gr = GetI18NCategory("Graphics"); std::ostringstream messageStream; messageStream << gr->T("Frame Skipping") << ":" << " "; @@ -564,7 +564,7 @@ namespace MainWindow { g_Config.iFrameSkipType = 0; } - I18NCategory *gr = GetI18NCategory("Graphics"); + auto gr = GetI18NCategory("Graphics"); std::ostringstream messageStream; messageStream << gr->T("Frame Skipping Type") << ":" << " "; @@ -598,7 +598,7 @@ namespace MainWindow { void MainWindowMenu_Process(HWND hWnd, WPARAM wParam) { std::string fn; - I18NCategory *gr = GetI18NCategory("Graphics"); + auto gr = GetI18NCategory("Graphics"); int wmId = LOWORD(wParam); int wmEvent = HIWORD(wParam); diff --git a/Windows/PPSSPP.manifest b/Windows/PPSSPP.manifest index 54de138d8b..3be621bb0a 100644 --- a/Windows/PPSSPP.manifest +++ b/Windows/PPSSPP.manifest @@ -32,7 +32,8 @@ - True + true + true diff --git a/Windows/PPSSPP.vcxproj b/Windows/PPSSPP.vcxproj index 9eed29a578..212518524e 100644 --- a/Windows/PPSSPP.vcxproj +++ b/Windows/PPSSPP.vcxproj @@ -238,7 +238,7 @@ Use Level3 ProgramDatabase - ../dx9sdk/Include/DX11;../ext;../common;..;../ext/native;../ext/glew;../ext/zlib + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../dx9sdk/Include/DX11;../ext;../common;..;../ext/native;../ext/glew;../ext/zlib stdafx.h;Common/DbgNew.h MultiThreadedDebug StreamingSIMDExtensions2 @@ -249,7 +249,7 @@ $(EXTERNAL_COMPILE_OPTIONS) - Winmm.lib;Ws2_32.lib;opengl32.lib;dsound.lib;glu32.lib;comctl32.lib;d3d9.lib;dxguid.lib;..\ffmpeg\Windows\x86\lib\avcodec.lib;..\ffmpeg\Windows\x86\lib\avformat.lib;..\ffmpeg\Windows\x86\lib\avutil.lib;..\ffmpeg\Windows\x86\lib\swresample.lib;..\ffmpeg\Windows\x86\lib\swscale.lib;%(AdditionalDependencies) + mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;opengl32.lib;dsound.lib;glu32.lib;comctl32.lib;d3d9.lib;dxguid.lib;..\ffmpeg\Windows\x86\lib\avcodec.lib;..\ffmpeg\Windows\x86\lib\avformat.lib;..\ffmpeg\Windows\x86\lib\avutil.lib;..\ffmpeg\Windows\x86\lib\swresample.lib;..\ffmpeg\Windows\x86\lib\swscale.lib;%(AdditionalDependencies) true Windows MachineX86 @@ -275,7 +275,7 @@ Use Level3 ProgramDatabase - ../dx9sdk/Include/DX11;../ext;../common;..;../ext/native;../ext/glew;../ext/zlib + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../dx9sdk/Include/DX11;../ext;../common;..;../ext/native;../ext/glew;../ext/zlib stdafx.h;Common/DbgNew.h MultiThreadedDebug false @@ -287,7 +287,7 @@ $(EXTERNAL_COMPILE_OPTIONS) - Winmm.lib;Ws2_32.lib;opengl32.lib;dsound.lib;glu32.lib;comctl32.lib;d3d9.lib;dxguid.lib;..\ffmpeg\Windows\x86_64\lib\avcodec.lib;..\ffmpeg\Windows\x86_64\lib\avformat.lib;..\ffmpeg\Windows\x86_64\lib\avutil.lib;..\ffmpeg\Windows\x86_64\lib\swresample.lib;..\ffmpeg\Windows\x86_64\lib\swscale.lib;%(AdditionalDependencies) + mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;opengl32.lib;dsound.lib;glu32.lib;comctl32.lib;d3d9.lib;dxguid.lib;..\ffmpeg\Windows\x86_64\lib\avcodec.lib;..\ffmpeg\Windows\x86_64\lib\avformat.lib;..\ffmpeg\Windows\x86_64\lib\avutil.lib;..\ffmpeg\Windows\x86_64\lib\swresample.lib;..\ffmpeg\Windows\x86_64\lib\swscale.lib;%(AdditionalDependencies) true $(OutDir)$(ProjectName).pdb true @@ -342,7 +342,7 @@ Use Level3 ProgramDatabase - ../dx9sdk/Include/DX11;../ext;../common;..;../ext/native;../ext/glew;../ext/zlib + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../dx9sdk/Include/DX11;../ext;../common;..;../ext/native;../ext/glew;../ext/zlib stdafx.h MultiThreadedDebug false @@ -354,7 +354,7 @@ $(EXTERNAL_COMPILE_OPTIONS) - oleaut32.lib;comdlg32.lib;shell32.lib;user32.lib;gdi32.lib;advapi32.lib;ole32.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;..\ffmpeg\Windows\arm\lib\avcodec.lib;..\ffmpeg\Windows\arm\lib\avformat.lib;..\ffmpeg\Windows\arm\lib\avutil.lib;..\ffmpeg\Windows\arm\lib\swresample.lib;..\ffmpeg\Windows\arm\lib\swscale.lib;%(AdditionalDependencies) + mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;oleaut32.lib;comdlg32.lib;shell32.lib;user32.lib;gdi32.lib;advapi32.lib;ole32.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;..\ffmpeg\Windows\arm\lib\avcodec.lib;..\ffmpeg\Windows\arm\lib\avformat.lib;..\ffmpeg\Windows\arm\lib\avutil.lib;..\ffmpeg\Windows\arm\lib\swresample.lib;..\ffmpeg\Windows\arm\lib\swscale.lib;%(AdditionalDependencies) true $(OutDir)$(ProjectName).pdb true @@ -382,7 +382,7 @@ Use Level3 ProgramDatabase - ../dx9sdk/Include/DX11;../ext;../common;..;../ext/native;../ext/glew;../ext/zlib + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../dx9sdk/Include/DX11;../ext;../common;..;../ext/native;../ext/glew;../ext/zlib stdafx.h stdafx.h true @@ -390,7 +390,7 @@ $(EXTERNAL_COMPILE_OPTIONS) - Winmm.lib;Ws2_32.lib;opengl32.lib;dsound.lib;glu32.lib;comctl32.lib;d3d9.lib;dxguid.lib;..\ffmpeg\Windows\x86\lib\avcodec.lib;..\ffmpeg\Windows\x86\lib\avformat.lib;..\ffmpeg\Windows\x86\lib\avutil.lib;..\ffmpeg\Windows\x86\lib\swresample.lib;..\ffmpeg\Windows\x86\lib\swscale.lib;%(AdditionalDependencies) + mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;opengl32.lib;dsound.lib;glu32.lib;comctl32.lib;d3d9.lib;dxguid.lib;..\ffmpeg\Windows\x86\lib\avcodec.lib;..\ffmpeg\Windows\x86\lib\avformat.lib;..\ffmpeg\Windows\x86\lib\avutil.lib;..\ffmpeg\Windows\x86\lib\swresample.lib;..\ffmpeg\Windows\x86\lib\swscale.lib;%(AdditionalDependencies) $(OutDir)$(TargetName)$(TargetExt) %(AdditionalLibraryDirectories) true @@ -431,14 +431,14 @@ Use Level3 ProgramDatabase - ../dx9sdk/Include/DX11;../ext;../common;..;../ext/native;../ext/glew;../ext/zlib + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../dx9sdk/Include/DX11;../ext;../common;..;../ext/native;../ext/glew;../ext/zlib stdafx.h true false $(EXTERNAL_COMPILE_OPTIONS) - Winmm.lib;Ws2_32.lib;opengl32.lib;dsound.lib;glu32.lib;comctl32.lib;d3d9.lib;dxguid.lib;..\ffmpeg\Windows\x86_64\lib\avcodec.lib;..\ffmpeg\Windows\x86_64\lib\avformat.lib;..\ffmpeg\Windows\x86_64\lib\avutil.lib;..\ffmpeg\Windows\x86_64\lib\swresample.lib;..\ffmpeg\Windows\x86_64\lib\swscale.lib;%(AdditionalDependencies) + mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;opengl32.lib;dsound.lib;glu32.lib;comctl32.lib;d3d9.lib;dxguid.lib;..\ffmpeg\Windows\x86_64\lib\avcodec.lib;..\ffmpeg\Windows\x86_64\lib\avformat.lib;..\ffmpeg\Windows\x86_64\lib\avutil.lib;..\ffmpeg\Windows\x86_64\lib\swresample.lib;..\ffmpeg\Windows\x86_64\lib\swscale.lib;%(AdditionalDependencies) %(AdditionalLibraryDirectories) true Windows @@ -514,14 +514,14 @@ Use Level3 ProgramDatabase - ../dx9sdk/Include/DX11;../ext;../common;..;../ext/native;../ext/glew;../ext/zlib + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../dx9sdk/Include/DX11;../ext;../common;..;../ext/native;../ext/glew;../ext/zlib stdafx.h true false $(EXTERNAL_COMPILE_OPTIONS) - oleaut32.lib;comdlg32.lib;shell32.lib;user32.lib;gdi32.lib;advapi32.lib;ole32.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;..\ffmpeg\Windows\arm\lib\avcodec.lib;..\ffmpeg\Windows\arm\lib\avformat.lib;..\ffmpeg\Windows\arm\lib\avutil.lib;..\ffmpeg\Windows\arm\lib\swresample.lib;..\ffmpeg\Windows\arm\lib\swscale.lib;%(AdditionalDependencies) + mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;oleaut32.lib;comdlg32.lib;shell32.lib;user32.lib;gdi32.lib;advapi32.lib;ole32.lib;Winmm.lib;Ws2_32.lib;dsound.lib;comctl32.lib;d3d9.lib;dxguid.lib;..\ffmpeg\Windows\arm\lib\avcodec.lib;..\ffmpeg\Windows\arm\lib\avformat.lib;..\ffmpeg\Windows\arm\lib\avutil.lib;..\ffmpeg\Windows\arm\lib\swresample.lib;..\ffmpeg\Windows\arm\lib\swscale.lib;%(AdditionalDependencies) %(AdditionalLibraryDirectories) true Windows @@ -680,6 +680,7 @@ true true + @@ -871,6 +872,8 @@ true true + + diff --git a/Windows/PPSSPP.vcxproj.filters b/Windows/PPSSPP.vcxproj.filters index c640c5642e..d51c53a9f5 100644 --- a/Windows/PPSSPP.vcxproj.filters +++ b/Windows/PPSSPP.vcxproj.filters @@ -191,6 +191,9 @@ Windows\System + + Windows\Input + @@ -354,6 +357,12 @@ Windows\System + + Windows\Input + + + Windows\Input + diff --git a/Windows/W32Util/Misc.cpp b/Windows/W32Util/Misc.cpp index 7c2953b2d6..ca26ad7a4b 100644 --- a/Windows/W32Util/Misc.cpp +++ b/Windows/W32Util/Misc.cpp @@ -131,15 +131,36 @@ namespace W32Util return cmdline; } + void GetSelfExecuteParams(std::wstring &workingDirectory, std::wstring &moduleFilename) { + workingDirectory.resize(MAX_PATH); + size_t sz = GetCurrentDirectoryW((DWORD)workingDirectory.size(), &workingDirectory[0]); + if (sz != 0 && sz < workingDirectory.size()) { + // This means success, so now we can remove the null terminator. + workingDirectory.resize(sz); + } else if (sz > workingDirectory.size()) { + // If insufficient, sz will include the null terminator, so we remove after. + workingDirectory.resize(sz); + sz = GetCurrentDirectoryW((DWORD)sz, &workingDirectory[0]); + workingDirectory.resize(sz); + } + + moduleFilename.clear(); + do { + moduleFilename.resize(moduleFilename.size() + MAX_PATH); + // On failure, this will return the same value as passed in, but success will always be one lower. + sz = GetModuleFileName(GetModuleHandle(nullptr), &moduleFilename[0], (DWORD)moduleFilename.size()); + } while (sz >= moduleFilename.size()); + moduleFilename.resize(sz); + } + void ExitAndRestart() { // This preserves arguments (for example, config file) and working directory. + std::wstring workingDirectory; + std::wstring moduleFilename; + GetSelfExecuteParams(workingDirectory, moduleFilename); - wchar_t moduleFilename[MAX_PATH]; - wchar_t workingDirectory[MAX_PATH]; - GetCurrentDirectoryW(MAX_PATH, workingDirectory); const wchar_t *cmdline = RemoveExecutableFromCommandLine(GetCommandLineW()); - GetModuleFileName(GetModuleHandle(NULL), moduleFilename, MAX_PATH); - ShellExecute(NULL, NULL, moduleFilename, cmdline, workingDirectory, SW_SHOW); + ShellExecute(nullptr, nullptr, moduleFilename.c_str(), cmdline, workingDirectory.c_str(), SW_SHOW); ExitProcess(0); } diff --git a/Windows/W32Util/Misc.h b/Windows/W32Util/Misc.h index 457874450e..680a48c62e 100644 --- a/Windows/W32Util/Misc.h +++ b/Windows/W32Util/Misc.h @@ -12,6 +12,7 @@ namespace W32Util BOOL CopyTextToClipboard(HWND hwnd, const std::wstring &wtext); void MakeTopMost(HWND hwnd, bool topMost); void ExitAndRestart(); + void GetSelfExecuteParams(std::wstring &workingDirectory, std::wstring &moduleFilename); } struct GenericListViewColumn diff --git a/Windows/W32Util/ShellUtil.cpp b/Windows/W32Util/ShellUtil.cpp index 9a73338de2..39938b3fc0 100644 --- a/Windows/W32Util/ShellUtil.cpp +++ b/Windows/W32Util/ShellUtil.cpp @@ -9,10 +9,10 @@ #include "util/text/utf8.h" #include "ShellUtil.h" -#include "CommDlg.h" #include #include +#include namespace W32Util { @@ -31,93 +31,147 @@ namespace W32Util //info.pszDisplayName auto idList = SHBrowseForFolder(&info); + HMODULE shell32 = GetModuleHandle(L"shell32.dll"); + typedef BOOL (WINAPI *SHGetPathFromIDListEx_f)(PCIDLIST_ABSOLUTE pidl, PWSTR pszPath, DWORD cchPath, GPFIDL_FLAGS uOpts); + SHGetPathFromIDListEx_f SHGetPathFromIDListEx_ = (SHGetPathFromIDListEx_f)GetProcAddress(shell32, "SHGetPathFromIDListEx"); + + std::string result; + if (SHGetPathFromIDListEx_) { + std::wstring temp; + do { + // Assume it's failing if it goes on too long. + if (temp.size() > 32768 * 10) { + temp.clear(); + break; + } + temp.resize(temp.size() + MAX_PATH); + } while (SHGetPathFromIDListEx_(idList, &temp[0], (DWORD)temp.size(), GPFIDL_DEFAULT) == 0); + result = ConvertWStringToUTF8(temp); + } else { + wchar_t temp[MAX_PATH]{}; + SHGetPathFromIDList(idList, temp); + result = ConvertWStringToUTF8(temp); + } - wchar_t temp[MAX_PATH]; - SHGetPathFromIDList(idList, temp); CoTaskMemFree(idList); - if (wcslen(temp)) - return ConvertWStringToUTF8(temp); - else - return ""; + return result; } //--------------------------------------------------------------------------------------------------- // function WinBrowseForFileName //--------------------------------------------------------------------------------------------------- - bool BrowseForFileName (bool _bLoad, HWND _hParent, const wchar_t *_pTitle, - const wchar_t *_pInitialFolder,const wchar_t *_pFilter,const wchar_t *_pExtension, - std::string& _strFileName) - { - wchar_t szFile [MAX_PATH+1] = {0}; - wchar_t szFileTitle [MAX_PATH+1] = {0}; + bool BrowseForFileName(bool _bLoad, HWND _hParent, const wchar_t *_pTitle, + const wchar_t *_pInitialFolder, const wchar_t *_pFilter, const wchar_t *_pExtension, + std::string &_strFileName) { + // Let's hope this is large enough, don't want to trigger the dialog twice... + std::wstring filenameBuffer(32768 * 10, '\0'); OPENFILENAME ofn{ sizeof(OPENFILENAME) }; + auto resetFileBuffer = [&] { + ofn.nMaxFile = (DWORD)filenameBuffer.size(); + ofn.lpstrFile = &filenameBuffer[0]; + if (!_strFileName.empty()) + wcsncpy(ofn.lpstrFile, ConvertUTF8ToWString(_strFileName).c_str(), filenameBuffer.size() - 1); + }; + + resetFileBuffer(); ofn.lpstrInitialDir = _pInitialFolder; ofn.lpstrFilter = _pFilter; - ofn.nMaxFile = ARRAY_SIZE(szFile); - ofn.lpstrFile = szFile; - ofn.lpstrFileTitle = szFileTitle; - ofn.nMaxFileTitle = ARRAY_SIZE(szFileTitle); + ofn.lpstrFileTitle = nullptr; + ofn.nMaxFileTitle = 0; ofn.lpstrDefExt = _pExtension; ofn.hwndOwner = _hParent; - ofn.Flags = OFN_NOCHANGEDIR | OFN_EXPLORER | OFN_HIDEREADONLY; + ofn.Flags = OFN_NOCHANGEDIR | OFN_EXPLORER; + if (!_bLoad) + ofn.Flags |= OFN_HIDEREADONLY; - if (!_strFileName.empty()) - wcsncpy(ofn.lpstrFile, ConvertUTF8ToWString(_strFileName).c_str(), MAX_PATH); + int success = _bLoad ? GetOpenFileName(&ofn) : GetSaveFileName(&ofn); + if (success == 0 && CommDlgExtendedError() == FNERR_BUFFERTOOSMALL) { + size_t sz = *(unsigned short *)&filenameBuffer[0]; + // Documentation is unclear if this is WCHARs to CHARs. + filenameBuffer.resize(filenameBuffer.size() + sz * 2); + resetFileBuffer(); + success = _bLoad ? GetOpenFileName(&ofn) : GetSaveFileName(&ofn); + } - if (((_bLoad) ? GetOpenFileName(&ofn) : GetSaveFileName(&ofn))) - { + if (success) { _strFileName = ConvertWStringToUTF8(ofn.lpstrFile); return true; } - else - return false; + return false; } std::vector BrowseForFileNameMultiSelect(bool _bLoad, HWND _hParent, const wchar_t *_pTitle, - const wchar_t *_pInitialFolder,const wchar_t *_pFilter,const wchar_t *_pExtension) - { - wchar_t szFile [MAX_PATH+1+2048*2] = {0}; - wchar_t szFileTitle [MAX_PATH+1] = {0}; + const wchar_t *_pInitialFolder, const wchar_t *_pFilter, const wchar_t *_pExtension) { + // Let's hope this is large enough, don't want to trigger the dialog twice... + std::wstring filenameBuffer(32768 * 10, '\0'); OPENFILENAME ofn{ sizeof(OPENFILENAME) }; + auto resetFileBuffer = [&] { + ofn.nMaxFile = (DWORD)filenameBuffer.size(); + ofn.lpstrFile = &filenameBuffer[0]; + }; + + resetFileBuffer(); ofn.lpstrInitialDir = _pInitialFolder; ofn.lpstrFilter = _pFilter; - ofn.nMaxFile = ARRAY_SIZE(szFile); - ofn.lpstrFile = szFile; - ofn.lpstrFileTitle = szFileTitle; - ofn.nMaxFileTitle = ARRAY_SIZE(szFileTitle); + ofn.lpstrFileTitle = nullptr; + ofn.nMaxFileTitle = 0; ofn.lpstrDefExt = _pExtension; ofn.hwndOwner = _hParent; - ofn.Flags = OFN_NOCHANGEDIR | OFN_EXPLORER | OFN_HIDEREADONLY | OFN_ALLOWMULTISELECT; + ofn.Flags = OFN_NOCHANGEDIR | OFN_EXPLORER | OFN_ALLOWMULTISELECT; + if (!_bLoad) + ofn.Flags |= OFN_HIDEREADONLY; std::vector files; + int success = _bLoad ? GetOpenFileName(&ofn) : GetSaveFileName(&ofn); + if (success == 0 && CommDlgExtendedError() == FNERR_BUFFERTOOSMALL) { + size_t sz = *(unsigned short *)&filenameBuffer[0]; + // Documentation is unclear if this is WCHARs to CHARs. + filenameBuffer.resize(filenameBuffer.size() + sz * 2); + resetFileBuffer(); + success = _bLoad ? GetOpenFileName(&ofn) : GetSaveFileName(&ofn); + } - if (((_bLoad) ? GetOpenFileName(&ofn) : GetSaveFileName(&ofn))) - { + if (success) { std::string directory = ConvertWStringToUTF8(ofn.lpstrFile); wchar_t *temp = ofn.lpstrFile; - wchar_t *oldtemp = temp; - temp += wcslen(temp)+1; - if (*temp==0) - { + temp += wcslen(temp) + 1; + if (*temp == 0) { //we only got one file - files.push_back(ConvertWStringToUTF8(oldtemp)); - } - else - { - while (*temp) - { - files.push_back(directory+"\\"+ConvertWStringToUTF8(temp)); - temp += wcslen(temp)+1; + files.push_back(directory); + } else { + while (*temp) { + files.push_back(directory + "\\" + ConvertWStringToUTF8(temp)); + temp += wcslen(temp) + 1; } } - return files; } - else - return std::vector(); // empty vector; + return files; + } + + std::string UserDocumentsPath() { + std::string result; + HMODULE shell32 = GetModuleHandle(L"shell32.dll"); + typedef HRESULT(WINAPI *SHGetKnownFolderPath_f)(REFKNOWNFOLDERID rfid, DWORD dwFlags, HANDLE hToken, PWSTR *ppszPath); + SHGetKnownFolderPath_f SHGetKnownFolderPath_ = (SHGetKnownFolderPath_f)GetProcAddress(shell32, "SHGetKnownFolderPath"); + if (SHGetKnownFolderPath_) { + PWSTR path = nullptr; + if (SHGetKnownFolderPath_(FOLDERID_Documents, 0, nullptr, &path) == S_OK) { + result = ConvertWStringToUTF8(path); + } + if (path) + CoTaskMemFree(path); + } else { + wchar_t path[MAX_PATH]; + if (SHGetFolderPath(nullptr, CSIDL_PERSONAL, nullptr, SHGFP_TYPE_CURRENT, path) == S_OK) { + result = ConvertWStringToUTF8(path); + } + } + + return result; } AsyncBrowseDialog::AsyncBrowseDialog(HWND parent, UINT completeMsg, std::wstring title) diff --git a/Windows/W32Util/ShellUtil.h b/Windows/W32Util/ShellUtil.h index da5b83ee52..8231d8c31c 100644 --- a/Windows/W32Util/ShellUtil.h +++ b/Windows/W32Util/ShellUtil.h @@ -14,6 +14,8 @@ namespace W32Util std::vector BrowseForFileNameMultiSelect(bool _bLoad, HWND _hParent, const wchar_t*_pTitle, const wchar_t*_pInitialFolder,const wchar_t*_pFilter,const wchar_t*_pExtension); + std::string UserDocumentsPath(); + struct AsyncBrowseDialog { public: enum Type { @@ -49,4 +51,4 @@ namespace W32Util bool result_; std::string filename_; }; -} \ No newline at end of file +} diff --git a/Windows/WindowsHost.cpp b/Windows/WindowsHost.cpp index 4508a1d3d3..84d09875bd 100644 --- a/Windows/WindowsHost.cpp +++ b/Windows/WindowsHost.cpp @@ -353,6 +353,7 @@ bool WindowsHost::CreateDesktopShortcut(std::string argumentPath, std::string ga // Get the desktop folder + // TODO: Not long path safe. wchar_t *pathbuf = new wchar_t[MAX_PATH + gameTitle.size() + 100]; SHGetFolderPath(0, CSIDL_DESKTOPDIRECTORY, NULL, SHGFP_TYPE_CURRENT, pathbuf); @@ -370,10 +371,16 @@ bool WindowsHost::CreateDesktopShortcut(std::string argumentPath, std::string ga wcscat(pathbuf, L"\\"); wcscat(pathbuf, ConvertUTF8ToWString(gameTitle).c_str()); - wchar_t module[MAX_PATH]; - GetModuleFileName(NULL, module, MAX_PATH); + std::wstring moduleFilename; + size_t sz; + do { + moduleFilename.resize(moduleFilename.size() + MAX_PATH); + // On failure, this will return the same value as passed in, but success will always be one lower. + sz = GetModuleFileName(nullptr, &moduleFilename[0], (DWORD)moduleFilename.size()); + } while (sz >= moduleFilename.size()); + moduleFilename.resize(sz); - CreateLink(module, ConvertUTF8ToWString(argumentPath).c_str(), pathbuf, ConvertUTF8ToWString(gameTitle).c_str()); + CreateLink(moduleFilename.c_str(), ConvertUTF8ToWString(argumentPath).c_str(), pathbuf, ConvertUTF8ToWString(gameTitle).c_str()); delete [] pathbuf; return false; diff --git a/Windows/main.cpp b/Windows/main.cpp index 866f4c0746..7acdd73713 100644 --- a/Windows/main.cpp +++ b/Windows/main.cpp @@ -126,7 +126,7 @@ std::string GetVideoCardDriverVersion() { } IWbemLocator *pIWbemLocator = NULL; - hr = CoCreateInstance(__uuidof(WbemLocator), NULL, CLSCTX_INPROC_SERVER, + hr = CoCreateInstance(__uuidof(WbemLocator), NULL, CLSCTX_INPROC_SERVER, __uuidof(IWbemLocator), (LPVOID *)&pIWbemLocator); if (FAILED(hr)) { CoUninitialize(); @@ -143,9 +143,9 @@ std::string GetVideoCardDriverVersion() { return retvalue; } - hr = CoSetProxyBlanket(pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, + hr = CoSetProxyBlanket(pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL,EOAC_DEFAULT); - + BSTR bstrWQL = SysAllocString(L"WQL"); BSTR bstrPath = SysAllocString(L"select * from Win32_VideoController"); IEnumWbemClassObject* pEnum; @@ -232,12 +232,8 @@ int System_GetPropertyInt(SystemProperty prop) { switch (prop) { case SYSPROP_AUDIO_SAMPLE_RATE: return winAudioBackend ? winAudioBackend->GetSampleRate() : -1; - case SYSPROP_DISPLAY_REFRESH_RATE: - return 60000; case SYSPROP_DEVICE_TYPE: return DEVICE_TYPE_DESKTOP; - case SYSPROP_DISPLAY_DPI: - return ScreenDPI(); case SYSPROP_DISPLAY_COUNT: return GetSystemMetrics(SM_CMONITORS); default: @@ -245,6 +241,17 @@ int System_GetPropertyInt(SystemProperty prop) { } } +float System_GetPropertyFloat(SystemProperty prop) { + switch (prop) { + case SYSPROP_DISPLAY_REFRESH_RATE: + return 60.f; + case SYSPROP_DISPLAY_DPI: + return (float)ScreenDPI(); + default: + return -1; + } +} + bool System_GetPropertyBool(SystemProperty prop) { switch (prop) { case SYSPROP_HAS_FILE_BROWSER: @@ -290,7 +297,7 @@ void System_SendMessage(const char *command, const char *parameter) { } else if (!strcmp(command, "browse_file")) { MainWindow::BrowseAndBoot(""); } else if (!strcmp(command, "browse_folder")) { - I18NCategory *mm = GetI18NCategory("MainMenu"); + auto mm = GetI18NCategory("MainMenu"); std::string folder = W32Util::BrowseForFolder(MainWindow::GetHWND(), mm->T("Choose folder")); if (folder.size()) NativeMessageReceived("browse_folderSelect", folder.c_str()); @@ -374,17 +381,17 @@ static std::string GetDefaultLangRegion() { static const int EXIT_CODE_VULKAN_WORKS = 42; static bool DetectVulkanInExternalProcess() { - wchar_t moduleFilename[MAX_PATH]; - wchar_t workingDirectory[MAX_PATH]; - GetCurrentDirectoryW(MAX_PATH, workingDirectory); + std::wstring workingDirectory; + std::wstring moduleFilename; + W32Util::GetSelfExecuteParams(workingDirectory, moduleFilename); + const wchar_t *cmdline = L"--vulkan-available-check"; - GetModuleFileName(GetModuleHandle(NULL), moduleFilename, MAX_PATH); SHELLEXECUTEINFO info{ sizeof(SHELLEXECUTEINFO) }; info.fMask = SEE_MASK_NOCLOSEPROCESS; - info.lpFile = moduleFilename; + info.lpFile = moduleFilename.c_str(); info.lpParameters = cmdline; - info.lpDirectory = workingDirectory; + info.lpDirectory = workingDirectory.c_str(); info.nShow = SW_HIDE; if (ShellExecuteEx(&info) != TRUE) { return false; @@ -491,8 +498,9 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin LogManager::Init(); - // On Win32 it makes more sense to initialize the system directories here + // On Win32 it makes more sense to initialize the system directories here // because the next place it was called was in the EmuThread, and it's too late by then. + g_Config.internalDataDirectory = W32Util::UserDocumentsPath(); InitSysDirectories(); // Load config up here, because those changes below would be overwritten @@ -588,7 +596,7 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin // - The -l switch is expected to show the log console, REGARDLESS of config settings. // - It should be possible to log to a file without showing the console. LogManager::GetInstance()->GetConsoleListener()->Init(showLog, 150, 120, "PPSSPP Debug Console"); - + if (debugLogLevel) LogManager::GetInstance()->SetAllLogLevels(LogTypes::LDEBUG); @@ -602,7 +610,7 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin HWND hwndMain = MainWindow::GetHWND(); HWND hwndDisplay = MainWindow::GetDisplayHWND(); - + //initialize custom controls CtrlDisAsmView::init(); CtrlMemView::init(); @@ -635,7 +643,7 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin { //hack to enable/disable menu command accelerate keys MainWindow::UpdateCommands(); - + //hack to make it possible to get to main window from floating windows with Esc if (msg.hwnd != hwndMain && msg.wParam == VK_ESCAPE) BringWindowToTop(hwndMain); diff --git a/Windows/ppsspp_gold.ico b/Windows/ppsspp_gold.ico index 999b4fa0ac..36e3c7bd64 100644 Binary files a/Windows/ppsspp_gold.ico and b/Windows/ppsspp_gold.ico differ diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 00b2c74ea9..da3f9312bb 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -275,6 +275,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/ELF/ParamSFO.cpp \ $(SRC)/Core/HW/SimpleAudioDec.cpp \ $(SRC)/Core/HW/AsyncIOManager.cpp \ + $(SRC)/Core/HW/Camera.cpp \ $(SRC)/Core/HW/MemoryStick.cpp \ $(SRC)/Core/HW/MpegDemux.cpp.arm \ $(SRC)/Core/HW/MediaEngine.cpp.arm \ diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index 1f9655b3a7..0ceace3732 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -46,6 +46,8 @@ #include "Core/ConfigValues.h" #include "Core/Loaders.h" #include "Core/System.h" +#include "Core/HLE/sceUsbCam.h" +#include "Core/HLE/sceUsbGps.h" #include "Common/CPUDetect.h" #include "Common/Log.h" #include "UI/GameInfoCache.h" @@ -301,8 +303,15 @@ int System_GetPropertyInt(SystemProperty prop) { return optimalSampleRate; case SYSPROP_AUDIO_OPTIMAL_FRAMES_PER_BUFFER: return optimalFramesPerBuffer; + default: + return -1; + } +} + +float System_GetPropertyFloat(SystemProperty prop) { + switch (prop) { case SYSPROP_DISPLAY_REFRESH_RATE: - return (int)(display_hz * 1000.0); + return display_hz; default: return -1; } @@ -680,7 +689,7 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayRender(JNIEnv *env, hasSetThreadName = true; setCurrentThreadName("AndroidRender"); } - + if (useCPUThread) { // This is the "GPU thread". if (graphicsContext) @@ -921,18 +930,51 @@ extern "C" jint JNICALL Java_org_ppsspp_ppsspp_NativeApp_getDesiredBackbufferHei return desiredBackbufferSizeY; } -extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_pushNewGpsData(JNIEnv *, jclass, - jfloat latitude, jfloat longitude, jfloat altitude, jfloat speed, jfloat bearing, jlong time) { - PushNewGpsData(latitude, longitude, altitude, speed, bearing, time); + +std::vector __cameraGetDeviceList() { + jclass cameraClass = findClass("org/ppsspp/ppsspp/CameraHelper"); + jmethodID deviceListMethod = getEnv()->GetStaticMethodID(cameraClass, "getDeviceList", "()Ljava/util/ArrayList;"); + jobject deviceListObject = getEnv()->CallStaticObjectMethod(cameraClass, deviceListMethod); + jclass arrayListClass = getEnv()->FindClass("java/util/ArrayList"); + jmethodID arrayListSize = getEnv()->GetMethodID(arrayListClass, "size", "()I"); + jmethodID arrayListGet = getEnv()->GetMethodID(arrayListClass, "get", "(I)Ljava/lang/Object;"); + + jint arrayListObjectLen = getEnv()->CallIntMethod(deviceListObject, arrayListSize); + std::vector deviceListVector; + + for (int i=0; i < arrayListObjectLen; i++) { + jstring dev = static_cast(getEnv()->CallObjectMethod(deviceListObject, arrayListGet, i)); + const char* cdev = getEnv()->GetStringUTFChars(dev, nullptr); + deviceListVector.push_back(cdev); + getEnv()->ReleaseStringUTFChars(dev, cdev); + getEnv()->DeleteLocalRef(dev); + } + return deviceListVector; } -extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_pushCameraImage(JNIEnv *env, jclass, +extern "C" jint Java_org_ppsspp_ppsspp_NativeApp_getSelectedCamera(JNIEnv *, jclass) { + int cameraId = 0; + sscanf(g_Config.sCameraDevice.c_str(), "%d:", &cameraId); + return cameraId; +} + +extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_setGpsDataAndroid(JNIEnv *, jclass, + jlong time, jfloat hdop, jfloat latitude, jfloat longitude, jfloat altitude, jfloat speed, jfloat bearing) { + GPS::setGpsData(time, hdop, latitude, longitude, altitude, speed, bearing); +} + +extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_setSatInfoAndroid(JNIEnv *, jclass, + jshort index, jshort id, jshort elevation, jshort azimuth, jshort snr, jshort good) { + GPS::setSatInfo(index, id, elevation, azimuth, snr, good); +} + +extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_pushCameraImageAndroid(JNIEnv *env, jclass, jbyteArray image) { if (image != NULL) { jlong size = env->GetArrayLength(image); jbyte* buffer = env->GetByteArrayElements(image, NULL); - PushCameraImage(size, (unsigned char *)buffer); + Camera::pushCameraImage(size, (unsigned char *)buffer); env->ReleaseByteArrayElements(image, buffer, JNI_ABORT); } } diff --git a/android/src/org/ppsspp/ppsspp/CameraHelper.java b/android/src/org/ppsspp/ppsspp/CameraHelper.java index 9979b81660..2b46fe8393 100644 --- a/android/src/org/ppsspp/ppsspp/CameraHelper.java +++ b/android/src/org/ppsspp/ppsspp/CameraHelper.java @@ -15,6 +15,7 @@ import android.view.WindowManager; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.ArrayList; import java.util.List; @TargetApi(23) @@ -22,8 +23,11 @@ import java.util.List; class CameraHelper { private static final String TAG = CameraHelper.class.getSimpleName(); private Display mDisplay; + private int mTargetWidth = 0; + private int mTargetHeight = 0; private Camera mCamera = null; private boolean mIsCameraRunning = false; + private int mCameraFacing = 0; private int mCameraOrientation = 0; private Camera.Size mPreviewSize = null; private long mLastFrameTime = 0; @@ -38,7 +42,11 @@ class CameraHelper { case Surface.ROTATION_180: displayDegrees = 180; break; case Surface.ROTATION_270: displayDegrees = 270; break; } - return (mCameraOrientation - displayDegrees + 360) % 360; + if (mCameraFacing == Camera.CameraInfo.CAMERA_FACING_FRONT) { + return (mCameraOrientation + displayDegrees) % 360; + } else { + return (mCameraOrientation - displayDegrees + 360) % 360; + } } static byte[] rotateNV21(final byte[] input, final int inWidth, final int inHeight, @@ -104,20 +112,16 @@ class CameraHelper { } mLastFrameTime = currentTime; - // the expected values arrives in sceUsbCamSetupVideo - int targetW = 480; - int targetH = 272; - int cameraRotation = getCameraRotation(); byte[] newPreviewData = rotateNV21(previewData, mPreviewSize.width, mPreviewSize.height, - targetW, targetH, cameraRotation); - YuvImage yuvImage = new YuvImage(newPreviewData, ImageFormat.NV21, targetW, targetH, null); + mTargetWidth, mTargetHeight, cameraRotation); + YuvImage yuvImage = new YuvImage(newPreviewData, ImageFormat.NV21, mTargetWidth, mTargetHeight, null); ByteArrayOutputStream baos = new ByteArrayOutputStream(); // convert to Jpeg - Rect crop = new Rect(0, 0, targetW, targetH); + Rect crop = new Rect(0, 0, mTargetWidth, mTargetHeight); yuvImage.compressToJpeg(crop, 80, baos); - NativeApp.pushCameraImage(baos.toByteArray()); + NativeApp.pushCameraImageAndroid(baos.toByteArray()); try { baos.close(); } catch (IOException e) { @@ -132,14 +136,34 @@ class CameraHelper { mSurfaceTexture = new SurfaceTexture(10); } + static ArrayList getDeviceList() { + ArrayList deviceList = new ArrayList<>(); + int nrCam = Camera.getNumberOfCameras(); + for (int index = 0; index < nrCam; index++) { + Camera.CameraInfo info = new Camera.CameraInfo(); + Camera.getCameraInfo(index, info); + String devName = index + ":" + (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK ? "Back Camera" : "Front Camera"); + deviceList.add(devName); + } + return deviceList; + } + + void setCameraSize(int width, int height) { + mTargetWidth = width; + mTargetHeight = height; + } + void startCamera() { - Log.d(TAG, "startCamera"); try { - Camera.CameraInfo info = new android.hardware.Camera.CameraInfo(); - Camera.getCameraInfo(0, info); + int cameraId = NativeApp.getSelectedCamera(); + Log.d(TAG, "startCamera [id=" + cameraId + ", res=" + mTargetWidth + "x" + mTargetHeight + "]"); + + Camera.CameraInfo info = new Camera.CameraInfo(); + Camera.getCameraInfo(cameraId, info); + mCameraFacing = info.facing; mCameraOrientation = info.orientation; - mCamera = Camera.open(); + mCamera = Camera.open(cameraId); Camera.Parameters param = mCamera.getParameters(); // Set preview size diff --git a/android/src/org/ppsspp/ppsspp/LocationHelper.java b/android/src/org/ppsspp/ppsspp/LocationHelper.java index a71bcaeb19..f5582964df 100644 --- a/android/src/org/ppsspp/ppsspp/LocationHelper.java +++ b/android/src/org/ppsspp/ppsspp/LocationHelper.java @@ -1,16 +1,30 @@ package org.ppsspp.ppsspp; +import android.annotation.TargetApi; import android.content.Context; +import android.location.GnssStatus; +import android.location.GpsSatellite; +import android.location.GpsStatus; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; +import android.location.OnNmeaMessageListener; +import android.os.Build; import android.os.Bundle; import android.util.Log; +import java.util.Iterator; + class LocationHelper implements LocationListener { - private static final String TAG = "LocationHelper"; + private static final String TAG = LocationHelper.class.getSimpleName(); private LocationManager mLocationManager; private boolean mLocationEnable; + private GpsStatus.Listener mGpsStatusListener; + private GnssStatus.Callback mGnssStatusCallback; + private OnNmeaMessageListener mNmeaMessageListener; + private GpsStatus.NmeaListener mNmeaListener; + private float mAltitudeAboveSeaLevel = 0f; + private float mHdop = 0f; LocationHelper(Context context) { mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); @@ -27,6 +41,37 @@ class LocationHelper implements LocationListener { isNetworkEnabled = mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER); mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 0, this); mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 1000, 0, this); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + mGnssStatusCallback = new GnssStatus.Callback() { + @Override + public void onSatelliteStatusChanged(GnssStatus status) { + onSatelliteStatus(status); + } + }; + mLocationManager.registerGnssStatusCallback(mGnssStatusCallback); + mNmeaMessageListener = new OnNmeaMessageListener() { + @Override + public void onNmeaMessage(String message, long timestamp) { + onNmea(message); + } + }; + mLocationManager.addNmeaListener(mNmeaMessageListener); + } else { + mGpsStatusListener = new GpsStatus.Listener() { + @Override + public void onGpsStatusChanged(int event) { + onGpsStatus(event); + } + }; + mLocationManager.addGpsStatusListener(mGpsStatusListener); + mNmeaListener = new GpsStatus.NmeaListener() { + @Override + public void onNmeaReceived(long timestamp, String nmea) { + onNmea(nmea); + } + }; + mLocationManager.addNmeaListener(mNmeaListener); + } mLocationEnable = true; } catch (SecurityException e) { Log.e(TAG, "Cannot start location updates: " + e.toString()); @@ -40,22 +85,45 @@ class LocationHelper implements LocationListener { void stopLocationUpdates() { Log.d(TAG, "stopLocationUpdates"); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + if (mGnssStatusCallback != null) { + mLocationManager.unregisterGnssStatusCallback(mGnssStatusCallback); + mGnssStatusCallback = null; + } + if (mNmeaMessageListener != null) { + mLocationManager.removeNmeaListener(mNmeaMessageListener); + mNmeaMessageListener = null; + } + } else { + if (mGpsStatusListener != null) { + mLocationManager.removeGpsStatusListener(mGpsStatusListener); + mGpsStatusListener = null; + } + if (mNmeaListener != null) { + mLocationManager.removeNmeaListener(mNmeaListener); + mNmeaListener = null; + } + } if (mLocationEnable) { mLocationEnable = false; mLocationManager.removeUpdates(this); } } + + /* + * LocationListener + */ + @Override public void onLocationChanged(Location location) { + long time = location.getTime() / 1000; // ms to s !! float latitude = (float) location.getLatitude(); float longitude = (float) location.getLongitude(); - float altitude = (float) location.getAltitude(); - float speed = location.getSpeed(); + float speed = location.getSpeed() * 3.6f; // m/s to km/h !! float bearing = location.getBearing(); - long time = location.getTime() / 1000; // ms to s !! - NativeApp.pushNewGpsData(latitude, longitude, altitude, speed, bearing, time); + NativeApp.setGpsDataAndroid(time, mHdop, latitude, longitude, mAltitudeAboveSeaLevel, speed, bearing); } @Override @@ -69,4 +137,67 @@ class LocationHelper implements LocationListener { @Override public void onProviderDisabled(String provider) { } + + + + @TargetApi(Build.VERSION_CODES.N) + private void onSatelliteStatus(GnssStatus status) { + short index = 0; + for (short i = 0; i < status.getSatelliteCount(); i++) { + if (status.getConstellationType(i) != GnssStatus.CONSTELLATION_GPS) { + continue; + } + NativeApp.setSatInfoAndroid(index, (short) status.getSvid(i), (short) status.getElevationDegrees(i), + (short) status.getAzimuthDegrees(i), (short) status.getCn0DbHz(i), status.usedInFix(i) ? (short) 1 : (short) 0); + index++; + if (index == 24) { + break; + } + } + } + + private void onGpsStatus(int event) { + switch (event) { + case GpsStatus.GPS_EVENT_STARTED: + case GpsStatus.GPS_EVENT_STOPPED: + case GpsStatus.GPS_EVENT_FIRST_FIX: + break; + case GpsStatus.GPS_EVENT_SATELLITE_STATUS: { + try { + GpsStatus gpsStatus = mLocationManager.getGpsStatus(null); + Iterable satellites = gpsStatus.getSatellites(); + + short index = 0; + for (Iterator iterator = satellites.iterator(); iterator.hasNext(); ) { + GpsSatellite satellite = iterator.next(); + if (satellite.getPrn() > 37) { + continue; + } + NativeApp.setSatInfoAndroid(index, (short) satellite.getPrn(), (short) satellite.getElevation(), + (short) satellite.getAzimuth(), (short) satellite.getSnr(), satellite.usedInFix() ? (short) 1 : (short) 0); + index++; + if (index == 24) { + break; + } + } + } catch (SecurityException e) { + Log.e(TAG, e.toString()); + } + break; + } + } + } + + private void onNmea(String nmea) { + String[] tokens = nmea.split(","); + if (tokens.length < 10 || !tokens[0].equals("$GPGGA")) { + return; + } + if (!tokens[8].isEmpty()) { + mHdop = Float.valueOf(tokens[8]); + } + if (!tokens[9].isEmpty()) { + mAltitudeAboveSeaLevel = Float.valueOf(tokens[9]); + } + } } diff --git a/android/src/org/ppsspp/ppsspp/NativeActivity.java b/android/src/org/ppsspp/ppsspp/NativeActivity.java index 5676afee23..700318133a 100644 --- a/android/src/org/ppsspp/ppsspp/NativeActivity.java +++ b/android/src/org/ppsspp/ppsspp/NativeActivity.java @@ -53,6 +53,8 @@ import java.io.File; import java.lang.reflect.Field; import java.util.List; import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public abstract class NativeActivity extends Activity implements SurfaceHolder.Callback { // Remember to loadLibrary your JNI .so in a static {} block @@ -557,10 +559,13 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C mGLSurfaceView.setEGLConfigChooser(new NativeEGLConfigChooser()); } else { // Tried to mess around with config choosers (NativeEGLConfigChooser) here but fail completely on Xperia Play. - // On the other hand, I think from ICS we should be safe to at least require 8888 and stencil... - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH && !Build.MANUFACTURER.equals("Amazon")) { - mGLSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 8); - } + + // Then I tried to require 8888/16/8 but that backfired too, does not work on Mali 450 which is + // used in popular TVs and boxes like Mi Box. So we'll just get what we get, I guess... + + // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH && !Build.MANUFACTURER.equals("Amazon")) { + // mGLSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 8); + // } } mGLSurfaceView.setRenderer(nativeRenderer); setContentView(mGLSurfaceView); @@ -1393,7 +1398,14 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C mLocationHelper.stopLocationUpdates(); } } else if (command.equals("camera_command")) { - if (params.equals("startVideo")) { + if (params.startsWith("startVideo")) { + Pattern pattern = Pattern.compile("startVideo_(\\d+)x(\\d+)"); + Matcher matcher = pattern.matcher(params); + if (!matcher.matches()) + return false; + int width = Integer.parseInt(matcher.group(1)); + int height = Integer.parseInt(matcher.group(2)); + mCameraHelper.setCameraSize(width, height); if (mCameraHelper != null && !askForPermissions(permissionsForCamera, REQUEST_CODE_CAMERA_PERMISSION)) { mCameraHelper.startCamera(); } @@ -1409,12 +1421,6 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C // Only keep the screen bright ingame. window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } - } else if (command.equals("event")) { - if (params.equals("exitgame")) { - if (mCameraHelper != null) { - mCameraHelper.stopCamera(); - } - } } return false; } diff --git a/android/src/org/ppsspp/ppsspp/NativeApp.java b/android/src/org/ppsspp/ppsspp/NativeApp.java index 140f3edd59..fec8c5c002 100644 --- a/android/src/org/ppsspp/ppsspp/NativeApp.java +++ b/android/src/org/ppsspp/ppsspp/NativeApp.java @@ -51,7 +51,8 @@ public class NativeApp { public static native String queryConfig(String queryName); - public static native void pushNewGpsData(float latitude, float longitude, float altitude, float speed, float bearing, long time); - - public static native void pushCameraImage(byte[] image); + public static native int getSelectedCamera(); + public static native void setGpsDataAndroid(long time, float hdop, float latitude, float longitude, float altitude, float speed, float bearing); + public static native void setSatInfoAndroid(short index, short id, short elevation, short azimuth, short snr, short good); + public static native void pushCameraImageAndroid(byte[] image); } diff --git a/assets/compat.ini b/assets/compat.ini index df1bea3498..621ebbb13d 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -710,6 +710,10 @@ ULUS10433 = true ULES01376 = true ULUS10466 = true +# Hot Wheels - Ultimate Racing (issue #12604, this is a workaround) +ULUS10239 = true +ULES00828 = true + [JitInvalidationHack] # This is an absolutely awful hack that somehow prevents issues when clearing the JIT, # if the game has copied code with EmuHack opcodes or something. Hopefully will be able diff --git a/assets/gamecontrollerdb.txt b/assets/gamecontrollerdb.txt index cbc06396c4..76ba6498a5 100644 --- a/assets/gamecontrollerdb.txt +++ b/assets/gamecontrollerdb.txt @@ -78,6 +78,7 @@ d814000000000000cecf000000000000,MC Cthulhu,platform:Mac OS X,leftx:,lefty:,righ 5e04000000000000ea02000000000000,Xbox Wireless Controller,platform:Mac OS X,x:b2,a:b0,b:b1,y:b3,back:b9,guide:b10,start:b8,dpleft:b13,dpdown:b12,dpright:b14,dpup:b11,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b6,rightstick:b7,leftx:a0,lefty:a1,rightx:a3,righty:a4, 81170000000000007e05000000000000,Sega Saturn,x:b0,a:b2,b:b4,y:b6,start:b13,dpleft:b15,dpdown:b16,dpright:b14,dpup:b17,leftshoulder:b8,lefttrigger:a5,lefttrigger:b10,rightshoulder:b9,righttrigger:a4,righttrigger:b11,leftx:a0,lefty:a2,platform:Mac OS X, bd1200000000000015d0000000000000,Tomee SNES USB Controller,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,platform:Mac OS X, +03000000632500002305000000010000,Redragon Saturn,a:b2,b:b1,x:b3,y:b0,back:b8,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Mac OS X, # Linux 050000004c0500006802000000000000,Sony PLAYSTATION(R)3 Controller,platform:Linux,a:b14,b:b13,x:b15,y:b12,back:b0,guide:b16,start:b3,leftstick:b1,rightstick:b2,leftshoulder:b10,rightshoulder:b11,dpup:b4,dpdown:b6,dpleft:b7,dpright:b5,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a12,righttrigger:a13, diff --git a/assets/lang b/assets/lang index 6189eb455e..3e05ae374e 160000 --- a/assets/lang +++ b/assets/lang @@ -1 +1 @@ -Subproject commit 6189eb455e4b184ede558f7354e6ba31b485fd58 +Subproject commit 3e05ae374e9b8baab899d6f10141af870828c643 diff --git a/ext/native/base/NativeApp.h b/ext/native/base/NativeApp.h index 623332d37e..f1f348acc7 100644 --- a/ext/native/base/NativeApp.h +++ b/ext/native/base/NativeApp.h @@ -1,6 +1,7 @@ #pragma once #include +#include // The Native App API. // @@ -147,7 +148,8 @@ enum SystemProperty { SYSPROP_SYSTEMVERSION, SYSPROP_DISPLAY_XRES, SYSPROP_DISPLAY_YRES, - SYSPROP_DISPLAY_REFRESH_RATE, // returns 1000*the refresh rate in Hz as it can be non-integer + SYSPROP_DISPLAY_REFRESH_RATE, + SYSPROP_DISPLAY_LOGICAL_DPI, SYSPROP_DISPLAY_DPI, SYSPROP_DISPLAY_COUNT, SYSPROP_MOGA_VERSION, @@ -172,7 +174,7 @@ enum SystemProperty { std::string System_GetProperty(SystemProperty prop); int System_GetPropertyInt(SystemProperty prop); +float System_GetPropertyFloat(SystemProperty prop); bool System_GetPropertyBool(SystemProperty prop); -void PushNewGpsData(float latitude, float longitude, float altitude, float speed, float bearing, long long time); -void PushCameraImage(long long length, unsigned char* image); +std::vector __cameraGetDeviceList(); diff --git a/ext/native/base/buffer.cpp b/ext/native/base/buffer.cpp index 8e48848319..68cc412dd3 100644 --- a/ext/native/base/buffer.cpp +++ b/ext/native/base/buffer.cpp @@ -26,9 +26,13 @@ Buffer::Buffer() { } Buffer::~Buffer() { } char *Buffer::Append(ssize_t length) { - size_t old_size = data_.size(); - data_.resize(old_size + length); - return &data_[0] + old_size; + if (length > 0) { + size_t old_size = data_.size(); + data_.resize(old_size + length); + return &data_[0] + old_size; + } else { + return nullptr; + } } void Buffer::Append(const std::string &str) { @@ -44,8 +48,10 @@ void Buffer::Append(const char *str) { void Buffer::Append(const Buffer &other) { size_t len = other.size(); - char *dest = Append(len); - memcpy(dest, &other.data_[0], len); + if (len > 0) { + char *dest = Append(len); + memcpy(dest, &other.data_[0], len); + } } void Buffer::AppendValue(int value) { @@ -175,7 +181,7 @@ bool Buffer::ReadAll(int fd, int hintSize) { } else if (hintSize >= 1024 * 16) { buf.resize(hintSize / 16); } else { - buf.resize(1024); + buf.resize(4096); } while (true) { diff --git a/ext/native/i18n/i18n.cpp b/ext/native/i18n/i18n.cpp index 974610efd4..958f162cae 100644 --- a/ext/native/i18n/i18n.cpp +++ b/ext/native/i18n/i18n.cpp @@ -15,7 +15,7 @@ std::string I18NRepo::LanguageID() { void I18NRepo::Clear() { for (auto iter = cats_.begin(); iter != cats_.end(); ++iter) { - delete iter->second; + iter->second.reset(); } cats_.clear(); } @@ -52,14 +52,14 @@ void I18NCategory::SetMap(const std::map &m) { } } -I18NCategory *I18NRepo::GetCategory(const char *category) { +std::shared_ptr I18NRepo::GetCategory(const char *category) { auto iter = cats_.find(category); if (iter != cats_.end()) { return iter->second; } else { I18NCategory *c = new I18NCategory(this, category); - cats_[category] = c; - return c; + cats_[category].reset(c); + return cats_[category]; } } @@ -96,7 +96,7 @@ bool I18NRepo::LoadIni(const std::string &languageID, const std::string &overrid for (auto iter = sections.begin(); iter != sections.end(); ++iter) { if (iter->name() != "") { - cats_[iter->name()] = LoadSection(&(*iter), iter->name().c_str()); + cats_[iter->name()].reset(LoadSection(&(*iter), iter->name().c_str())); } } @@ -124,7 +124,7 @@ void I18NRepo::SaveIni(const std::string &languageID) { ini.Save(GetIniPath(languageID)); } -void I18NRepo::SaveSection(IniFile &ini, IniFile::Section *section, I18NCategory *cat) { +void I18NRepo::SaveSection(IniFile &ini, IniFile::Section *section, std::shared_ptr cat) { const std::map &missed = cat->Missed(); for (auto iter = missed.begin(); iter != missed.end(); ++iter) { diff --git a/ext/native/i18n/i18n.h b/ext/native/i18n/i18n.h index ede7f1e8a6..5a527d6646 100644 --- a/ext/native/i18n/i18n.h +++ b/ext/native/i18n/i18n.h @@ -9,6 +9,7 @@ // As usual, everything is UTF-8. Nothing else allowed. #include +#include #include #include @@ -76,7 +77,7 @@ public: std::string LanguageID(); - I18NCategory *GetCategory(const char *categoryName); + std::shared_ptr GetCategory(const char *categoryName); bool HasCategory(const char *categoryName) const { return cats_.find(categoryName) != cats_.end(); } @@ -86,9 +87,9 @@ private: std::string GetIniPath(const std::string &languageID) const; void Clear(); I18NCategory *LoadSection(const IniFile::Section *section, const char *name); - void SaveSection(IniFile &ini, IniFile::Section *section, I18NCategory *cat); + void SaveSection(IniFile &ini, IniFile::Section *section, std::shared_ptr cat); - std::map cats_; + std::map> cats_; std::string languageID_; DISALLOW_COPY_AND_ASSIGN(I18NRepo); @@ -98,7 +99,7 @@ extern I18NRepo i18nrepo; // These are simply talking to the one global instance of I18NRepo. -inline I18NCategory *GetI18NCategory(const char *categoryName) { +inline std::shared_ptr GetI18NCategory(const char *categoryName) { if (!categoryName) return nullptr; return i18nrepo.GetCategory(categoryName); diff --git a/ext/native/net/http_client.cpp b/ext/native/net/http_client.cpp index 05bb656830..91fec283a8 100644 --- a/ext/native/net/http_client.cpp +++ b/ext/native/net/http_client.cpp @@ -383,6 +383,11 @@ int Client::ReadResponseEntity(Buffer *readbuf, const std::vector & } } + if (contentLength < 0) { + // Just sanity checking... + contentLength = 0; + } + if (!contentLength && progress) { // Content length is unknown. // Set progress to 1% so it looks like something is happening... diff --git a/ext/native/thin3d/d3dx9_loader.cpp b/ext/native/thin3d/d3dx9_loader.cpp index d7e5fa807f..f269ecbe04 100644 --- a/ext/native/thin3d/d3dx9_loader.cpp +++ b/ext/native/thin3d/d3dx9_loader.cpp @@ -52,15 +52,18 @@ bool checkDllExistsW(const WCHAR* fname, bool bIncludeExeDir) { return ::GetFileAttributesW(fname) != DWORD(-1); } -bool checkExistsDllW(const WCHAR* sDllFilename) { - std::wstring sfullpath; - WCHAR bufsysdir[MAX_PATH]; - - if (!::GetSystemDirectoryW(bufsysdir, MAX_PATH)) { +bool checkExistsDllW(const WCHAR *sDllFilename) { + std::wstring sfullpath(MAX_PATH, '\0'); + size_t sz = ::GetSystemDirectoryW(&sfullpath[0], (UINT)sfullpath.size()); + if (sz >= sfullpath.size()) { + sfullpath.resize(sz); + sz = ::GetSystemDirectoryW(&sfullpath[0], (UINT)sfullpath.size()); + } + sfullpath.resize(sz); + if (sz == 0) { throw (std::runtime_error("system error")); } - sfullpath = bufsysdir; sfullpath += L'\\'; sfullpath += sDllFilename; diff --git a/ext/native/thin3d/thin3d.cpp b/ext/native/thin3d/thin3d.cpp index 340157708d..2d3fdcc2f8 100644 --- a/ext/native/thin3d/thin3d.cpp +++ b/ext/native/thin3d/thin3d.cpp @@ -262,7 +262,7 @@ static const std::vector vsCol = { "};\n" "VS_OUTPUT main(VS_INPUT input) {\n" " VS_OUTPUT output;\n" - " output.Position = mul(float4(input.Position, 1.0), WorldViewProj);\n" + " output.Position = mul(WorldViewProj, float4(input.Position, 1.0));\n" " output.Color0 = input.Color0;\n" " return output;\n" "}\n" diff --git a/ext/native/ui/ui_screen.cpp b/ext/native/ui/ui_screen.cpp index b2662ba385..9ed9aa2b38 100644 --- a/ext/native/ui/ui_screen.cpp +++ b/ext/native/ui/ui_screen.cpp @@ -233,7 +233,7 @@ UI::EventReturn UIScreen::OnCancel(UI::EventParams &e) { PopupScreen::PopupScreen(std::string title, std::string button1, std::string button2) : box_(0), defaultButton_(nullptr), title_(title) { - I18NCategory *di = GetI18NCategory("Dialog"); + auto di = GetI18NCategory("Dialog"); if (!button1.empty()) button1_ = di->T(button1.c_str()); if (!button2.empty()) @@ -427,7 +427,7 @@ std::string ChopTitle(const std::string &title) { UI::EventReturn PopupMultiChoice::HandleClick(UI::EventParams &e) { restoreFocus_ = HasFocus(); - I18NCategory *category = category_ ? GetI18NCategory(category_) : nullptr; + auto category = category_ ? GetI18NCategory(category_) : nullptr; std::vector choices; for (int i = 0; i < numChoices_; i++) { @@ -450,7 +450,7 @@ void PopupMultiChoice::Update() { void PopupMultiChoice::UpdateText() { if (!choices_) return; - I18NCategory *category = GetI18NCategory(category_); + auto category = GetI18NCategory(category_); // Clamp the value to be safe. if (*value_ < minVal_ || *value_ > minVal_ + numChoices_ - 1) { valueText_ = "(invalid choice)"; // Shouldn't happen. Should be no need to translate this. @@ -865,7 +865,7 @@ void ChoiceWithValueDisplay::Draw(UIContext &dc) { int paddingX = 12; dc.SetFontStyle(dc.theme->uiFont); - I18NCategory *category = GetI18NCategory(category_); + auto category = GetI18NCategory(category_); std::ostringstream valueText; if (translateCallback_ && sValue_) { valueText << translateCallback_(sValue_->c_str()); diff --git a/headless/Headless.cpp b/headless/Headless.cpp index 0d655d7957..aeb48f58d8 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -74,6 +74,9 @@ std::string System_GetProperty(SystemProperty prop) { return ""; } int System_GetPropertyInt(SystemProperty prop) { return -1; } +float System_GetPropertyFloat(SystemProperty prop) { + return -1; +} bool System_GetPropertyBool(SystemProperty prop) { return false; } @@ -225,7 +228,7 @@ int main(int argc, const char* argv[]) const char *stateToLoad = 0; GPUCore gpuCore = GPUCORE_NULL; CPUCore cpuCore = CPUCore::JIT; - + std::vector testFilenames; const char *mountIso = 0; const char *mountRoot = 0; @@ -321,7 +324,7 @@ int main(int argc, const char* argv[]) LogManager::Init(); LogManager *logman = LogManager::GetInstance(); - + PrintfLogger *printfLogger = new PrintfLogger(); for (int i = 0; i < LogTypes::NUMBER_OF_LOGS; i++) { @@ -381,6 +384,7 @@ int main(int argc, const char* argv[]) g_Config.iAudioLatency = 1; #ifdef _WIN32 + g_Config.internalDataDirectory = ""; InitSysDirectories(); #endif diff --git a/headless/Headless.vcxproj b/headless/Headless.vcxproj index dd42cbe4fc..94c6d72c8a 100644 --- a/headless/Headless.vcxproj +++ b/headless/Headless.vcxproj @@ -301,7 +301,7 @@ true true USING_WIN_UI;GLEW_STATIC;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_ARCH_32=1;_CONSOLE;_UNICODE;UNICODE;%(PreprocessorDefinitions);_CRT_SECURE_NO_WARNINGS - ../dx9sdk/Include/DX11;../Common;..;../Core;../ext/glew;../ext/native + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../dx9sdk/Include/DX11;../Common;..;../Core;../ext/glew;../ext/native false StreamingSIMDExtensions2 Precise @@ -316,7 +316,7 @@ true true true - Winmm.lib;Ws2_32.lib;opengl32.lib;dsound.lib;glu32.lib;..\ffmpeg\Windows\x86\lib\avcodec.lib;..\ffmpeg\Windows\x86\lib\avformat.lib;..\ffmpeg\Windows\x86\lib\avutil.lib;..\ffmpeg\Windows\x86\lib\swresample.lib;..\ffmpeg\Windows\x86\lib\swscale.lib;comctl32.lib;d3d9.lib;d3dx9.lib;dxguid.lib;%(AdditionalDependencies) + mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;opengl32.lib;dsound.lib;glu32.lib;..\ffmpeg\Windows\x86\lib\avcodec.lib;..\ffmpeg\Windows\x86\lib\avformat.lib;..\ffmpeg\Windows\x86\lib\avutil.lib;..\ffmpeg\Windows\x86\lib\swresample.lib;..\ffmpeg\Windows\x86\lib\swscale.lib;comctl32.lib;d3d9.lib;d3dx9.lib;dxguid.lib;%(AdditionalDependencies) 0x00400000 false true @@ -336,7 +336,7 @@ true true USING_WIN_UI;GLEW_STATIC;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_ARCH_64=1;_CONSOLE;_UNICODE;UNICODE;%(PreprocessorDefinitions) - ../dx9sdk/Include/DX11;../Common;..;../Core;../ext/glew;../ext/native + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../dx9sdk/Include/DX11;../Common;..;../Core;../ext/glew;../ext/native false NotSet Precise @@ -353,7 +353,7 @@ true true true - Winmm.lib;Ws2_32.lib;opengl32.lib;dsound.lib;glu32.lib;..\ffmpeg\Windows\x86_64\lib\avcodec.lib;..\ffmpeg\Windows\x86_64\lib\avformat.lib;..\ffmpeg\Windows\x86_64\lib\avutil.lib;..\ffmpeg\Windows\x86_64\lib\swresample.lib;..\ffmpeg\Windows\x86_64\lib\swscale.lib;comctl32.lib;d3d9.lib;d3dx9.lib;dxguid.lib;%(AdditionalDependencies) + mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Winmm.lib;Ws2_32.lib;opengl32.lib;dsound.lib;glu32.lib;..\ffmpeg\Windows\x86_64\lib\avcodec.lib;..\ffmpeg\Windows\x86_64\lib\avformat.lib;..\ffmpeg\Windows\x86_64\lib\avutil.lib;..\ffmpeg\Windows\x86_64\lib\swresample.lib;..\ffmpeg\Windows\x86_64\lib\swscale.lib;comctl32.lib;d3d9.lib;d3dx9.lib;dxguid.lib;%(AdditionalDependencies) 0x00400000 false true @@ -407,7 +407,7 @@ true true USING_WIN_UI;GLEW_STATIC;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_ARCH_32=1;_CONSOLE;_UNICODE;UNICODE;%(PreprocessorDefinitions) - ../dx9sdk/Include/DX11;../Common;..;../Core;../ext/glew;../ext/native + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../dx9sdk/Include/DX11;../Common;..;../Core;../ext/glew;../ext/native false NotSet Precise @@ -424,7 +424,7 @@ true true true - shell32.lib;advapi32.lib;gdi32.lib;Winmm.lib;Ws2_32.lib;dsound.lib;..\ffmpeg\Windows\arm\lib\avcodec.lib;..\ffmpeg\Windows\arm\lib\avformat.lib;..\ffmpeg\Windows\arm\lib\avutil.lib;..\ffmpeg\Windows\arm\lib\swresample.lib;..\ffmpeg\Windows\arm\lib\swscale.lib;comctl32.lib;d3d9.lib;dxguid.lib;%(AdditionalDependencies) + mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;shell32.lib;advapi32.lib;gdi32.lib;Winmm.lib;Ws2_32.lib;dsound.lib;..\ffmpeg\Windows\arm\lib\avcodec.lib;..\ffmpeg\Windows\arm\lib\avformat.lib;..\ffmpeg\Windows\arm\lib\avutil.lib;..\ffmpeg\Windows\arm\lib\swresample.lib;..\ffmpeg\Windows\arm\lib\swscale.lib;comctl32.lib;d3d9.lib;dxguid.lib;%(AdditionalDependencies) /ignore:4049 /ignore:4217 %(AdditionalOptions) @@ -441,6 +441,7 @@ true true + diff --git a/headless/Headless.vcxproj.filters b/headless/Headless.vcxproj.filters index 962db7ee84..f084554e71 100644 --- a/headless/Headless.vcxproj.filters +++ b/headless/Headless.vcxproj.filters @@ -26,6 +26,9 @@ Other Platforms + + Windows + diff --git a/ios/CameraHelper.h b/ios/CameraHelper.h new file mode 100644 index 0000000000..45de67d5d9 --- /dev/null +++ b/ios/CameraHelper.h @@ -0,0 +1,17 @@ +#import +#import + +@protocol CameraFrameDelegate +@required +- (void) PushCameraImageIOS:(long long)len buffer:(unsigned char*)data; +@end + +@interface CameraHelper : NSObject + +@property (nonatomic, strong) id delegate; + +- (void) setCameraSize:(int)width h:(int)height; +- (void) startVideo; +- (void) stopVideo; + +@end diff --git a/ios/CameraHelper.mm b/ios/CameraHelper.mm new file mode 100644 index 0000000000..ef6a7c305a --- /dev/null +++ b/ios/CameraHelper.mm @@ -0,0 +1,158 @@ +#include +#include +#include "Core/Config.h" +#import "CameraHelper.h" +#import + +@interface CameraHelper() { + AVCaptureSession *captureSession; + AVCaptureVideoPreviewLayer *previewLayer; + int mWidth; + int mHeight; +} +@end + +@implementation CameraHelper + +std::vector __cameraGetDeviceList() { + std::vector deviceList; + for (AVCaptureDevice *device in [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) { + deviceList.push_back([device.localizedName UTF8String]); + } + return deviceList; +} + +NSString *getSelectedCamera() { + NSString *selectedCamera = [NSString stringWithCString:g_Config.sCameraDevice.c_str() encoding:[NSString defaultCStringEncoding]]; + return selectedCamera; +} + +-(int) checkPermission { + AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; + NSLog(@"CameraHelper::checkPermission %ld", (long)status); + + switch (status) { + case AVAuthorizationStatusNotDetermined: { + [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) { + if (granted) { + NSLog(@"camera permission granted"); + dispatch_async(dispatch_get_main_queue(), ^{ + [self startVideo]; + }); + } else { + NSLog(@"camera permission denied"); + } + }]; + return 1; + } + case AVAuthorizationStatusRestricted: + case AVAuthorizationStatusDenied: { + NSLog(@"camera permission denied"); + return 1; + } + case AVAuthorizationStatusAuthorized: { + return 0; + } + } +} + +-(void) setCameraSize: (int)width h:(int)height { + NSLog(@"CameraHelper::setCameraSize %dx%d", width, height); + mWidth = width; + mHeight = height; +} + +-(void) startVideo { + NSLog(@"CameraHelper::startVideo"); + if ([self checkPermission]) { + return; + } + + dispatch_async(dispatch_get_main_queue(), ^{ + NSError *error = nil; + + captureSession = [[AVCaptureSession alloc] init]; + captureSession.sessionPreset = AVCaptureSessionPresetMedium; + + AVCaptureDeviceInput *videoInput = nil; + NSString *selectedCamera = getSelectedCamera(); + for (AVCaptureDevice *device in [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) { + if ([device.localizedName isEqualToString:selectedCamera]) { + videoInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error]; + } + } + if (videoInput == nil || error) { + NSLog(@"selectedCamera error; try default device"); + + AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + if (videoDevice == nil) { + NSLog(@"videoDevice error"); + return; + } + videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:&error]; + if (videoInput == nil) { + NSLog(@"videoInput error"); + return; + } + } + [captureSession addInput:videoInput]; + + AVCaptureVideoDataOutput *videoOutput = [[AVCaptureVideoDataOutput alloc] init]; + videoOutput.videoSettings = [NSDictionary dictionaryWithObject: [NSNumber numberWithInt:kCVPixelFormatType_32BGRA] forKey: (id)kCVPixelBufferPixelFormatTypeKey]; + + [captureSession addOutput:videoOutput]; + + dispatch_queue_t queue = dispatch_queue_create("cameraQueue", NULL); + [videoOutput setSampleBufferDelegate:self queue:queue]; + + previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:captureSession]; + previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; + + [previewLayer setFrame:CGRectMake(0, 0, mWidth, mHeight)]; + + [captureSession startRunning]; + }); +} + +-(void) stopVideo { + dispatch_async(dispatch_get_main_queue(), ^{ + [captureSession stopRunning]; + }); +} + +- (void) captureOutput:(AVCaptureOutput *)captureOutput + didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer + fromConnection:(AVCaptureConnection *)connection { + CGImageRef cgImage = [self imageFromSampleBuffer:sampleBuffer]; + UIImage *theImage = [UIImage imageWithCGImage: cgImage]; + CGImageRelease(cgImage); + NSData *imageData = UIImageJPEGRepresentation(theImage, 0.6); + + [self.delegate PushCameraImageIOS:imageData.length buffer:(unsigned char*)imageData.bytes]; +} + +- (CGImageRef) imageFromSampleBuffer:(CMSampleBufferRef) sampleBuffer { + CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); + CVPixelBufferLockBaseAddress(imageBuffer,0); + void* baseAddress = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0); + size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer); + size_t width = CVPixelBufferGetWidth(imageBuffer); + size_t height = CVPixelBufferGetHeight(imageBuffer); + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + + CGContextRef inContext = CGBitmapContextCreate(baseAddress, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst); + CGImageRef inImage = CGBitmapContextCreateImage(inContext); + CGContextRelease(inContext); + + CGRect outRect = CGRectMake(0, 0, width, height); + CGContextRef outContext = CGBitmapContextCreate(nil, mWidth, mHeight, 8, mWidth * 4, colorSpace, kCGImageAlphaPremultipliedFirst); + CGContextDrawImage(outContext, outRect, inImage); + CGImageRelease(inImage); + CGImageRef outImage = CGBitmapContextCreateImage(outContext); + + CGColorSpaceRelease(colorSpace); + CVPixelBufferUnlockBaseAddress(imageBuffer, 0); + return outImage; +} + +@end diff --git a/ios/LocationHelper.h b/ios/LocationHelper.h new file mode 100644 index 0000000000..f04a8ddb99 --- /dev/null +++ b/ios/LocationHelper.h @@ -0,0 +1,18 @@ +#import +#import + +@protocol LocationHandlerDelegate +@required +- (void) SetGpsDataIOS:(CLLocation*)newLocation; +@end + +@interface LocationHelper : NSObject { + CLLocationManager *locationManager; +} + +@property(nonatomic,strong) id delegate; + +- (void) startLocationUpdates; +- (void) stopLocationUpdates; + +@end diff --git a/ios/LocationHelper.mm b/ios/LocationHelper.mm new file mode 100644 index 0000000000..237a9e12fe --- /dev/null +++ b/ios/LocationHelper.mm @@ -0,0 +1,34 @@ +#import "LocationHelper.h" + +@interface LocationHelper() +@end + +@implementation LocationHelper + +-(id) init { + NSLog(@"LocationHelper::init"); + locationManager = [[CLLocationManager alloc] init]; + [locationManager setDelegate:self]; + return self; +} + +-(void) startLocationUpdates { + NSLog(@"LocationHelper::startLocationUpdates"); + if ([locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) { + [locationManager requestWhenInUseAuthorization]; + } + [locationManager startUpdatingLocation]; +} + +-(void) stopLocationUpdates { + NSLog(@"LocationHelper::stopLocationUpdates"); + [locationManager stopUpdatingLocation]; +} + +- (void) locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { + for (id location in locations) { + [self.delegate SetGpsDataIOS:location]; + } +} + +@end diff --git a/ios/PPSSPP-Info.plist b/ios/PPSSPP-Info.plist index 8569dc54cd..74234337c8 100644 --- a/ios/PPSSPP-Info.plist +++ b/ios/PPSSPP-Info.plist @@ -4,6 +4,12 @@ UISupportsDocumentBrowser + NSLocationWhenInUseUsageDescription + Your location may be used to emulate Go!Explore, a GPS accessory + NSCameraUsageDescription + Your camera may be used to emulate Go!Cam, a camera accessory + NSMicrophoneUsageDescription + Your microphone may be used to emulate Go!Cam/Talkman, a microphone accessory CFBundleDevelopmentRegion en CFBundleDisplayName diff --git a/ios/ViewController.h b/ios/ViewController.h index 4eedd89e7c..340ea79a79 100644 --- a/ios/ViewController.h +++ b/ios/ViewController.h @@ -6,11 +6,19 @@ #import #endif #import "iCade/iCadeReaderView.h" +#import "CameraHelper.h" +#import "LocationHelper.h" -@interface ViewController : GLKViewController +@interface ViewController : GLKViewController - (void)shutdown; @end extern __unsafe_unretained ViewController* sharedViewController; +void setCameraSize(int width, int height); +void startVideo(); +void stopVideo(); +void startLocation(); +void stopLocation(); diff --git a/ios/ViewController.mm b/ios/ViewController.mm index ad334f0d8d..65549221ba 100644 --- a/ios/ViewController.mm +++ b/ios/ViewController.mm @@ -27,6 +27,8 @@ #include "Core/Config.h" #include "Core/ConfigValues.h" #include "Core/System.h" +#include "Core/HLE/sceUsbCam.h" +#include "Core/HLE/sceUsbGps.h" #include "Common/GraphicsContext.h" #include @@ -84,6 +86,8 @@ static bool threadStopped = false; __unsafe_unretained ViewController* sharedViewController; static GraphicsContext *graphicsContext; +static CameraHelper *cameraHelper; +static LocationHelper *locationHelper; @interface ViewController () { std::map iCadeToKeyMap; @@ -202,6 +206,12 @@ static GraphicsContext *graphicsContext; volume.delegate = self; [self.view addSubview:volume]; [self.view bringSubviewToFront:volume]; + + cameraHelper = [[CameraHelper alloc] init]; + [cameraHelper setDelegate:self]; + + locationHelper = [[LocationHelper alloc] init]; + [locationHelper setDelegate:self]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ NativeInitGraphics(graphicsContext); @@ -667,6 +677,39 @@ static GraphicsContext *graphicsContext; } #endif +void setCameraSize(int width, int height) { + [cameraHelper setCameraSize: width h:height]; +} + +void startVideo() { + [cameraHelper startVideo]; +} + +void stopVideo() { + [cameraHelper stopVideo]; +} + +-(void) PushCameraImageIOS:(long long)len buffer:(unsigned char*)data { + Camera::pushCameraImage(len, data); +} + +void startLocation() { + [locationHelper startLocationUpdates]; +} + +void stopLocation() { + [locationHelper stopLocationUpdates]; +} + +-(void) SetGpsDataIOS:(CLLocation *)newLocation { + GPS::setGpsData((long long)newLocation.timestamp.timeIntervalSince1970, + newLocation.horizontalAccuracy/5.0, + newLocation.coordinate.latitude, newLocation.coordinate.longitude, + newLocation.altitude, + MAX(newLocation.speed * 3.6, 0.0), /* m/s to km/h */ + 0 /* bearing */); +} + @end void OpenDirectory(const char *path) { diff --git a/ios/main.mm b/ios/main.mm index cad442c87b..107a804b6e 100644 --- a/ios/main.mm +++ b/ios/main.mm @@ -80,8 +80,6 @@ int System_GetPropertyInt(SystemProperty prop) { switch (prop) { case SYSPROP_AUDIO_SAMPLE_RATE: return 44100; - case SYSPROP_DISPLAY_REFRESH_RATE: - return 60000; case SYSPROP_DEVICE_TYPE: return DEVICE_TYPE_MOBILE; default: @@ -89,6 +87,15 @@ int System_GetPropertyInt(SystemProperty prop) { } } +float System_GetPropertyFloat(SystemProperty prop) { + switch (prop) { + case SYSPROP_DISPLAY_REFRESH_RATE: + return 60.f; + default: + return -1; + } +} + bool System_GetPropertyBool(SystemProperty prop) { switch (prop) { case SYSPROP_HAS_BACK_BUTTON: @@ -112,6 +119,21 @@ void System_SendMessage(const char *command, const char *parameter) { // [sharedViewController shutdown]; // exit(0); // }); + } else if (!strcmp(command, "camera_command")) { + if (!strncmp(parameter, "startVideo", 10)) { + int width = 0, height = 0; + sscanf(parameter, "startVideo_%dx%d", &width, &height); + setCameraSize(width, height); + startVideo(); + } else if (!strcmp(parameter, "stopVideo")) { + stopVideo(); + } + } else if (!strcmp(command, "gps_command")) { + if (!strcmp(parameter, "open")) { + startLocation(); + } else if (!strcmp(parameter, "close")) { + stopLocation(); + } } } @@ -127,7 +149,7 @@ BOOL SupportsTaptic() { return NO; } - + // http://www.mikitamanko.com/blog/2017/01/29/haptic-feedback-with-uifeedbackgenerator/ // use private API against UIDevice to determine the haptic stepping // 2 - iPhone 7 or above, full taptic feedback @@ -138,7 +160,7 @@ BOOL SupportsTaptic() } void Vibrate(int mode) { - + if(SupportsTaptic()) { PPSSPPUIApplication* app = (PPSSPPUIApplication*)[UIApplication sharedApplication]; @@ -153,10 +175,10 @@ void Vibrate(int mode) { { NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; NSArray *pattern = @[@YES, @30, @NO, @2]; - + dictionary[@"VibePattern"] = pattern; dictionary[@"Intensity"] = @2; - + AudioServicesPlaySystemSoundWithVibration(kSystemSoundID_Vibrate, nil, dictionary); } } @@ -168,15 +190,15 @@ int main(int argc, char *argv[]) fprintf(stderr, "Unable to cleanly obtain CS_DEBUGGED - probably not jailbroken. Attempting old method.\n"); ptrace(PTRACE_TRACEME, 0, 0, 0); } - + PROFILE_INIT(); - + @autoreleasepool { NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; NSString *bundlePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:@"/assets/"]; - + NativeInit(argc, (const char**)argv, documentsPath.UTF8String, bundlePath.UTF8String, NULL); - + return UIApplicationMain(argc, argv, NSStringFromClass([PPSSPPUIApplication class]), NSStringFromClass([AppDelegate class])); } } diff --git a/libretro/libretro.cpp b/libretro/libretro.cpp index cfeb70b978..488aba6bad 100644 --- a/libretro/libretro.cpp +++ b/libretro/libretro.cpp @@ -325,10 +325,10 @@ void retro_init(void) { g_Config.bEnableSound = true; g_Config.bAudioResampler = false; g_Config.iCwCheatRefreshRate = 60; - + g_Config.iFirmwareVersion = PSP_DEFAULT_FIRMWARE; g_Config.iPSPModel = PSP_MODEL_SLIM; - + LogManager::Init(); host = new LibretroHost; @@ -733,7 +733,7 @@ bool retro_serialize(void *data, size_t size) { if (useEmuThread) { EmuThreadPause(); // Does nothing if already paused } - + SaveState::SaveStart state; assert(CChunkFileReader::MeasurePtr(state) <= size); bool retVal = CChunkFileReader::SavePtr((u8 *)data, state) == CChunkFileReader::ERROR_NONE; @@ -742,7 +742,7 @@ bool retro_serialize(void *data, size_t size) { EmuThreadStart(); sleep_ms(4); } - + return retVal; } @@ -789,8 +789,15 @@ int System_GetPropertyInt(SystemProperty prop) { switch (prop) { case SYSPROP_AUDIO_SAMPLE_RATE: return SAMPLERATE; + default: + return -1; + } +} + +float System_GetPropertyFloat(SystemProperty prop) { + switch (prop) { case SYSPROP_DISPLAY_REFRESH_RATE: - return 60000; + return 60.f; default: return -1; } @@ -802,3 +809,7 @@ void NativeUpdate() {} void NativeRender(GraphicsContext *graphicsContext) {} void NativeResized() {} bool System_InputBoxGetWString(const wchar_t *title, const std::wstring &defaultvalue, std::wstring &outvalue) { return false; } + +#if PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(IOS) +std::vector __cameraGetDeviceList() { return std::vector(); } +#endif diff --git a/unittest/UnitTest.cpp b/unittest/UnitTest.cpp index 8e0d47f122..e2b94af96a 100644 --- a/unittest/UnitTest.cpp +++ b/unittest/UnitTest.cpp @@ -55,6 +55,9 @@ std::string System_GetProperty(SystemProperty prop) { return ""; } int System_GetPropertyInt(SystemProperty prop) { return -1; } +float System_GetPropertyFloat(SystemProperty prop) { + return -1; +} bool System_GetPropertyBool(SystemProperty prop) { return false; } @@ -157,7 +160,7 @@ void fcs(float angle, float &sinout, float &cosout) { int phasein = angle * (1 << BITSPERQUARTER); // Modulo phase into quarter, convert to float 0..1 float modphase = (phasein & ((1<> BITSPERQUARTER; // Recognize quarter if (!quarter) { diff --git a/unittest/UnitTests.vcxproj b/unittest/UnitTests.vcxproj index e9975c81d7..5e3c5629a5 100644 --- a/unittest/UnitTests.vcxproj +++ b/unittest/UnitTests.vcxproj @@ -264,7 +264,7 @@ true true USING_WIN_UI;GLEW_STATIC;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_ARCH_32=1;_WINDOWS;_UNICODE;UNICODE;%(PreprocessorDefinitions) - ../ext;../common;..;../ext/native;../ext/glew;../ext/zlib + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../ext;../common;..;../ext/native;../ext/glew;../ext/zlib false Size true @@ -277,7 +277,7 @@ true true true - Ws2_32.lib;winmm.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;opengl32.lib;dsound.lib;glu32.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;dxguid.lib;%(AdditionalDependencies) + mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Ws2_32.lib;winmm.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;opengl32.lib;dsound.lib;glu32.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;dxguid.lib;%(AdditionalDependencies) /ignore:4049 /ignore:4217 %(AdditionalOptions) ..\ffmpeg\Windows\x86\lib @@ -291,7 +291,7 @@ true true USING_WIN_UI;GLEW_STATIC;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_ARCH_64=1;_WINDOWS;_UNICODE;UNICODE;%(PreprocessorDefinitions) - ../ext;../common;..;../ext/native;../ext/glew;../ext/zlib + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../ext;../common;..;../ext/native;../ext/glew;../ext/zlib false true false @@ -306,7 +306,7 @@ true true true - Ws2_32.lib;winmm.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;opengl32.lib;dsound.lib;glu32.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;dxguid.lib;%(AdditionalDependencies) + mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Ws2_32.lib;winmm.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;opengl32.lib;dsound.lib;glu32.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;dxguid.lib;%(AdditionalDependencies) /ignore:4049 /ignore:4217 %(AdditionalOptions) ..\ffmpeg\Windows\x86_64\lib @@ -349,7 +349,7 @@ true true USING_WIN_UI;GLEW_STATIC;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_ARCH_32=1;_WINDOWS;_UNICODE;UNICODE;%(PreprocessorDefinitions) - ../ext;../common;..;../ext/native;../ext/glew;../ext/zlib + ..\ffmpeg\WindowsInclude;..\ffmpeg\Windows\x86_64\include;../ext;../common;..;../ext/native;../ext/glew;../ext/zlib false true false @@ -364,7 +364,7 @@ true true true - Ws2_32.lib;winmm.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;dxguid.lib;%(AdditionalDependencies) + mf.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;shlwapi.lib;Ws2_32.lib;winmm.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;dsound.lib;avcodec.lib;avformat.lib;avutil.lib;swresample.lib;swscale.lib;comctl32.lib;d3d9.lib;dxguid.lib;%(AdditionalDependencies) /ignore:4049 /ignore:4217 %(AdditionalOptions) ..\ffmpeg\Windows\arm\lib @@ -376,6 +376,7 @@ true true + true diff --git a/unittest/UnitTests.vcxproj.filters b/unittest/UnitTests.vcxproj.filters index a92e8e080c..fd685e86b1 100644 --- a/unittest/UnitTests.vcxproj.filters +++ b/unittest/UnitTests.vcxproj.filters @@ -8,10 +8,18 @@ + + Windows + - + + + {584f25d4-e7a1-461f-ba21-7588497a83f1} + + + \ No newline at end of file